web-dev-qa-db-fra.com

Comment fonctionne l'instruction améliorée pour les tableaux et comment obtenir un itérateur pour un tableau?

Étant donné l'extrait de code suivant:

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

J'ai les questions suivantes:

  1. Comment fonctionne ce qui précède pour chaque boucle?
  2. Comment obtenir un itérateur pour un tableau en Java?
  3. Le tableau est-il converti en une liste pour obtenir l'itérateur?
76
Emil

Si vous voulez un Iterator sur un tableau, vous pouvez utiliser l'une des implémentations directes au lieu d'encapsuler le tableau dans un List. Par exemple:

Apache Commons Collections ArrayIterator

Ou celui-ci, si vous souhaitez utiliser des génériques:

com.Ostermiller.util.ArrayIterator

Notez que si vous voulez avoir un Iterator sur les types primitifs, vous ne pouvez pas, car un type primitif ne peut pas être un paramètre générique. Par exemple, si vous voulez un Iterator<int>, vous devez utiliser un Iterator<Integer> à la place, ce qui entraînera beaucoup de procédures d'autoboxing et -unboxing si cela est soutenu par un int[].

55
uckelman

Non, il n'y a pas de conversion. La machine virtuelle Java effectue simplement une itération sur le tableau à l'aide d'un index en arrière-plan.

Extrait de Effective Java 2nd Ed., Élément 46:

Notez qu'il n'y a aucune pénalité de performance liée à l'utilisation de la boucle for-each, même pour les tableaux. En fait, elle peut offrir un léger avantage en termes de performances par rapport à une boucle for ordinaire dans certaines circonstances, car elle ne calcule la limite de l'index de tableau qu'une seule fois.

Donc, vous ne pouvez pas obtenir un Iterator pour un tableau (à moins bien sûr de le convertir d'abord en un List).

50
Péter Török

Tableaux.asList (arr) .iterator ();

Ou écrivez la vôtre en implémentant l'interface ListIterator.

33
Jochem

La collection de Google Guava Librarie s fournit cette fonction:

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

Il faut préférer Guava sur la collection Apache (qui semble abandonnée).

31
30thh

Dans Java 8:

Arrays.stream(arr).iterator();
14
Haneu
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();
  }
}
10
Ashok Domadiya

À proprement parler, vous ne pouvez pas obtenir d'itérateur du tableau primitif, car Iterator.next () ne peut renvoyer qu'un objet. Mais grâce à la magie de l'autoboxing, vous pouvez obtenir l'itérateur à l'aide de la méthode Arrays.asList () .

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

La réponse ci-dessus est fausse, vous ne pouvez pas utiliser Arrays.asList() sur un tableau primitif, cela retournerait un List<int[]>. Utilisez plutôt Ints.asList() de Guava .

9
Sean Patrick Floyd

Vous ne pouvez pas obtenir directement un itérateur pour un tableau.

Mais vous pouvez utiliser une liste, soutenue par votre tableau, et obtenir un ierator sur cette liste. Pour cela, votre tableau doit être un tableau Integer (au lieu d’un tableau int):

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

Note: ce n'est que de la théorie. Vous pouvez obtenir un itérateur comme celui-ci, mais je vous décourage de le faire. Les performances ne sont pas bonnes comparées à une itération directe sur le tableau avec la "syntaxe étendue pour".

Remarque 2: une construction de liste avec cette méthode ne prend pas en charge toutes les méthodes (car la liste est sauvegardée par le tableau qui a une taille fixe). Par exemple, la méthode "remove" de votre itérateur entraînera une exception.

5
Benoit Courtine

Comment fonctionne ce qui précède pour chaque boucle?

Comme beaucoup d'autres fonctionnalités de tableau, le JSL mentionne explicitement les tableaux et leur donne des propriétés magiques. JLS 7 14.14.2 :

EnhancedForStatement:

    for ( FormalParameter : Expression ) Statement

[...]

Si le type d'expression est un sous-type de Iterable, alors la traduction est la suivante

[...]

Sinon, l'expression a nécessairement un type de tableau, T[]. [[ LA MAGIE! ]]

Laisser L1 ... Lm soit la séquence d'étiquettes (éventuellement vide) précédant immédiatement l'instruction améliorée pour.

La déclaration for améliorée est équivalente à une déclaration for de base de la forme:

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

#a et #i sont des identifiants générés automatiquement qui sont distincts de tout autre identifiant (généré automatiquement ou non) qui se trouve dans la portée au moment où l’instruction améliorée pour se produit.

Le tableau est-il converti en une liste pour obtenir l'itérateur?

Let's javap ça va:

public class ArrayForLoop {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        for (int i : arr)
            System.out.println(i);
    }
}

ensuite:

javac ArrayForLoop.Java
javap -v ArrayForLoop

main avec un peu d’édition pour faciliter la lecture:

 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

Panne:

  • 0 à 14: créer le tableau
  • 15 à 22: prépare la boucle for. À 22 , enregistrez le nombre entier 0 de la pile à la position locale 4. C'EST la variable de boucle.
  • 24 à 47: la boucle. La variable de boucle est récupérée à 31, et incrémenté à 44. Quand il est égal à la longueur du tableau qui est stocké dans la variable locale 3 sur la vérification à 27, la boucle se termine.

Conclusion : équivaut à faire une boucle for explicite avec une variable d'index, sans itérateur.

Pour (2), Guava fournit exactement ce que vous voulez en tant que Int.asList () . Il existe un équivalent pour chaque type de primitive dans la classe associée, par exemple, Booleans pour boolean, etc.

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

Je suis un peu en retard au jeu, mais j’ai remarqué certains points essentiels qui ont été laissés de côté, notamment en ce qui concerne Java 8 et l’efficacité de Arrays.asList.

1. Comment fonctionne la boucle for-each?

Comme Ciro Santilli a souligné , il existe un utilitaire pratique pour examiner le code intermédiaire fourni avec le JDK: javap. En utilisant cela, nous pouvons déterminer que les deux extraits de code suivants produisent un code identique à partir de Java 8u74:

Pour chaque boucle:

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

Pour boucle:

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. Comment obtenir un itérateur pour un tableau en Java?

Bien que cela ne fonctionne pas pour les primitives, il convient de noter que la conversion d'un tableau en liste avec Arrays.asList N'affecte pas les performances de manière significative. L'impact sur la mémoire et les performances est presque incommensurable.

Arrays.asList N'utilise pas une implémentation List normale facilement accessible en tant que classe. Il utilise Java.util.Arrays.ArrayList, Ce qui n'est pas la même chose que Java.util.ArrayList. C'est un très fin wrapper autour d'un tableau et ne peut pas être redimensionné. En regardant le code source de Java.util.Arrays.ArrayList, Nous pouvons voir qu'il est conçu pour être fonctionnellement équivalent à un tableau. Il n'y a presque pas de frais généraux. Notez que j'ai omis tout sauf le code le plus pertinent et ajouté mes propres commentaires.

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

L'itérateur est à Java.util.AbstractList.Itr. En ce qui concerne les itérateurs, c'est très simple. il appelle simplement get() jusqu'à ce que size() soit atteint, comme le ferait une boucle manuelle. C'est l'implémentation la plus simple et généralement la plus efficace d'un Iterator pour un tableau.

Encore une fois, Arrays.asList Ne crée pas un Java.util.ArrayList. C'est beaucoup plus léger et approprié pour obtenir un itérateur avec des frais généraux négligeables.

Tableaux primitifs

Comme d'autres l'ont noté, Arrays.asList Ne peut pas être utilisé sur des tableaux primitifs. Java 8 introduit plusieurs nouvelles technologies pour traiter des ensembles de données, dont plusieurs pourraient être utilisées pour extraire des itérateurs simples et relativement efficaces à partir de tableaux. Notez que si vous utilisez des génériques, vous êtes toujours prêt pour avoir le problème boxing-unboxing: vous aurez besoin de convertir int en integer, puis de nouveau en int. Alors que boxing/unboxing est généralement négligeable, il a un O(1) performance Dans ce cas, cela aurait un impact important et pourrait entraîner des problèmes avec de très grands tableaux ou avec des ordinateurs disposant de ressources très limitées (par exemple, SoC ).

Mon favori personnel pour tout type d'opération de transtypage/boxing de tableaux dans Java 8 est la nouvelle API de flux. Par exemple:

int[] arr = {1, 2, 3};
Iterator<Integer> iterator = Arrays.stream(arr).mapToObj(Integer::valueOf).iterator();

L'API de flux propose également des constructions permettant d'éviter le problème de la boxe, mais cela nécessite d'abandonner les itérateurs au profit des flux. Il existe des types de flux dédiés pour int, long et double (IntStream, LongStream et DoubleStream, respectivement).

int[] arr = {1, 2, 3};
IntStream stream = Arrays.stream(arr);
stream.forEach(System.out::println);

Fait intéressant, Java 8 ajoute également Java.util.PrimitiveIterator. Ceci fournit le meilleur des deux mondes: compatibilité avec Iterator<T> Via la boxe avec des méthodes pour éviter la boxe. PrimitiveIterator a trois interfaces intégrées qui l'étendent: OfInt, OfLong et OfDouble. Toutes trois cocheront si next() est appelé, mais peut également renvoyer des primitives via des méthodes telles que nextInt(). Nouveau code conçu pour Java 8 devrait éviter d'utiliser next()] à moins que la boxe ne soit absolument nécessaire.

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

Si vous n'êtes pas encore sur Java 8, malheureusement, votre option la plus simple est beaucoup moins concise et impliquera presque certainement de la boxe:

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++];
    }
};

Ou si vous voulez créer quelque chose de plus réutilisable:

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++];
    }
}

Vous pouvez contourner le problème de la boxe ici en ajoutant vos propres méthodes pour obtenir des primitives, mais cela ne fonctionnerait qu'avec votre propre code interne.

3. Le tableau est-il converti en une liste pour obtenir l'itérateur?

Non ce n'est pas. Cependant, cela ne signifie pas que son insertion dans une liste va vous donner des performances pires si vous utilisez quelque chose de léger, tel que Arrays.asList.

3
Zenexer

Je suis un étudiant récent, mais JE CROIS que l'exemple original avec int [] itère sur le tableau de primitives, mais pas en utilisant un objet Iterator. Il a simplement la même syntaxe (similaire) avec des contenus différents,

for (primitive_type : array) { }

for (object_type : iterableObject) { }

Arrays.asList () APPARENTLY n’applique que les méthodes List à un tableau d’objets qui lui est attribué - mais pour tout autre type d’objet, y compris un tableau primitif, iterator (). Next () APPARENT simplement, vous remet la référence à c'est comme une liste avec un élément. Peut-on voir le code source pour cela? Ne préféreriez-vous pas une exception? Ça ne fait rien. Je suppose (c’est GUESS) que c’est comme (ou c’EST) une collection singleton. Donc ici asList () est sans importance pour le cas d’un tableau de primitives, mais déroutant. Je ne sais pas si j'ai raison, mais j'ai écrit un programme qui dit que je le suis.

Ainsi, cet exemple (où asList () ne fait pas ce que vous pensiez, et n’est donc pas quelque chose que vous utiliseriez réellement de cette façon) - j’espère que le code fonctionne mieux que mon marquage en tant que code, et, hé, regarde cette dernière ligne:

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

J'aime la réponse à partir de 30h en utilisant Iterators de Guava. Cependant, dans certains frameworks, j'obtiens null au lieu d'un tableau vide, et Iterators.forArray(array) ne gère pas très bien. Alors je suis venu avec cette méthode d'assistance, que vous pouvez appeler avec 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;
        }
    };
}
1
Max Hohenegger