(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 )
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 List
here . Vous pouvez commencer par ArrayList
, mais vous découvrirez peu après qu'une autre implémentation est plus appropriée.
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.)
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:
(posté principalement pour le diagramme de collection)
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.
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);
}
}
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.
(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".
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
.
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<>();
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:
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.
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 .
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.
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.
Je dirais que 1 est préférable, à moins que
Mon hypothèse est que dans 99% des cas, vous pouvez vous en tirer avec List, ce qui est préférable.
removeAll
ou add(null)
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.