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

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

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

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

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

Ответ 1

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

Коллекции сообщества Apache ArrayIterator

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

com.Ostermiller.util.ArrayIterator

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

Ответ 2

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

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

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

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

Ответ 3

<ы > Arrays.asList(обр).iterator();

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

Ответ 4

Google Коллекция Guava Librarie предоставляет такую ​​функцию:

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

Нужно предпочесть Guava над Apache Collection (который, кажется, заброшен).

Ответ 5

В Java 8:

Arrays.stream(arr).iterator();

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

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

Ответ 9

Как работает вышеперечисленное для каждого цикла?

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

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

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

[...]

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

Пусть L1 ... Lm - (возможно, пустая) последовательность меток, непосредственно предшествующая инструкции расширенного для.

Усиленный оператор for эквивалентен основному выражению формы:

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

#a и #i - это автоматически генерируемые идентификаторы, отличные от любых других идентификаторов (автоматически сгенерированных или других), которые находятся в области видимости в точке, где происходит расширение для утверждения.

Является ли массив преобразованным в список для получения итератора?

Пусть 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 to 14: создать массив
  • 15 to 22: подготовьтесь к циклу for. В 22 храните целое число 0 из стека в локальную позицию 4. Это переменная цикла.
  • 24 to 47: цикл. Переменная цикла получается в 31 и увеличивается на 44. Когда он равен длине массива, которая хранится в локальной переменной 3 при проверке в 27, цикл заканчивается.

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

Ответ 10

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

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

Ответ 11

Я немного опаздываю в игру, но я заметил некоторые ключевые моменты, которые были упущены, особенно в отношении 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);
}

Для цикла:

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

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

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

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

Ответ 12

Я недавний студент, но я ВЕРЮ пример из оригинала, когда int [] выполняет итерацию по массиву примитивов, но не используя объект Iterator. Он просто имеет тот же (похожий) синтаксис с другим содержимым,

for (primitive_type : array) { }

for (object_type : iterableObject) { }

Arrays.asList() APPARENTLY просто применяет методы List к массиву объектов, который он дал, но для любого другого типа объекта, включая примитивный массив, iterator(). next() APPARENTLY просто передает вам ссылку на оригинал объект, рассматривая его как список с одним элементом. Можем ли мы увидеть исходный код для этого? Вы предпочли бы исключение? Неважно. Я думаю (что УГАДАЙ), что ему нравится (или это) одноэлементная коллекция. Итак, здесь 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());
}
}

Ответ 13

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