Як працює вдосконалений оператор для масивів та як отримати ітератор для масиву?


76

Враховуючи такий фрагмент коду:

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

У мене є такі запитання:

  1. Як працює вищезазначений цикл для кожного циклу?
  2. Як отримати ітератор для масиву на Java?
  3. Чи масив перетворюється на список, щоб отримати ітератор?


Вище питання про ефективність було закрито. Він відповів тут: stackoverflow.com/questions/256859 / ...
Orangle

Я не знаходжу остаточної відповіді на пункт 1. Чи перевіряє для кожного циклу тип елемента, який повторюється, і використовує звичайний цикл for під кришками, якщо це масив? Або це створює Iterable на льоту? Або він перетворюється на список? Я не знайшов остаточної відповіді на це.
Елізабет

Відповіді:


55

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

Колекції Apache Commons ArrayIterator

Або цей, якщо ви хочете використовувати дженерики:

com.Ostermiller.util.ArrayIterator

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


2
Погодьтеся. Або створіть власний ArrayIterator, якщо хочете продовжувати використовувати примітивний тип даних
Khaled.K

Завершення коду не містить жодних пропозицій щодо ArrayIt; якщо взяти до уваги всі сторонні бібліотеки.
Марк Єронімус

50

Ні, перетворення немає. JVM просто перебирає масив, використовуючи індекс у фоновому режимі.

Цитата з Effective Java 2nd Ed., Пункт 46:

Зверніть увагу, що за використання циклу для кожного циклу не передбачено штрафу за продуктивність, навіть для масивів. Насправді, це може запропонувати невелику перевагу продуктивності перед звичайним циклом for в деяких випадках, оскільки він обчислює межу індексу масиву лише один раз.

Отже, ви не можете отримати Iteratorмасив for (якщо, звичайно, не перетворивши його на Listперший).


2
Так, +1 Це просто звичайне for. @Emil, ти не можеш.
st0le

Ефективна Java помиляється, якщо це порожня колекція. У цьому випадку він створить ітератор без потреби. Це призводить до відтоку об'єктів. Виправлення полягає в тому, щоб обернути його в перевірку isEmpty (). Це особливо важливо на пристроях з обмеженою пам'яттю, таких як Android.
клавішник

Ми впевнені, що це не створення Iterable для масиву або перетворення масиву в Список? Іншими словами, всередині Java каже: "якщо тип - це масив, то використовуйте звичайний цикл for із прихованим індексом, інакше використовуйте Iterable". ???
Елізабет

33

Arrays.asList (arr) .iterator ();

Або напишіть свій власний, реалізуючи інтерфейс ListIterator ..


23
Arrays.asList()не можу взяти int[], тільки Object[]і підкласи.
ILMTitan

3
це неправильно. це повертає a List<int[]>, а отже, an Iterator<int[]>, а не a List<Integer>!
njzk2

Ви перетворюєте масив у список лише для того, щоб надати ітератор ?!
Халед. К.

1
@KhaledKhnifer Насправді не так багато конвертації. Arrays.asList()використовує фактичний масив і обертає його в клас, який адаптує його до інтерфейсу списку. Тож за винятком проблеми "не підтримуючих примітивів" (і для людей, які прийшли сюди з такою ж проблемою в непримітивній ситуації), це насправді хороше рішення.
Джаспер

@ Khaled.K см (Zenexer в stackoverflow.com/users/1188377/zenexer ) новий відповідь.
Orangle

31

Колекція Google Guava Librarie пропонує таку функцію:

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

Слід віддавати перевагу Гуаві перед колекцією Apache (яка, здається, покинута).


1
Це, як правило, найкраща відповідь, за винятком випадків, коли ми маємо справу з масивом примітивів (таких як int[]), то це вже не працює :( оскільки ми не можемо вказати Iterator<int>абоIterator<Integer>
chakrit


10
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

Власне кажучи, ви не можете отримати ітератор примітивного масиву, оскільки Iterator.next () може повернути лише Об'єкт. Але завдяки магії автобоксу ви можете отримати ітератор, використовуючи метод Arrays.asList () .

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

Наведена вище відповідь неправильна, ви не можете використовувати Arrays.asList()примітивний масив, він поверне a List<int[]>. Використання гуави «S Ints.asList()замість цього.


5

Ви не можете безпосередньо отримати ітератор для масиву.

Але ви можете скористатися списком, підкріпленим вашим масивом, і отримати в цьому списку іератор. Для цього ваш масив повинен бути масивом Integer (замість масиву int):

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

Примітка: це лише теорія. Ви можете отримати такий ітератор, але я відмовляю вас це робити. Виконання не є добрим у порівнянні з прямою ітерацією масиву з "розширеним для синтаксису".

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


Я не думаю, що це буде працювати. Arrays.asList (arr) поверне список типу int []. Оскільки це примітивний масив.
Еміль

@Emil: Загальні засоби Java не працюють для примітивних типів. Arrays.asListнеявно піклується про це шляхом розміщення значень у масиві. Отже, результат дійсно єList<Integer> .
Конрад Рудольф,

@emil це працює, тому що Arrays.asList приймає параметр varargs
Шон Патрік Флойд,

@seanizer: але я спробував наведений вище код у своїй IDE, і він показує помилку.
Еміль

воно складається в моєму затемненні. Ви встановили відповідність компілятору щонайменше 1,5?
Шон Патрік Флойд,

4

Як працює вищезазначений цикл для кожного циклу?

Як і багато інших функцій масиву, JSL прямо згадує масиви і надає їм магічні властивості. JLS 7 14.14.2 :

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

Якщо тип виразу є підтипом Iterable, тоді переклад здійснюється наступним чином

[...]

В іншому випадку, вираз обов'язково має тип масиву, T[]. [[МАГІЯ! ]]]

Нехай 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

Зламатися:

  • 0to 14: створити масив
  • 15to 22: підготуватися до циклу for. У 22 , збережіть ціле число 0зі стека в місцеве положення 4. ТО є змінною циклу.
  • 24to 47: цикл. Змінна циклу отримується на 31та збільшується на 44. Коли вона дорівнює довжині масиву, яка зберігається в локальній змінній 3 під час перевірки 27, цикл закінчується.

Висновок : це те саме, що зробити явний цикл for зі змінною індексу, без участі ітераторів.


4

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

1. Як працює цикл для кожного?

Як Ciro Сантіллі六四事件法轮功包卓轩вказав, що є зручна утиліта для вивчення байткод , який поставляється з 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 представляє кілька нових технологій роботи з колекціями даних, кілька з яких можна використовувати для вилучення простих і відносно ефективних ітераторів з масивів. Зверніть увагу, що якщо ви використовуєте дженерики, у вас завжди буде проблема боксу-розпакування: вам потрібно буде перетворити з 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.


1
На мій погляд, найповніша відповідь, але я думаю, вам слід замінити mapToObj(Integer::new)наmapToObj(Integer::valueOf)
Michael Schmeißer

Здається, ти маєш рацію , @ MichaelSchmeißer. Дякую за підказку. Відповідь я оновив відповідно.
Zenexer

3

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

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

2

Я недавній студент, але ВІРЮ, що оригінальний приклад з 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());
}
}

1

Мені подобається відповідь із 30-ї години за допомогою IteratorsGuava. Однак з деяких фреймворків я отримую 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;
        }
    };
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.