Как работает расширенный оператор for для массивов и как получить итератор для массива? - PullRequest
76 голосов
/ 12 октября 2010

Учитывая следующий фрагмент кода:

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

У меня есть следующие вопросы:

  1. Как работает вышеприведенный цикл для каждого цикла?
  2. Какполучить итератор для массива в Java?
  3. Преобразуется ли массив в список для получения итератора?

Ответы [ 13 ]

55 голосов
/ 12 октября 2010

Если вы хотите Iterator для массива, вы можете использовать одну из прямых реализаций вместо того, чтобы оборачивать массив в List.Например:

Коллекции Apache Commons ArrayIterator

Или этот, если вы хотите использовать дженерики:

com.Ostermiller.util.ArrayIterator

Обратите внимание, что если вы хотите иметь Iterator над примитивными типами, вы не можете этого сделать, потому что примитивный тип не может быть универсальным параметром.Например, если вы хотите Iterator<int>, вы должны вместо этого использовать Iterator<Integer>, что приведет к большому количеству автобоксов и -боксов, если оно поддерживается int[].

50 голосов
/ 12 октября 2010

Нет, конвертации нет.JVM просто перебирает массив, используя индекс в фоновом режиме.

Цитата из Effective Java 2nd Ed., Item 46:

Обратите внимание, что при использованиидля каждого цикла, даже для массивов.Фактически, он может предложить небольшое преимущество в производительности по сравнению с обычным циклом for в некоторых случаях, поскольку он вычисляет ограничение индекса массива только один раз.

Таким образом, вы не можете получить Iteratorдля массива (если, конечно, не преобразовав его сначала в List).

33 голосов
/ 12 октября 2010

Arrays.asList (arr) .iterator ();

Или напишите свой собственный, реализуя интерфейс ListIterator ..

31 голосов
/ 15 ноября 2011

Google Библиотека Guava * Коллекция 1002 * s обеспечивает такую ​​функцию:

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

Следует отдавать предпочтение гуаве по сравнению с коллекцией Apache (которая, похоже, заброшена).

14 голосов
/ 07 марта 2015

В Java 8:

Arrays.stream(arr).iterator();
10 голосов
/ 09 июля 2012
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();
  }
}
9 голосов
/ 12 октября 2010

Строго говоря, вы не можете получить итератор массива примитивов, потому что Iterator.next () может возвращать только объект. Но с помощью магии автобокса вы можете получить итератор, используя метод Arrays.asList () .

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

Приведенный выше ответ неверен, вы не можете использовать Arrays.asList() для примитивного массива, он вернет List<int[]>. Используйте взамен Гуава Ints.asList().

5 голосов
/ 12 октября 2010

Вы не можете напрямую получить итератор для массива.

Но вы можете использовать Список, поддерживаемый вашим массивом, и получить итератор в этом списке. Для этого ваш массив должен быть массивом Integer (вместо массива int):

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

Примечание: это всего лишь теория. Вы можете получить такой итератор, но я не рекомендую вам это делать. Производительность не очень хорошая по сравнению с прямой итерацией в массиве с «расширенным синтаксисом».

Примечание 2: конструкция списка с этим методом поддерживает не все методы (поскольку список поддерживается массивом, имеющим фиксированный размер). Например, метод «remove» вашего итератора приведет к исключению.

4 голосов

Как работает вышеприведенный цикл for-each?

Как и многие другие функции массива, JSL явно упоминает массивы и наделяет их магическими свойствами. JLS 7 14.14.2 :

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

Если тип выражения является подтипом Iterable, то перевод будет следующим:

[...]

В противном случае выражение обязательно имеет тип массива, T[]. [[МАГИЯ! ]]

Пусть L1 ... Lm будет (возможно, пустой) последовательностью меток, непосредственно предшествующих расширенному оператору 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 * ​​1055 * сохраните целое число 0 из стека в локальную позицию 4. Это переменная цикла.
  • 24 до 47: цикл. Переменная цикла извлекается в 31 и увеличивается на 44. Когда он равен длине массива, который хранится в локальной переменной 3 при проверке в 27, цикл заканчивается.

Заключение : это то же самое, что делать явный цикл for с индексной переменной, без участия итераторов.

3 голосов
/ 15 марта 2016

Я немного опоздал с игрой, но заметил некоторые ключевые моменты, которые не были учтены, особенно в отношении Java 8 и эффективности Arrays.asList.

1.Как работает цикл for-each?

Как указал Ciro Santilli 六四 事件 there's 包 卓 轩 , есть удобная утилита для проверки байт-кода, поставляемая с JDK: javap,Используя это, мы можем определить, что следующие два фрагмента кода выдают идентичный байт-код для 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() до тех пор, пока не будет достигнут * 1031, так же, как это делает руководство для цикла.Это самая простая и обычно наиболее эффективная реализация Iterator для массива.

Опять же, Arrays.asList не создает java.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();

Streams 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 имеет три встроенных интерфейса, которые расширяют его: OfInt, OfLong и OfDouble.Все три будут отображаться, если вызывается next(), но также могут возвращать примитивы с помощью таких методов, как 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.

...