Java'nın her döngü için bir yineleme sayacına erişmenin bir yolu var mı?


274

Java'nın her döngü için bir yolu var mı

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

döngünün ne sıklıkta işlendiğini öğrenmek için?

Eski ve iyi bilinen for(int i=0; i < boundary; i++)döngüyü kullanmanın yanı sıra , yapı

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

her bir döngü için böyle bir sayaç kullanılabilir olmanın tek yolu nedir?


2
Başka bir yazık, döngü değişkenini döngü dışında kullanamazsınızType var = null; for (var : set) dosomething; if (var != null) then ...
Val

@Val referans etkili olmadığı sürece. Bu özelliğin nasıl kullanılacağına dair cevabımı görün
rmuller

Yanıtlar:


205

Hayır, ancak kendi sayacınızı sağlayabilirsiniz.

Bunun nedeni için-her döngü içten olmamasıdır sahip bir sayaç; Yinelenebilir arabirime dayanır , yani Iterator"koleksiyon" arasında bir döngü kullanır - bu hiç bir koleksiyon olmayabilir ve aslında dizinlere (bağlantılı bir liste gibi) dayanmayan bir şey olabilir.


5
Ruby bunun için bir yapıya sahip ve Java da onu almalı ...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza

9
Cevap "Hayır, ancak kendi sayacınızı sağlayabilirsiniz" ile başlamalıdır.
user1094206

1
FYI @NicholasDiPiazza Ruby each_with_indexiçin bir döngü yöntemi vardır Enumerable. Bkz. Apidock.com/ruby/Enumerable/each_with_index
saywhatnow

@ saywhatnow evet bu ne demek istediğim üzgünüm - önceki yorum yaptığımda ne sigara emin değilim
Nicholas DiPiazza

2
"Bunun nedeni, for-each döngüsünün dahili olarak bir sayacı olmamasıdır". "Java geliştiricileri programcı fordöngü içinde başlangıç ​​değeri ile dizin değişkeni belirtmek için umursamadı" olarak okunmalıdır , gibifor (int i = 0; String s: stringArray; ++i)
izogfif

64

Başka bir yol var.

Kendi Indexsınıfınızı ve Iterablebu sınıfın fazla örneğini döndüren statik bir yöntemi yazdığınız göz önüne alındığında ,

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

Nerede uygulanması With.indexgibi bir şey

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++); }
                    ...
                }
            }
        }
    }
}

7
Düzgün bir fikir. Index sınıfının kısa ömürlü nesne yaratması olmasaydı iptal olurdu.
mR_fr0g

3
@ mR_fr0g endişelenmeyin, bunu karşılaştırdım ve tüm bu nesneleri oluşturmak her yineleme için aynı nesneyi tekrar kullanmaktan daha yavaş değil. Bunun nedeni, tüm bu nesnelerin sadece eden alanına tahsis edilmiş olması ve asla yığınlara ulaşacak kadar uzun ömürlü olmamasıdır. Yani onları ayırmak, örneğin yerel değişkenleri ayırmak kadar hızlıdır.
akuhn

1
@akuhn Bekle. Eden alanı, hiçbir GC'nin gerekli olmadığı anlamına gelmez. Tam tersine, yapıcıyı çağırmanız, GC ile daha erken taramanız ve sonlandırmanız gerekir. Bu sadece CPU'yu yüklemekle kalmaz, aynı zamanda önbelleği de geçersiz kılar. Yerel değişkenler için böyle bir şey gerekli değildir. Neden bunun "aynı" olduğunu söylüyorsun?
Val

7
Bunun gibi kısa ömürlü nesnelerin performansından endişe ediyorsanız, yanlış yapıyorsunuz. Performans endişelenecek en son şey olmalı ... birincisi okunabilirlik. Bu aslında oldukça iyi bir yapı.
Bill K

4
Dizin örneğinin bir kez oluşturulabileceği ve yeniden kullanılabilir / güncellenebileceği zaman tartışmayı gerçekten anlamadığımı söyleyecektim, sadece tartışmayı tartışmak ve herkesi mutlu etmek için. Ancak, ölçümlerimde, her seferinde yeni bir Index () oluşturan sürüm, makinemde aynı yinelemeleri çalıştıran yerel bir yineleyiciye eşit iki kat daha hızlı gerçekleşti.
Eric Woodruff

47

Kolay çözüm olduğunu sadece bu nedenle kendi sayacı çalıştırmak için:

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

Bunun nedeni, bir koleksiyondaki öğelerin (bu varyantın for dolaşır üzeri) bile var hatta bir dizin veya sahip belirli bir düzen (eklemek veya kaldırma elemanları bazı koleksiyonları sırasını değişebilir).

Örneğin, aşağıdaki koda bakın:

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");
  }
}

Bunu çalıştırdığınızda, şöyle bir şey görebilirsiniz:

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

bu, haklı olarak, düzenin bir kümenin göze çarpan bir özelliği olarak görülmediğini gösterir.

Manuel bir sayaç olmadan yapmanın başka yolları da var, ancak şüpheli fayda için adil bir iş.


2
Bu hala Liste ve Yinelenebilir arabirimler için bile en açık çözüm gibi görünüyor.
Joshua Pinter

27

Kullanılması lambdas ve fonksiyonel arayüzler de Java 8 olası yeni döngü soyutlamalar oluşturarak yapar. Ben dizin ve koleksiyon boyutu ile bir koleksiyon üzerinde döngü olabilir:

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

Hangi çıktılar:

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

Hangi olarak uyguladı:

   @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());
      }
   }

İmkanlar sonsuzdur. Örneğin, yalnızca ilk öğe için özel bir işlev kullanan bir soyutlama oluşturuyorum:

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

Hangi virgülle ayrılmış listeyi doğru yazdırır:

one,two,three,four

Hangi olarak uyguladı:

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);
      }
   }
}

Kütüphaneler bu tür şeyleri yapmak için açılmaya başlayacak ya da kendinizinkini oluşturabilirsiniz.


3
Gönderinin bir eleştirisi değil (sanırım bu günlerde "bunu yapmanın havalı yolu"), ama bunun basit bir eski okul okulundan daha kolay / daha iyi olduğunu görmek için mücadele ediyorum: for (int i = 0; i < list.size (); i ++) {} Büyükanne bile bunu anlayabilirdi ve muhtemelen bir lambda'nın sözdizimi ve yaygın olmayan karakterler olmadan yazmak daha kolaydır. Beni yanlış anlamayın, lambdas'ı belirli şeyler için kullanmayı seviyorum (geri arama / olay işleyicisi desen tipi), ancak böyle durumlarda kullanımı anlayamıyorum. Harika bir araç, ama her zaman kullanarak , yapamam.
Manius

Sanırım, listenin kendisine karşı tek tek indeks aşırı / düşük taşmalarından kaçınma. Ancak yukarıda yayınlanan seçenek vardır: int i = 0; for (String s: stringArray) {doSomethingWith (s, i); i ++; }
Manius

21

Java 8 , her bir döngü için "klasik" ile karşılaştırıldığında birçok / uygulama için daha verimli olan Iterable#forEach()/ Map#forEach()yöntemini tanıttı . Ancak, bu durumda da bir dizin sağlanmaz. Buradaki hile lambda ifadesinin dışında kullanmaktır . Not: lambda ifadesinde kullanılan değişkenler etkili bir şekilde nihai olmalıdır, bu yüzden sıradan bir şey kullanamayız .CollectionMapAtomicIntegerint

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

16

Korkarım ki bu mümkün değil foreach. Ama size eski tarz basit bir döngüler önerebilirim :

    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();
    }

Liste arabiriminin size erişmesine izin verdiğine dikkat edin it.nextIndex().

(Düzenle)

Değişen örneğinize:

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

9

Değişikliklerden biri Suniçin düşünmektedir Java7iç erişim sağlamak için bir Iteratorforeach döngülerinde. sözdizimi böyle bir şey olacaktır (bu kabul edilirse):

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

Bu sözdizimsel şekerdir, ancak görünüşe göre bu özellik için çok fazla istek yapıldı. Ancak onaylanana kadar, yinelemeleri kendiniz saymanız veya bir ile normal bir döngü kullanmanız gerekir Iterator.


7

Deyimsel Çözüm:

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();
}

bu aslında Google'ın neden a sağlamadıklarına ilişkin Guava tartışmasında önerdiği çözümdür CountingIterator.


4

Aynı şeyi başarmak için pek çok başka yol olsa da, bazı tatminsiz kullanıcılar için yolumu paylaşacağım. Java 8 IntStream özelliğini kullanıyorum.

1. Diziler

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. Liste

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));
});

2

Her döngü için bir sayaca ihtiyacınız varsa, kendinizi saymanız gerekir. Bildiğim kadarıyla yerleşik bir sayaç yok.



1

Pax 'cevabının bir "varyantı" var ... ;-)

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

3
Merakla, bunu daha açık bir i=0; i++;yaklaşımla kullanmanın herhangi bir faydası var mı?
Joshua Pinter

1
Eğer kapsam için doSomething sonra tekrar i endeksi kullanmanız gerekiyorsa sanırım.
gcedo

1

Bir catch yan tümcesinde olduğu gibi, yalnızca endekse ihtiyaç duyduğum durumlar için bazen indexOf kullanacağım.

for(String s : stringArray) {
  try {
    doSomethingWith(s);
  } catch (Exception e) {
    LOGGER.warn("Had some kind of problem with string " +
      stringArray.indexOf(s) + ": " + s, e);
  }
}

2
Bunun, belirli bir nesneyi benzersiz şekilde tanımlayabilen Arrays nesnelerinin bir .equals () - uygulamasını gerektirdiğine dikkat edin. Bu, Dizeler için geçerli değildir , belirli bir dize Dizi'de birden çok kez bulunuyorsa, aynı dizeye sahip daha sonraki bir öğeyi zaten yineliyor olsanız bile, yalnızca ilk oluşumun dizinini alırsınız.
Kosi2801

-1

En iyi ve optimize edilmiş çözüm aşağıdakileri yapmaktır:

int i=0;

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

Burada Tür herhangi bir veri türü ve türü olabilir, bir döngü için uyguladığınız değişkendir.


Lütfen yanıtınızı tekrarlanabilir bir kod biçiminde sağlayın.
Nareman Darwish

Bu tam olarak alternatif bir çözümün istendiği yoldur. Soru, türlerle ilgili değil, döngü sayacına manuel olarak saymaktan farklı bir şekilde farklı olasılıklara erişme olasılıkları hakkındadır.
Kosi2801

-4

Biraz şaşırdım, kimse aşağıdakileri önermedi (tembel bir yaklaşım olduğunu itiraf ediyorum ...); StringArray bir tür Liste ise, geçerli sayım için bir değer döndürmek için stringArray.indexOf (S) gibi bir şey kullanabilirsiniz.

Not: Bu, Liste öğelerinin benzersiz olduğunu veya benzersiz olmamalarının önemli olmadığını varsayar (çünkü bu durumda bulunan ilk kopyanın dizinini döndürür).

Bunun yeterli olacağı durumlar var ...


9
Tüm döngü için O'ya (n²) yaklaşan bir performansla, bunun başka herhangi bir şeyden daha üstün olacağı birkaç durumu hayal etmek çok zordur. Afedersiniz.
Kosi2801

-5

İşte bunu nasıl yaptığımın bir örneği. Bu, her döngü için dizini alır. Bu yardımcı olur umarım.

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); 


    }
}

1
Üzgünüz, bu soruya cevap vermiyor. İçeriğe erişmekle değil , döngünün ne sıklıkta yinelendiğiyle ilgili bir sayaçla ilgilidir.
Kosi2801

Soru, her bir stil döngüsünde döngüdeki geçerli dizini bulmaktı.
rumman0786
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.