Javaのfor-eachループでイテレーションカウンターにアクセスする方法はありますか?


274

Javaのfor-eachループに方法はありますか

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

ループが既に処理されている頻度を調べるには?

古くて有名なfor(int i=0; i < boundary; i++)-ループを使用する以外に、構成は

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

for-eachループでそのようなカウンターを使用できる唯一の方法は?


2
もう1つの残念な点は、ループの外ではループ変数を使用できないことですType var = null; for (var : set) dosomething; if (var != null) then ...
Val

参照が有効な最終でない限り、@ Val。この機能の使用方法については私の回答を参照してください
rmuller

回答:


205

いいえ。ただし、独自のカウンターを提供できます。

この理由は、のために、各ループは内部でないということである持ってカウンターを。これはIterableインターフェースに基づいています。つまりIterator、「コレクション」をループするためにを使用します。これはまったくコレクションではない場合があり、実際には、インデックスに基づくものではない(リンクリストなど)場合があります。


5
Rubyにはこのための構造体があり、Javaもそれを取得する必要があります...for(int idx=0, String s; s : stringArray; ++idx) doSomethingWith(s, idx);
Nicholas DiPiazza 14

9
答えは「いいえ、あなた自身のカウンターを提供することができます」で始まるはずです。
user1094206 2015年

1
FYI @NicholasDiPiazza Rubyにはeach_with_indexループメソッドがありますEnumerable。apidock.com/ruby/Enumerable/each_with_index
saywhatnowを

@saywhatnowええ、それは申し訳ありませんでした
Nicholas DiPiazza

2
「この理由は、for-eachループには内部的にカウンターがないためです」。「Java開発者はプログラマーにforループ内の初期値でインデックス変数を指定することを気にかけなかった」のように読む必要がありますfor (int i = 0; String s: stringArray; ++i)
izogfif

64

別の方法があります。

独自のIndexクラスと、Iterableこのクラスのoverインスタンスを返す静的メソッドを作成すると、

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

の実装With.indexは次のようなものです

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
きちんとしたアイデア。Indexクラスの短命のオブジェクト作成がなければ、私は賛成票を投じていただろう。
mR_fr0g 2013年

3
@ mR_fr0gは心配しないで、私はこれをベンチマークし、これらすべてのオブジェクトを作成することは、反復ごとに同じオブジェクトを再利用するよりも遅くはありません。その理由は、これらのオブジェクトはすべてedenスペースにのみ割り当てられ、ヒープに到達するのに十分な期間は存続しないためです。したがって、それらの割り当ては、たとえばローカル変数の割り当てと同じくらい高速です。
akuhn 2013年

1
@akuhn待って。Edenスペースは、GCが再要求されないことを意味しません。まったく逆の場合は、コンストラクターを呼び出して、GCでより早くスキャンして終了する必要があります。これはCPUをロードするだけでなく、常にキャッシュを無効にします。ローカル変数では、このようなことは必要ありません。なぜこれが「同じ」だと言うのですか?
Val

7
このような短期間のオブジェクトのパフォーマンスが心配な場合は、間違っています。パフォーマンスは、最初に心配するべき最後のものです...読みやすさ。これは実際にはかなり良い構成です。
Bill K

4
Indexインスタンスを一度作成して再利用/更新できる場合、議論の余地をなくしてみんなを幸せにするために、議論する必要性について本当に理解していないと言いました。ただし、私の測定では、毎回新しいIndex()を作成するバージョンは、私のマシンで2倍以上高速で実行されました。これは、同じ反復を実行するネイティブイテレータとほぼ同じです。
エリックウッドラフ

47

最も簡単な解決策、次のように独自のカウンターを実行することです。

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

その理由は、コレクション内のアイテム(そのバリエーションのfor反復)インデックスを持っている、または定義済みの順序を持っているという保証は実際にはないためです(一部のコレクションは、要素を追加または削除すると順序が変わる場合があります)。

たとえば、次のコードを参照してください。

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

これを実行すると、次のようなものが表示されます。

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

当然のことながら、順序はセットの顕著な特徴とは見なされません。

手動カウンターなしでそれを行う他の方法がありますが、それは疑わしい利益のためのかなりの仕事です。


2
これは、ListおよびIterableインターフェースを使用しても、依然として最も明確なソリューションのようです。
ジョシュア・ピンター

27

Java 8ラムダ関数型インターフェースを使用すると、新しいループ抽象化を作成できます。インデックスとコレクションサイズでコレクションをループできます。

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

どの出力:

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

私は次のように実装しました:

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

可能性は無限大。たとえば、最初の要素だけに特別な関数を使用する抽象化を作成します。

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

これはカンマ区切りのリストを正しく出力します:

one,two,three,four

私は次のように実装しました:

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

ライブラリはこの種のことをするためにポップアップし始めます、あるいはあなたはあなた自身のものを転がすことができます。


3
投稿への批判ではありませんが(最近では「クールな方法」だと思います)、これがループの単純な古い学校よりも簡単/優れていることを確認するのに苦労しています:for(int i = 0; i < list.size(); i ++){}おばあちゃんでもこれを理解でき、ラムダが使用する構文や一般的でない文字を使わずに入力する方がおそらく簡単です。誤解しないでください。特定のもの(コールバック/イベントハンドラータイプのパターン)にラムダを使用するのは好きですが、このような場合の使用法を理解できません。すばらしいツールですが、いつもそれを使用していると、私にはできません。
Manius

私は、リスト自体に対して、1つずれたインデックスのオーバーフロー/アンダーフローを回避していると思います。しかし、上記のオプションがあります。inti = 0; for(String s:stringArray){doSomethingWith(s、i); i ++; }
マニウス2018

21

Java 8はIterable#forEach()/ Map#forEach()メソッドを導入しました。これは、「クラシック」なfor-eachループと比較して、多くのCollection/ Map実装でより効率的です。ただし、この場合もインデックスは提供されません。ここでの秘訣はAtomicInteger、ラムダ式の外で使用することです。注:ラムダ式内で使用される変数は事実上最終でなければならないため、通常のは使用できませんint

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

16

これはで不可能ですforeach。しかし、私はあなたに単純な古いスタイルのforループを提案することができます

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

Listインターフェースからにアクセスできることに注意してくださいit.nextIndex()

(編集)

変更した例:

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

9

foreachループの内部へのアクセスを提供することをSun検討している変更の1つです。構文は次のようになります(これが受け入れられる場合)。Java7Iterator

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

これは構文上の砂糖ですが、この機能に対して多くのリクエストがあったようです。ただし、承認されるまでは、自分で反復をカウントするか、で通常のforループを使用する必要がありIteratorます。


7

慣用的な解決策:

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

これは実際にグアバの議論グーバがなぜ彼らが提供しなかったのかについて提案する解決策ですCountingIterator


4

同じことを達成するために言及されている方法は他にもたくさんありますが、満足していないユーザーのために私の方法を共有します。Java 8 IntStream機能を使用しています。

1.配列

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.リスト

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

for-eachループでカウンターが必要な場合は、自分でカウントする必要があります。私の知る限り、内蔵カウンターはありません。


(質問のバグを修正しました。)
トム・ホーティン-09年

1

paxの答えには「バリアント」があります... ;-)

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

3
好奇心が強い、一見、より明確なi=0; i++;アプローチに対してこれを使用する利点はありますか?
ジョシュア・ピンター

1
forスコープでdoSomethingの後にiインデックスを再度使用する必要があるかどうかと思います。
gcedo 14

1

catch句のように、インデックスがたまにしか必要ない状況では、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);
  }
}

2
これには、特定のオブジェクトを一意に識別できるArraysオブジェクトの.equals()実装が必要であることに注意してください。これは文字列には当てはまりません。特定の文字列が配列に複数回ある場合、同じ文字列を使用して後の項目で既に反復していても、最初に出現したインデックスのみが取得されます。
Kosi2801 2017年

-1

最適で最適なソリューションは、次のことを行うことです。

int i=0;

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

Typeは任意のデータ型で、typesはループを適用する変数です。


回答を再現可能なコード形式で提供してください。
Nareman Darwish

これは、代替ソリューションが必要な方法とまったく同じです。問題は型についてではなく、手動でカウントするのとは異なる方法でループカウンターにアクセスする可能性についてです。
Kosi2801

-4

私は少し驚いて、誰も次のことを提案しませんでした(私はそれが怠惰なアプローチであることを認めます...); stringArrayが何らかのリストである場合、stringArray.indexOf(S)などを使用して、現在のカウントの値を返すことができます。

注:これは、リストの要素が一意であること、またはそれらが一意でなくても問題ないことを前提としています(その場合、最初に見つかったコピーのインデックスが返されるため)。

それで十分な状況があります...


9
ループ全体でパフォーマンスがO(n²)に近づいているため、これが他よりも優れている状況をいくつか想像することは非常に困難です。ごめんなさい。
Kosi2801 2011年

-5

これが私がこれをどのようにしたかの例です。これは、for eachループでインデックスを取得します。お役に立てれば。

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
これは質問に答えていません。それはコンテンツにアクセスすることではなく、ループが既に反復されている頻度に関するカウンターです。
Kosi2801

問題は、for-eachスタイルのループでループ内の現在のインデックスを見つけることでした。
rumman0786
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.