Apakah ada cara untuk mengakses penghitung-iterasi di Java untuk setiap loop?

274

Apakah ada cara di Jawa untuk setiap loop

for(String s : stringArray) {
  doSomethingWith(s);
}

untuk mengetahui seberapa sering loop telah diproses?

Selain menggunakan for(int i=0; i < boundary; i++)loop lama dan terkenal , adalah konstruknya

int i = 0;
for(String s : stringArray) {
  doSomethingWith(s);
  i++;
}

satu-satunya cara untuk memiliki penghitung seperti itu tersedia di setiap loop?

Kosi2801
sumber
2
Sayang sekali adalah bahwa Anda tidak dapat menggunakan variabel loop di luar loop,Type var = null; for (var : set) dosomething; if (var != null) then ...
Val
@ Val kecuali rujukannya final efektif. Lihat jawaban saya untuk bagaimana menggunakan fitur ini
rmuller

Jawaban:

205

Tidak, tetapi Anda bisa menyediakan loket sendiri.

Alasan untuk ini adalah bahwa untuk-setiap loop secara internal tidak memiliki penghitung; itu didasarkan pada antarmuka Iterable , yaitu menggunakan Iteratoruntuk loop melalui "koleksi" - yang mungkin bukan koleksi sama sekali, dan mungkin sebenarnya sesuatu yang sama sekali tidak didasarkan pada indeks (seperti daftar tertaut).

Michael Borgwardt
sumber
5
Ruby memiliki konstruksi untuk ini dan Java juga harus mendapatkannya ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza
9
Jawabannya harus dimulai dengan "Tidak, tetapi Anda dapat memberikan counter Anda sendiri."
user1094206
1
FYI @NicholasDiPiazza ada each_with_indexmetode loop Enumerabledi Ruby. Lihat apidock.com/ruby/Enumerable/each_with_index
saywhatnow
@saywhatnow ya itulah yang saya maksudkan maaf - tidak yakin apa yang saya merokok ketika saya membuat komentar sebelumnya
Nicholas DiPiazza
2
"Alasan untuk ini adalah bahwa untuk-setiap loop secara internal tidak memiliki penghitung". Harus dibaca sebagai "pengembang Java tidak peduli untuk membiarkan programmer menentukan variabel indeks dengan nilai awal di dalam forloop", sesuatu sepertifor (int i = 0; String s: stringArray; ++i)
izogfif
64

Ada cara lain.

Mengingat bahwa Anda menulis Indexkelas Anda sendiri dan metode statis yang mengembalikan Iterablecontoh lebih dari kelas ini Anda bisa

for (Index<String> each: With.index(stringArray)) {
    each.value;
    each.index;
    ...
}

Di mana implementasi With.indexadalah sesuatu seperti

class With {
    public static <T> Iterable<Index<T>> index(final T[] array) {
        return new Iterable<Index<T>>() {
            public Iterator<Index<T>> iterator() {
                return new Iterator<Index<T>>() {
                    index = 0;
                    public boolean hasNext() { return index < array.size }
                    public Index<T> next() { return new Index(array[index], index++); }
                    ...
                }
            }
        }
    }
}
akuhn
sumber
7
Ide yang rapi. Saya akan terangkat jika bukan karena penciptaan objek singkat dari kelas Indeks.
mR_fr0g
3
@ mR_fr0g jangan khawatir, saya membandingkan ini dan membuat semua objek ini tidak lebih lambat daripada menggunakan kembali objek yang sama untuk setiap iterasi. Alasannya adalah bahwa semua benda ini dialokasikan di ruang eden saja dan tidak pernah hidup cukup lama untuk mencapai tumpukan. Jadi mengalokasikannya secepat misalnya mengalokasikan variabel lokal.
akuhn
1
@ Akuhn Tunggu. Ruang Eden tidak berarti bahwa tidak ada GC yang diminta kembali. Justru sebaliknya, Anda perlu memanggil konstruktor, memindai lebih cepat dengan GC dan menyelesaikan. Ini tidak hanya memuat CPU tetapi juga membatalkan cache sepanjang waktu. Tidak ada yang seperti ini yang diperlukan untuk variabel lokal. Mengapa Anda mengatakan bahwa ini "sama"?
Val
7
Jika Anda khawatir tentang kinerja objek yang berumur pendek seperti ini, Anda salah melakukannya. Kinerja harus menjadi hal TERAKHIR untuk dikhawatirkan ... keterbacaan yang pertama. Ini sebenarnya konstruksi yang cukup bagus.
Bill K
4
Saya akan mengatakan saya tidak benar-benar mengerti kebutuhan untuk berdebat ketika instance Indeks dapat dibuat sekali dan digunakan kembali / diperbarui, hanya untuk memperdebatkan argumen dan membuat semua orang senang. Namun, dalam pengukuran saya, versi yang membuat Indeks baru () setiap kali dilakukan lebih dari dua kali lebih cepat pada mesin saya, hampir sama dengan iterator asli yang menjalankan iterasi yang sama.
Eric Woodruff
47

Solusi termudah adalah dengan menjalankan counter Anda sendiri dengan demikian:

int i = 0;
for (String s : stringArray) {
    doSomethingWith(s, i);
    i++;
}

Alasan untuk ini adalah karena tidak ada jaminan aktual bahwa item dalam koleksi (yang varian itu forberulang) bahkan memiliki indeks, atau bahkan memiliki urutan yang ditentukan (beberapa koleksi dapat mengubah urutan ketika Anda menambah atau menghapus elemen).

Lihat misalnya, kode berikut:

import java.util.*;

public class TestApp {
  public static void AddAndDump(AbstractSet<String> set, String str) {
    System.out.println("Adding [" + str + "]");
    set.add(str);
    int i = 0;
    for(String s : set) {
        System.out.println("   " + i + ": " + s);
        i++;
    }
  }

  public static void main(String[] args) {
    AbstractSet<String> coll = new HashSet<String>();
    AddAndDump(coll, "Hello");
    AddAndDump(coll, "My");
    AddAndDump(coll, "Name");
    AddAndDump(coll, "Is");
    AddAndDump(coll, "Pax");
  }
}

Ketika Anda menjalankannya, Anda dapat melihat sesuatu seperti:

Adding [Hello]
   0: Hello
Adding [My]
   0: Hello
   1: My
Adding [Name]
   0: Hello
   1: My
   2: Name
Adding [Is]
   0: Hello
   1: Is
   2: My
   3: Name
Adding [Pax]
   0: Hello
   1: Pax
   2: Is
   3: My
   4: Name

menunjukkan bahwa, memang demikian, urutan tidak dianggap sebagai fitur yang menonjol dari suatu set.

Ada beberapa cara lain untuk melakukannya tanpa penghitung manual, tetapi ini adalah pekerjaan yang adil untuk keuntungan yang meragukan.

paxdiablo
sumber
2
Ini tampaknya masih menjadi solusi yang paling jelas, bahkan dengan antarmuka Daftar dan Iterable.
Joshua Pinter
27

Menggunakan lambdas dan antarmuka fungsional di Java 8 memungkinkan pembuatan abstraksi loop baru. Saya dapat mengulang koleksi dengan indeks dan ukuran koleksi:

List<String> strings = Arrays.asList("one", "two","three","four");
forEach(strings, (x, i, n) -> System.out.println("" + (i+1) + "/"+n+": " + x));

Output yang mana:

1/4: one
2/4: two
3/4: three
4/4: four

Yang saya terapkan sebagai:

   @FunctionalInterface
   public interface LoopWithIndexAndSizeConsumer<T> {
       void accept(T t, int i, int n);
   }
   public static <T> void forEach(Collection<T> collection,
                                  LoopWithIndexAndSizeConsumer<T> consumer) {
      int index = 0;
      for (T object : collection){
         consumer.accept(object, index++, collection.size());
      }
   }

Kemungkinannya tidak terbatas. Misalnya, saya membuat abstraksi yang menggunakan fungsi khusus hanya untuk elemen pertama:

forEachHeadTail(strings, 
                (head) -> System.out.print(head), 
                (tail) -> System.out.print(","+tail));

Yang mencetak daftar yang dipisahkan koma dengan benar:

one,two,three,four

Yang saya terapkan sebagai:

public static <T> void forEachHeadTail(Collection<T> collection, 
                                       Consumer<T> headFunc, 
                                       Consumer<T> tailFunc) {
   int index = 0;
   for (T object : collection){
      if (index++ == 0){
         headFunc.accept(object);
      }
      else{
         tailFunc.accept(object);
      }
   }
}

Perpustakaan akan mulai muncul untuk melakukan hal-hal semacam ini, atau Anda dapat menjalankannya sendiri.

James Scriven
sumber
3
Bukan kritik terhadap postingan (itu "cara paling keren untuk melakukannya" hari ini saya rasa), tapi saya berjuang untuk melihat bagaimana ini lebih mudah / lebih baik daripada sekolah tua sederhana untuk loop: for (int i = 0; i < list.size (); i ++) {} Bahkan nenek bisa mengerti ini dan mungkin lebih mudah untuk mengetik tanpa sintaks dan karakter yang tidak biasa yang digunakan lambda. Jangan salah paham, saya suka menggunakan lambdas untuk hal-hal tertentu (pola panggilan balik / event handler) tapi saya tidak bisa mengerti penggunaannya dalam kasus seperti ini. Alat yang hebat, tetapi menggunakannya setiap saat , saya tidak bisa melakukannya.
Manius
Saya kira, beberapa indeks off-by-one over / underflow terhadap daftar itu sendiri. Tetapi ada opsi yang diposting di atas: int i = 0; untuk (String s: stringArray) {doSomethingWith (s, i); i ++; }
Manius
21

Java 8 memperkenalkan metode Iterable#forEach()/ Map#forEach(), yang lebih efisien untuk banyak Collection/ Mapimplementasi dibandingkan dengan "klasik" untuk-setiap loop. Namun, juga dalam hal ini indeks tidak disediakan. Kuncinya di sini adalah dengan menggunakan di AtomicIntegerluar ekspresi lambda. Catatan: variabel yang digunakan dalam ekspresi lambda harus final secara efektif, itu sebabnya kami tidak bisa menggunakan yang biasa int.

final AtomicInteger indexHolder = new AtomicInteger();
map.forEach((k, v) -> {
    final int index = indexHolder.getAndIncrement();
    // use the index
});
ruller
sumber
16

Saya khawatir ini tidak mungkin dengan foreach. Tetapi saya dapat menyarankan Anda untuk simpangan gaya lama yang simpel :

    List<String> l = new ArrayList<String>();

    l.add("a");
    l.add("b");
    l.add("c");
    l.add("d");

    // the array
    String[] array = new String[l.size()];

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        array[it.nextIndex()] = it.next();
    }

Perhatikan bahwa, antarmuka Daftar memberi Anda akses ke it.nextIndex().

(edit)

Untuk contoh Anda yang diubah:

    for(ListIterator<String> it =l.listIterator(); it.hasNext() ;)
    {
        int i = it.nextIndex();
        doSomethingWith(it.next(), i);
    }
bruno conde
sumber
9

Salah satu perubahan Sunyang dipertimbangkan Java7adalah untuk menyediakan akses ke Iteratorloop internal foreach. sintaksnya akan seperti ini (jika ini diterima):

for (String str : list : it) {
  if (str.length() > 100) {
    it.remove();
  }
}

Ini adalah gula sintaksis, tetapi tampaknya banyak permintaan dibuat untuk fitur ini. Tetapi sampai disetujui, Anda harus menghitung iterasi sendiri, atau menggunakan regular untuk loop dengan Iterator.

Yuval
sumber
7

Solusi Idiomatik:

final Set<Double> doubles; // boilerplate
final Iterator<Double> iterator = doubles.iterator();
for (int ordinal = 0; iterator.hasNext(); ordinal++)
{
    System.out.printf("%d:%f",ordinal,iterator.next());
    System.out.println();
}

ini sebenarnya solusi yang disarankan Google dalam diskusi Guava tentang mengapa mereka tidak menyediakan CountingIterator.

Masyarakat
sumber
4

Meskipun ada begitu banyak cara lain yang disebutkan untuk mencapai hal yang sama, saya akan membagikan cara saya untuk beberapa pengguna yang tidak puas. Saya menggunakan fitur Java 8 IntStream.

1. Array

Object[] obj = {1,2,3,4,5,6,7};
IntStream.range(0, obj.length).forEach(index-> {
    System.out.println("index: " + index);
    System.out.println("value: " + obj[index]);
});

2. Daftar

List<String> strings = new ArrayList<String>();
Collections.addAll(strings,"A","B","C","D");

IntStream.range(0, strings.size()).forEach(index-> {
    System.out.println("index: " + index);
    System.out.println("value: " + strings.get(index));
});
Amar Prakash Pandey
sumber
2

Jika Anda membutuhkan penghitung di setiap loop, Anda harus menghitung sendiri. Tidak ada built in counter sejauh yang saya tahu.

EricSchaefer
sumber
(Saya telah memperbaiki bug itu dalam pertanyaan.)
Tom Hawtin - tackline
1

Ada "varian" untuk menjawab pertanyaan ... ;-)

int i = -1;
for(String s : stringArray) {
    doSomethingWith(s, ++i);
}
Jorg
sumber
3
Penasaran, apakah ada manfaat menggunakan ini atas i=0; i++;pendekatan yang tampaknya lebih jelas ?
Joshua Pinter
1
Saya kira jika Anda perlu menggunakan lagi indeks i setelah doSomething di untuk lingkup.
gcedo
1

Untuk situasi di mana saya hanya perlu indeks sesekali, seperti dalam klausa menangkap, saya kadang-kadang akan menggunakan indexOf.

for(String s : stringArray) {
  try {
    doSomethingWith(s);
  } catch (Exception e) {
    LOGGER.warn("Had some kind of problem with string " +
      stringArray.indexOf(s) + ": " + s, e);
  }
}
Jeremy
sumber
2
Hati-hati karena ini membutuhkan .equals () - implementasi objek Array yang dapat mengidentifikasi objek tertentu secara unik. Ini bukan kasus untuk Strings, jika string tertentu berada di Array beberapa kali, Anda hanya akan mendapatkan indeks kejadian pertama, bahkan jika Anda sudah mengulangi item berikutnya dengan string yang sama.
Kosi2801
-1

Solusi terbaik dan dioptimalkan adalah melakukan hal berikut:

int i=0;

for(Type t: types) {
  ......
  i++;
}

Di mana Tipe dapat berupa tipe data apa pun dan tipe adalah variabel tempat Anda melamar.

Abhijeet Chopra
sumber
Harap berikan jawaban Anda dalam format kode yang dapat direproduksi.
Nareman Darwish
Inilah cara yang diinginkan untuk solusi alternatif. Pertanyaannya bukan tentang jenis tetapi tentang kemungkinan untuk mengakses penghitung lingkaran dengan cara yang BERBEDA daripada menghitung secara manual.
Kosi2801
-4

Saya sedikit terkejut tidak ada yang menyarankan yang berikut (saya akui itu pendekatan malas ...); Jika stringArray adalah Daftar sejenis, Anda bisa menggunakan sesuatu seperti stringArray.indexOf (S) untuk mengembalikan nilai untuk hitungan saat ini.

Catatan: ini mengasumsikan bahwa elemen-elemen Daftar adalah unik, atau bahwa itu tidak masalah jika mereka tidak unik (karena dalam kasus itu akan mengembalikan indeks dari salinan pertama yang ditemukan).

Ada situasi di mana itu sudah cukup ...

Rik
sumber
9
Dengan kinerja mendekati O (n²) untuk seluruh loop sangat sulit untuk membayangkan bahkan beberapa situasi di mana ini akan lebih unggul daripada yang lain. Maaf.
Kosi2801
-5

Ini adalah contoh bagaimana saya melakukan ini. Ini mendapatkan indeks pada untuk setiap loop. Semoga ini membantu.

public class CheckForEachLoop {

    public static void main(String[] args) {

        String[] months = new String[] { "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST",
                "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER" };
        for (String s : months) {
            if (s == months[2]) { // location where you can change
              doSomethingWith(s); // however many times s and months
                                  // doSomethingWith(s) will be completed and 
                                  // added together instead of counter
            }

        }
        System.out.println(s); 


    }
}
ashkanaral
sumber
1
Maaf, ini tidak menjawab pertanyaan. Ini bukan tentang mengakses konten tetapi tentang penghitung seberapa sering loop sudah diulang.
Kosi2801
Pertanyaannya adalah untuk mengetahui indeks saat ini dalam loop dalam untuk setiap gaya loop.
rumman0786