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