web-dev-qa-db-ja.com

拡張forステートメントは配列に対してどのように機能し、配列のイテレーターを取得する方法は?

次のコードスニペットがあるとします。

int[] arr = {1, 2, 3};
for (int i : arr)
    System.out.println(i);

次の質問があります。

  1. 上記のfor-eachループはどのように機能しますか?
  2. Javaで配列のイテレータを取得するにはどうすればよいですか?
  3. 反復子を取得するために配列がリストに変換されていますか?
76
Emil

配列にIteratorが必要な場合は、Listで配列をラップする代わりに、直接実装のいずれかを使用できます。例えば:

Apache Commons Collections ArrayIterator

または、ジェネリックを使用する場合は、次のようにします:

com.Ostermiller.util.ArrayIterator

プリミティブ型を汎用パラメーターにできないため、Iteratorをプリミティブ型にしたい場合はできないことに注意してください。たとえば、Iterator<int>が必要な場合は、代わりにIterator<Integer>を使用する必要があります。これにより、int[]によってサポートされている場合、多くのオートボクシングと-unboxingが行われます。

55
uckelman

いいえ、変換はありません。 JVMは、バックグラウンドでインデックスを使用して配列を反復処理します。

Effective Java 2nd Ed。、Item 46からの引用:

For-eachループを使用しても、配列であってもパフォーマンスが低下することはありません。実際、状況によっては、配列インデックスの制限を1回しか計算しないため、通常のforループよりもわずかにパフォーマンスが向上する場合があります。

したがって、配列のIteratorを取得することはできません(もちろん、最初にListに変換しない限り)。

50
Péter Török

Arrays.asList(arr).iterator();

または、独自のListIteratorインターフェースを実装します。

33
Jochem

Google Guava Librarie sコレクションはそのような機能を提供します:

Iterator<String> it = Iterators.forArray(array);

Apache Collection(放棄されたようです)よりもGuavaを好むべきです。

31
30thh

Java 8の場合:

Arrays.stream(arr).iterator();
14
Haneu
public class ArrayIterator<T> implements Iterator<T> {
  private T array[];
  private int pos = 0;

  public ArrayIterator(T anArray[]) {
    array = anArray;
  }

  public boolean hasNext() {
    return pos < array.length;
  }

  public T next() throws NoSuchElementException {
    if (hasNext())
      return array[pos++];
    else
      throw new NoSuchElementException();
  }

  public void remove() {
    throw new UnsupportedOperationException();
  }
}
10
Ashok Domadiya

Iterator.next() はObjectのみを返すことができるため、厳密に言えば、プリミティブ配列の反復子を取得することはできません。しかし、オートボクシングの魔法により、 Arrays.asList() メソッドを使用してイテレータを取得できます。

Iterator<Integer> it = Arrays.asList(arr).iterator();

上記の答えは間違っています。プリミティブ配列でArrays.asList()を使用することはできません。List<int[]>を返します。代わりに GuavaInts.asList()を使用してください。

9

配列の反復子を直接取得することはできません。

ただし、配列を基にしたリストを使用して、このリストのイテレーターを取得できます。そのためには、配列は整数配列(int配列ではなく)でなければなりません:

Integer[] arr={1,2,3};
List<Integer> arrAsList = Arrays.asList(arr);
Iterator<Integer> iter = arrAsList.iterator();

注:それは理論にすぎません。このようなイテレータを入手できますが、そうすることはお勧めしません。 「extended for syntax」を使用した配列の直接反復と比較すると、パフォーマンスは良くありません。

注2:このメソッドを使用したリストコンストラクトは、すべてのメソッドをサポートしているわけではありません(リストは固定サイズの配列によってサポートされているため)。たとえば、イテレータの「remove」メソッドは例外になります。

5
Benoit Courtine

上記のfor-eachループはどのように機能しますか?

他の多くの配列機能と同様に、JSLは配列を明示的に言及し、それらに魔法のプロパティを与えます。 JLS 7 14.14.2

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

ExpressionのタイプがIterableのサブタイプである場合、変換は次のようになります

[...]

それ以外の場合、Expressionには必ずT[]という配列型が必要です。 [[ 魔法! ]]

L1 ... Lmを、拡張forステートメントの直前の(おそらく空の)ラベルのシーケンスとします。

拡張forステートメントは、次の形式の基本forステートメントと同等です。

T[] #a = Expression;
L1: L2: ... Lm:
for (int #i = 0; #i < #a.length; #i++) {
    VariableModifiersopt TargetType Identifier = #a[#i];
    Statement
}

#aおよび#iは、自動生成された識別子であり、拡張forステートメントが発生する時点で有効範囲内にある他の識別子(自動生成またはその他)とは異なります。

反復子を取得するために配列がリストに変換されていますか?

javapにしましょう:

public class ArrayForLoop {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        for (int i : arr)
            System.out.println(i);
    }
}

その後:

javac ArrayForLoop.Java
javap -v ArrayForLoop

mainメソッドを少し編集して読みやすくします:

 0: iconst_3
 1: newarray       int
 3: dup
 4: iconst_0
 5: iconst_1
 6: iastore
 7: dup
 8: iconst_1
 9: iconst_2
10: iastore
11: dup
12: iconst_2
13: iconst_3
14: iastore

15: astore_1
16: aload_1
17: astore_2
18: aload_2
19: arraylength
20: istore_3
21: iconst_0
22: istore        4

24: iload         4
26: iload_3
27: if_icmpge     50
30: aload_2
31: iload         4
33: iaload
34: istore        5
36: getstatic     #2    // Field Java/lang/System.out:Ljava/io/PrintStream;
39: iload         5
41: invokevirtual #3    // Method Java/io/PrintStream.println:(I)V
44: iinc          4, 1
47: goto          24
50: return

壊す:

  • 0から14:配列の作成
  • 15から22:forループの準備。 22で、整数0をスタックからローカル位置4に保存します。これがループ変数です。
  • 24から47:ループ。ループ変数は31で取得され、44でインクリメントされます。 27のチェックでローカル変数3に格納されている配列の長さと等しい場合、ループは終了します。

結論:インデックス変数を使用して明示的なforループを実行するのと同じで、イテレータは含まれません。

(2)の場合、Guavaは Int.asList() として必要なものを提供します。関連付けられたクラスの各プリミティブタイプに相当するものがあります(例:Booleans for booleanなど).

    int[] arr={1,2,3};
    for(Integer i : Ints.asList(arr)) {
      System.out.println(i);
    }
3
BeeOnRope

私はゲームに少し遅れましたが、特にJava 8とArrays.asListの効率性に関して、いくつかの重要なポイントが残されていることに気付きました。

1. for-eachループはどのように機能しますか?

Ciro Santilli六四事件法轮功包卓轩 が指摘したように、JDKに同梱されているバイトコードを調べるための便利なユーティリティjavapがあります。これを使用して、次の2つのコードスニペットがJava 8u74の時点で同一のバイトコードを生成すると判断できます。

For-eachループ:

int[] arr = {1, 2, 3};
for (int n : arr) {
    System.out.println(n);
}

Forループ:

int[] arr = {1, 2, 3};

{  // These extra braces are to limit scope; they do not affect the bytecode
    int[] iter = arr;
    int length = iter.length;
    for (int i = 0; i < length; i++) {
        int n = iter[i];
        System.out.println(n);
    }
}

2. Javaで配列のイテレータを取得するにはどうすればよいですか?

これはプリミティブでは機能しませんが、Arrays.asListを使用して配列をリストに変換しても、パフォーマンスに大きな影響はありません。メモリとパフォーマンスの両方への影響はほとんど計り知れません。

Arrays.asListは、クラスとして簡単にアクセスできる通常のList実装を使用しません。 Java.util.Arrays.ArrayListを使用しますが、これはJava.util.ArrayListとは異なります。これは配列を囲む非常に薄いラッパーであり、サイズを変更できません。 Java.util.Arrays.ArrayListのソースコードを見ると、機能的に配列と同等になるように設計されていることがわかります。オーバーヘッドはほとんどありません。最も関連性のあるコード以外はすべて省略し、独自のコメントを追加していることに注意してください。

public class Arrays {
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, Java.io.Serializable {
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }
    }
}

イテレータはJava.util.AbstractList.Itrにあります。イテレータに関する限り、それは非常に単純です。 get()に到達するまでsize()を呼び出すだけです。マニュアルforループの場合と同様です。これは、配列のIteratorの最も単純で通常最も効率的な実装です。

繰り返しますが、Arrays.asListJava.util.ArrayListを作成しません。はるかに軽量で、オーバーヘッドが無視できるイテレータを取得するのに適しています。

プリミティブ配列

他の人が指摘したように、Arrays.asListはプリミティブ配列では使用できません。 Java 8には、データのコレクションを処理するためのいくつかの新しいテクノロジーが導入されており、そのいくつかは、配列から単純で比較的効率的なイテレーターを抽出するために使用できます。ジェネリックを使用する場合は、常にボクシングとアンボクシングの問題が発生することに注意してください。intからIntegerに変換してから、intに戻す必要があります。通常、ボクシング/アンボクシングは無視できますが、この場合はO(1)のパフォーマンスへの影響があり、非常に大きなアレイまたは非常に限られたリソースのコンピューターで問題が発生する可能性があります(つまり SoC )。

Java 8の配列キャスト/ボクシング操作のあらゆる種類の私の個人的なお気に入りは、新しいストリームAPIです。例えば:

int[] arr = {1, 2, 3};
Iterator<Integer> iterator = Arrays.stream(arr).mapToObj(Integer::valueOf).iterator();

ストリームAPIは、そもそもボクシングの問題を回避するための構成も提供しますが、これにはストリームを優先してイテレーターを放棄する必要があります。 int、long、およびdouble(それぞれIntStream、LongStream、およびDoubleStream)専用のストリームタイプがあります。

int[] arr = {1, 2, 3};
IntStream stream = Arrays.stream(arr);
stream.forEach(System.out::println);

興味深いことに、Java 8はJava.util.PrimitiveIteratorも追加します。これにより、両方の長所が提供されます。ボクシングを回避するための方法とともに、ボクシングによるIterator<T>との互換性。 PrimitiveIteratorには、それを拡張する3つの組み込みインターフェース、OfInt、OfLong、およびOfDoubleがあります。 next()が呼び出された場合、3つすべてがボックスになりますが、nextInt()などのメソッドを介してプリミティブを返すこともできます。 Java 8用に設計された新しいコードは、ボクシングが絶対に必要でない限り、next()の使用を避ける必要があります。

int[] arr = {1, 2, 3};
PrimitiveIterator.OfInt iterator = Arrays.stream(arr);

// You can use it as an Iterator<Integer> without casting:
Iterator<Integer> example = iterator;

// You can obtain primitives while iterating without ever boxing/unboxing:
while (iterator.hasNext()) {
    // Would result in boxing + unboxing:
    //int n = iterator.next();

    // No boxing/unboxing:
    int n = iterator.nextInt();

    System.out.println(n);
}

まだJava 8を使用していない場合、悲しいことにあなたの最も単純なオプションははるかに簡潔ではなく、ほぼ確実にボクシングを伴うでしょう:

final int[] arr = {1, 2, 3};
Iterator<Integer> iterator = new Iterator<Integer>() {
    int i = 0;

    @Override
    public boolean hasNext() {
        return i < arr.length;
    }

    @Override
    public Integer next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        return arr[i++];
    }
};

または、より再利用可能なものを作成する場合:

public final class IntIterator implements Iterator<Integer> {
    private final int[] arr;
    private int i = 0;

    public IntIterator(int[] arr) {
        this.arr = arr;
    }

    @Override
    public boolean hasNext() {
        return i < arr.length;
    }

    @Override
    public Integer next() {
        if (!hasNext()) {
            throw new NoSuchElementException();
        }

        return arr[i++];
    }
}

プリミティブを取得する独自のメソッドを追加することで、ここでボクシングの問題を回避できますが、独自の内部コードでのみ機能します。

3.反復子を取得するために配列はリストに変換されますか?

いいえそうではありません。ただし、Arrays.asListなどの軽量のものを使用する場合、リストにラップすることでパフォーマンスが低下するわけではありません。

3
Zenexer

私は最近の学生ですが、int []を使用した元の例はプリミティブ配列を反復していますが、Iteratorオブジェクトは使用していません。内容が異なる同じ(類似の)構文を持っているだけで、

for (primitive_type : array) { }

for (object_type : iterableObject) { }

Arrays.asList()は、指定されたオブジェクト配列にListメソッドを適用しますが、プリミティブ配列を含む他の種類のオブジェクトの場合、iterator()。next()は、元のオブジェクトへの参照を処理します。 1つの要素を持つリストとして。これのソースコードを見ることができますか?例外を好みませんか?気にしないで。私は推測します(それはGUESSです)それは、シングルトンコレクションに似ている(またはそうである)と思います。そのため、ここではasList()はプリミティブ配列の場合とは無関係ですが、混乱を招きます。私は自分が正しいとは知りませんが、私がそうだと言うプログラムを書きました。

したがって、この例(基本的にasList()はあなたが思っていることをしないので、実際にこのように使用するものではありません)-コードがコードとしてのマーキングよりもうまく機能することを願っていますちょっと、最後の行を見てください:

// Java(TM) SE Runtime Environment (build 1.6.0_19-b04)

import Java.util.*;

public class Page0434Ex00Ver07 {
public static void main(String[] args) {
    int[] ii = new int[4];
    ii[0] = 2;
    ii[1] = 3;
    ii[2] = 5;
    ii[3] = 7;

    Arrays.asList(ii);

    Iterator ai = Arrays.asList(ii).iterator();

    int[] i2 = (int[]) ai.next();

    for (int i : i2) {
        System.out.println(i);
    }

    System.out.println(Arrays.asList(12345678).iterator().next());
}
}
2
Robert Carnegie

グアバのIteratorsを使用して、30日からの回答が好きです。ただし、一部のフレームワークでは、空の配列ではなくnullを取得し、Iterators.forArray(array)はそれをうまく処理しません。そこで、このヘルパーメソッドを思い付きました。Iterator<String> it = emptyIfNull(array);で呼び出すことができます

public static <F> UnmodifiableIterator<F> emptyIfNull(F[] array) {
    if (array != null) {
        return Iterators.forArray(array);
    }
    return new UnmodifiableIterator<F>() {
        public boolean hasNext() {
            return false;
        }

        public F next() {
            return null;
        }
    };
}
1
Max Hohenegger