web-dev-qa-db-fra.com

Tapez Liste vs tapez ArrayList dans Java

(1) List<?> myList = new ArrayList<?>();

(2) ArrayList<?> myList = new ArrayList<?>();

Je comprends qu’avec (1), les implémentations de l’interface List peuvent être permutées. Il semble que (1) soit généralement utilisé dans une application quel que soit le besoin (moi-même, je l'utilise toujours).

Je me demande si quelqu'un utilise (2)?

Aussi, combien de fois (et puis-je obtenir un exemple) la situation nécessite-t-elle réellement l’utilisation de (1) sur (2) (c’est-à-dire où (2) ne suffirait pas..aside codage aux interfaces et meilleures pratiques )

522
kji

Presque toujours, le premier est préféré au second. Le premier a l'avantage que l'implémentation de List peut changer (en un LinkedList par exemple), sans affecter le reste du code. Ce sera une tâche difficile à effectuer avec ArrayList, non seulement parce que vous devrez changer ArrayList en LinkedList partout, mais aussi parce que vous avez peut-être utilisé des méthodes spécifiques à ArrayList.

Vous pouvez en savoir plus sur les implémentations de Listhere . Vous pouvez commencer par ArrayList, mais vous découvrirez peu après qu'une autre implémentation est plus appropriée.

414
kgiannakakis

Je me demande si quelqu'un utilise (2)?

Oui. Mais rarement pour une bonne raison (IMO).

Et les gens sont brûlés parce qu'ils ont utilisé ArrayList alors qu'ils auraient dû utiliser List:

  • Les méthodes utilitaires telles que Collections.singletonList(...) ou Arrays.asList(...) ne renvoient pas de ArrayList.

  • Les méthodes de l'API List ne garantissent pas le renvoi d'une liste du même type.

Par exemple, si quelqu'un est brûlé, dans https://stackoverflow.com/a/1481123/139985 , le poster a eu des problèmes avec le "découpage" car ArrayList.sublist(...) ne renvoie pas de ArrayList ... et il avait conçu son code pour utiliser ArrayList comme type de toutes ses variables de liste. Il a fini par "résoudre" le problème en copiant la sous-liste dans un nouveau ArrayList.

L'argument selon lequel vous devez savoir comment se comporte la variable List est largement traité à l'aide de l'interface de marqueur RandomAccess. Oui, c'est un peu maladroit, mais l'alternative est pire.

En outre, à quelle fréquence la situation nécessite-t-elle réellement l’utilisation de (1) sur (2) (c’est-à-dire où (2) ne suffirait pas… à part le "codage aux interfaces" et aux meilleures pratiques, etc.)

La partie "combien de fois" de la question est objectivement irréfutable.

(et puis-je s'il vous plaît obtenir un exemple)

Parfois, l'application peut nécessiter que vous utilisiez des méthodes dans l'API ArrayList qui ne sont pas dans l'API List. Par exemple, ensureCapacity(int), trimToSize() ou removeRange(int, int). (Et le dernier ne surviendra que si vous avez créé un sous-type de ArrayList qui déclare la méthode comme étant public.)

C’est la seule bonne raison de coder pour la classe plutôt que pour l’interface IMO.

(Il est théoriquement possible que vous obteniez une légère amélioration des performances ... dans certaines circonstances ... sur certaines plates-formes ... mais à moins que vous n'ayez vraiment besoin de cette dernière valeur, cela ne vaut pas la peine de le faire. raison sonore, IMO.)


Vous ne pouvez pas écrire de code efficace si vous ne savez pas si l'accès aléatoire est efficace ou non.

C'est une remarque véridique. Cependant, Java offre de meilleurs moyens de gérer ce problème. par exemple.

public <T extends List & RandomAccess> void test(T list) {
    // do stuff
}

Si vous appelez cela avec une liste qui n'implémente pas RandomAccess, vous obtiendrez une erreur de compilation.

Vous pouvez également tester dynamiquement ... en utilisant instanceof ... si le typage statique est trop compliqué. Et vous pourriez même écrire votre code pour utiliser différents algorithmes (dynamiquement) selon qu'une liste prend en charge l'accès aléatoire.

Notez que ArrayList n'est pas la seule classe de liste qui implémente RandomAccess. D'autres incluent CopyOnWriteList, Stack et Vector.

J'ai vu des gens se disputer de la même manière à propos de Serializable (car List ne le met pas en œuvre) ... mais l'approche ci-dessus résout également ce problème. (Dans la mesure où il est résolvable du tout en utilisant des types d'exécution. Un ArrayList échouera à la sérialisation si un élément n'est pas sérialisable.)

108
Stephen C

Par exemple, vous pouvez décider que LinkedList est le meilleur choix pour votre application, mais que vous décidiez plus tard, ArrayList pourrait être un meilleur choix pour des raisons de performances.

Utilisation:

List list = new ArrayList(100); // will be better also to set the initial capacity of a collection 

Au lieu de:

ArrayList list = new ArrayList();

Pour référence:

enter image description here

(posté principalement pour le diagramme de collection)

39
Maxim Shoustin

Il est considéré comme un bon style de stocker une référence à une HashSet ou TreeSet dans une variable de type Set.

Set<String> names = new HashSet<String>();

De cette façon, vous ne devez modifier qu'une seule ligne si vous décidez d'utiliser plutôt un TreeSet.

De plus, les méthodes qui fonctionnent sur des ensembles doivent spécifier des paramètres de type Ensemble:

public static void print(Set<String> s)

Ensuite, la méthode peut être utilisée pour toutes les implémentations de l'ensemble .

En théorie, nous devrions faire la même recommandation pour les listes chaînées, à savoir enregistrer les références LinkedList dans des variables de type List. Cependant, dans la bibliothèque Java, l'interface List est commune à la classe ArrayList et à la classe LinkedList. En particulier, il a défini et défini des méthodes pour un accès aléatoire, même si ces méthodes sont très inefficaces pour les listes chaînées.

Vous ne pouvez pas écrire de code efficace si vous ne savez pas si l'accès aléatoire est efficace ou non .

Il s'agit clairement d'une grave erreur de conception dans la bibliothèque standard, et je ne peux pas recommander l'utilisation de l'interface List pour cette raison.

Pour voir à quel point cette erreur est embarrassante, jetez un coup d’œil au code source de la méthode binarySearch de la Collections classe. Cette méthode prend un paramètre List, mais la recherche binaire n’a aucun sens pour une liste liée. Le code tente alors maladroitement de déterminer si la liste est une liste chaînée, puis bascule vers une recherche linéaire!

Les interfaces Set et Map sont bien conçues et vous devez les utiliser.

23
nazar_art

J'utilise (2) si le code est le "propriétaire" de la liste. Ceci est par exemple vrai pour les variables locales uniquement. Il n'y a aucune raison d'utiliser le type abstrait List au lieu de ArrayList. Un autre exemple pour démontrer la propriété:

public class Test {

    // This object is the owner of strings, so use the concrete type.
    private final ArrayList<String> strings = new ArrayList<>();

    // This object uses the argument but doesn't own it, so use abstract type.
    public void addStrings(List<String> add) {
        strings.addAll(add);
    }

    // Here we return the list but we do not give ownership away, so use abstract type. This also allows to create optionally an unmodifiable list.
    public List<String> getStrings() {
        return Collections.unmodifiableList(strings);
    }

    // Here we create a new list and give ownership to the caller. Use concrete type.
    public ArrayList<String> getStringsCopy() {
        return new ArrayList<>(strings);
    }
}
12
mazatwork

Lorsque vous écrivez List, vous indiquez en fait que votre objet implémente uniquement l'interface List, mais vous ne spécifiez pas la classe à laquelle appartient votre objet.

Lorsque vous écrivez ArrayList, vous indiquez que votre classe d'objets est un tableau redimensionnable.

La première version rend donc votre code plus flexible à l’avenir.

Regardez Java docs:

Classe ArrayList - Implémentation de tableau redimensionnable de l'interface List.

Interface List - Une collection ordonnée (également appelée séquence). L'utilisateur de cette interface a un contrôle précis sur l'endroit où chaque élément est inséré dans la liste.

Array - objet conteneur qui contient un nombre fixe de valeurs d'un seul type.

11
wzbozon

(3) Collection myCollection = new ArrayList ();

J'utilise ceci typiquement. Et seulement si j'ai besoin des méthodes List, je vais utiliser List. Idem avec ArrayList. Vous pouvez toujours basculer vers une interface plus "étroite", mais pas plus "large".

9
Pavel Vyazankin

Je pense que les personnes qui utilisent (2) ne connaissent pas le principe de substitution de Liskov ou le principe d'inversion de dépendance . Ou bien ils doivent vraiment utiliser ArrayList.

9
True Soft

En fait, il existe des cas où (2) est non seulement préféré, mais obligatoire et je suis très surpris que personne ne le mentionne ici.

Sérialisation!

Si vous avez une classe sérialisable et que vous voulez qu'elle contienne une liste, vous devez alors déclarer que le champ est d'un type concret et sérialisable comme ArrayList, car l'interface List ne s'étend pas Java.io.Serializable

Évidemment, la plupart des gens n’ont pas besoin de la sérialisation et oublient cela.

Un exemple:

public class ExampleData implements Java.io.Serializable {

// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();
9
raudi

Parmi les deux suivants:

(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();

Le premier est généralement préféré. Comme vous n'utiliserez que des méthodes provenant de l'interface List, vous aurez la liberté d'utiliser une autre implémentation de List par exemple. LinkedList à l'avenir. Cela vous dissocie donc d'une mise en œuvre spécifique. Il convient maintenant de mentionner deux points:

  1. Nous devrions toujours programmer pour l'interface. Plus ici .
  2. Vous finirez presque toujours par utiliser ArrayList sur LinkedList. Plus ici .

Je me demande si quelqu'un utilise (2)

Oui parfois (lu rarement). Lorsque nous avons besoin de méthodes faisant partie de l'implémentation de ArrayList mais pas de l'interface List. Par exemple, ensureCapacity.

En outre, à quelle fréquence (et puis-je obtenir un exemple, s'il vous plaît) la situation nécessite-t-elle réellement l'utilisation de (1) sur (2)

Presque toujours, vous préférez l'option (1). Il s'agit d'un modèle de conception classique dans OOP dans lequel vous essayez toujours de découpler votre code d'une implémentation spécifique et d'un programme vers l'interface.

7
i_am_zero

Quelqu'un a demandé à nouveau (duplicata) ce qui m'a fait aller un peu plus loin sur cette question.

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("a");
    list.add("b");

    ArrayList<String> aList = new ArrayList<String>();
    aList.add("a");
    aList.add("b");

}

Si nous utilisons un visualiseur de bytecode (j’ai utilisé http://asm.ow2.org/Eclipse/index.html ), nous verrons ce qui suit (uniquement l’initialisation et l’affectation de liste) pour notre list extrait:

   L0
    LINENUMBER 9 L0
    NEW ArrayList
    DUP
    INVOKESPECIAL ArrayList.<init> () : void
    ASTORE 1
   L1
    LINENUMBER 10 L1
    ALOAD 1: list
    LDC "a"
    INVOKEINTERFACE List.add (Object) : boolean
    POP
   L2
    LINENUMBER 11 L2
    ALOAD 1: list
    LDC "b"
    INVOKEINTERFACE List.add (Object) : boolean
    POP

et pour alist :

   L3
    LINENUMBER 13 L3
    NEW Java/util/ArrayList
    DUP
    INVOKESPECIAL Java/util/ArrayList.<init> ()V
    ASTORE 2
   L4
    LINENUMBER 14 L4
    ALOAD 2
    LDC "a"
    INVOKEVIRTUAL Java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP
   L5
    LINENUMBER 15 L5
    ALOAD 2
    LDC "b"
    INVOKEVIRTUAL Java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP

La différence est liste finit par appeler INVOKEINTERFACE alors que aList appelle INVOKEVIRTUAL. Selon la référence du plug-in Bycode Outline,

invokeinterface est utilisé pour invoquer une méthode déclarée dans une interface Java

tout invokevirtual

invoque toutes les méthodes sauf les méthodes d'interface (qui utilisent invokeinterface), les méthodes statiques (qui utilisent invokestatic) et les quelques cas spéciaux gérés par invokespecial.

En résumé, invokevirtual supprime objectref de la pile pour invokeinterface

l'interpréteur extrait les "n" éléments de la pile d'opérandes, où "n" est un paramètre d'entier non signé de 8 bits extrait du bytecode. Le premier de ces éléments est objectref, une référence à l'objet dont la méthode est appelée.

Si je comprends bien, la différence réside essentiellement dans la manière dont chaque moyen récupère objectref .

3
toxicafunk

List est une interface.Il n'a pas de méthodes. Lorsque vous appelez une méthode sur une référence List, il appelle en fait la méthode ArrayList dans les deux cas.

Et pour l'avenir, vous pouvez changer List obj = new ArrayList<> en List obj = new LinkList<> ou un autre type qui implémente Interface de liste.

3
Xar-e-ahmer Khan

Le seul cas que je connaisse où (2) peut être mieux est l'utilisation de GWT, car cela réduit l'empreinte de l'application (ce n'est pas mon idée, mais l'équipe de la boîte à outils google). Mais pour Java normal, l'exécution à l'intérieur de la machine virtuelle Java (1) est probablement toujours préférable.

2
Hans Westerbeek

Je dirais que 1 est préférable, à moins que

  • vous dépendez de l'implémentation du comportement optionnel * dans ArrayList, dans ce cas, utiliser explicitement ArrayList est plus clair
  • Vous utiliserez ArrayList dans un appel de méthode nécessitant ArrayList, éventuellement pour des caractéristiques de comportement ou de performance facultatives.

Mon hypothèse est que dans 99% des cas, vous pouvez vous en tirer avec List, ce qui est préférable.

  • par exemple removeAll ou add(null)
1
extraneon

List interface ont plusieurs classes différentes - ArrayList et LinkedList. LinkedList est utilisé pour créer une collection indexée et ArrayList - pour créer des listes triées. Ainsi, vous pouvez utiliser n'importe lequel de ces éléments dans vos arguments, mais vous pouvez autoriser les autres développeurs utilisant votre code, votre bibliothèque, etc., à utiliser différents types de listes, pas uniquement celles que vous utilisez, donc, dans cette méthode

ArrayList<Object> myMethod (ArrayList<Object> input) {
   // body
}

vous pouvez l’utiliser uniquement avec ArrayList et non pas LinkedList, mais vous pouvez autoriser l’utilisation de l’une des classes List sur d’autres endroits où elle est utilisée, c’est donc votre choix. L'interface peut permettre:

List<Object> myMethod (List<Object> input) {
   // body
}

Dans cette méthode, vous pouvez utiliser l'une des classes List que vous souhaitez utiliser:

List<Object> list = new ArrayList<Object> ();

list.add ("string");

myMethod (list);

CONCLUSION:

Utilisez les interfaces partout lorsque cela est possible, ne vous contraignez pas, ainsi que les autres utilisateurs, à utiliser différentes méthodes qu'ils souhaitent utiliser.

1
Acuna