Как упрощенная инструкция оператора работает для массивов и как получить итератор для массива?
Учитывая следующий фрагмент кода:
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;
}
};
}