Как работает оператор enhanced for для массивов и как получить итератор для массива?



дан следующий фрагмент кода:



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


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




  1. как работает выше для каждого цикла?

  2. как получить итератор для массива в Java?

  3. массив преобразуется в список, чтобы получить итератор?

654   13  

13 ответов:

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

Apache Commons Collections ArrayIterator

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

com.Ostermiller.util.ArrayIterator

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

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

цитата из эффективного Java 2-е изд. Пункт 46:

обратите внимание, что нет никакого штрафа за использование цикл for-each, даже для массивов. На самом деле, он может предложить небольшое преимущество в производительности над обычным циклом for В некоторых обстоятельствах, поскольку он вычисляет предел индекс массива только один раз.

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

массивы.asList (arr).iterator ();

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

Google Гуава Библиотекаколлекция s предоставляет такую функцию:

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

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

В Java 8:

Arrays.stream(arr).iterator();
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();
  }
}

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

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

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

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

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

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

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

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

как работает выше для каждого цикла?

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

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

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

[...]

в противном случае выражение обязательно имеет тип массива, 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 автоматически генерируемые идентификаторы, которые отличаются от любых других идентификаторов (автоматически генерируемых или иным образом), которые находятся в области действия в точке, где оператор enhanced 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. Когда он равен длине массива, который хранится в локальной переменной 3 при проверке на 27, то цикл заканчивается.

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

для (2), гуава обеспечивает именно то, что вы хотите, как Int.asList(). Существует эквивалент для каждого примитивного типа в связанном классе, например,Booleans на boolean и т. д.

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

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

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

как Ciro Santilli件件 法轮功 包卓轩 указал, что есть удобная утилита для изучения байт-кода, который поставляется с JDK:javap. Используя это, мы можем определить, что следующие два фрагмента кода производят идентичный байт-код на Java 8u74:

для каждого цикла:

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 не использует обычную реализацию списка, которая легко работает как класс. Он использует 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() достигается, так же, как руководство для цикла будет делать. Это самая простая и, как правило, наиболее эффективная реализация Iterator для массива.

опять Arrays.asList не создает java.util.ArrayList. Это гораздо более легкий и подходит для получения итератора с незначительными накладными расходами.

примитивные массивы

как уже отмечалось, Arrays.asList не может использоваться на примитивных массивах. Java 8 представляет несколько новых технологий для работа с коллекциями данных, некоторые из которых могут быть использованы для извлечения простых и относительно эффективных итераторов из массивов. Обратите внимание, что если вы используете дженерики, у вас всегда будет проблема бокса-распаковки: вам нужно будет преобразовать из int в Integer, а затем обратно в int. Хотя бокс / распаковка обычно незначительна, в этом случае она оказывает влияние на производительность O(1) и может привести к проблемам с очень большими массивами или на компьютерах с очень ограниченными ресурсами (т. е., SoC).

мой личный фаворит для любого вида операции литья массива / бокса в Java 8-это новый stream 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 имеет три встроенных интерфейса, которые расширяют его: 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.

Я недавний студент, но я считаю, что исходный пример с int[] повторяет массив примитивов, но не с помощью объекта итератора. Он просто имеет тот же (подобный) синтаксис с различным содержанием,

for (primitive_type : array) { }

for (object_type : iterableObject) { }

массивы.asList () по-видимому, просто применяет методы списка к массиву объектов, который он задан, но для любого другого типа объекта, включая примитивный массив, iterator().далее () видимо просто передает вам ссылку на исходный объект, рассматривая его как список с одним элементом. Можем ли мы увидеть исходный код для этого? Разве вы не предпочли бы исключение? Не берите в голову. Я предполагаю (это предположение), что это похоже (или это) на Одноэлементную коллекцию. Поэтому здесь asList () не имеет отношения к случаю с массивом примитивов, но запутывает. Я не знаю, что я прав, но я написал программу, которая говорит, что я прав.

таким образом, этот пример (где в основном asList() не делает то, что вы думали, и поэтому это не то, что вы на самом деле используете таким образом) - I надеюсь, что код работает лучше, чем моя маркировка как код, и, Эй, посмотрите на эту последнюю строку:

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

мне нравится ответ от 30thh с помощью Iterators из гуавы. Однако из некоторых фреймворков я получаю 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;
        }
    };
}

Comments

    Ничего не найдено.