Type Liste vs type ArrayList en 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 échangées. Il semble que (1) est généralement utilisé dans une application indépendamment du besoin (moi je l'utilise toujours).
je me demande si quelqu'un utilise (2)?
aussi, combien de fois (et puis-je s'il vous plaît obtenir un exemple) la situation exige-t-elle réellement l'utilisation de (1) sur (2) (c.-à-d. où (2) Ne suffirait pas..à part codage des interfaces et meilleures pratiques etc.)
15 réponses
presque toujours le premier est préféré au second. Le premier présente l'avantage que la mise en œuvre du List
peut changer (à un LinkedList
par exemple), sans affecter le reste du code. Ce sera une tâche difficile à faire avec un ArrayList
, non seulement parce que vous aurez besoin de changer ArrayList
à LinkedList
partout, mais aussi parce que vous pouvez avoir utilisé ArrayList
méthodes spécifiques.
Vous pouvez lire à propos de List
implémentations ici . Vous pouvez commencer avec un ArrayList
, mais peu de temps après découvrir 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 de l'Utilitaire comme
Collections.singletonList(...)
ouArrays.asList(...)
ne pas retournerArrayList
. -
dans les méthodes L'API
List
ne garantit pas le retour d'une liste du même type.
Pour l'exemple de quelqu'un se brûler, dans https://stackoverflow.com/a/1481123/139985 l'affiche avait des problèmes de "découpage" parce que ArrayList.sublist(...)
ne retourne pas une ArrayList
... et il avait conçu son code pour utiliser ArrayList
comme le type de toutes ses variables de liste. Il a fini par "résoudre" le problème en copiant le sous-liste dans une nouvelle ArrayList
.
l'argument selon lequel il faut savoir comment se comporte le List
est largement résolu en utilisant l'interface marqueur RandomAccess
. Oui, c'est un peu maladroit, mais l'alternative est pire.
en outre, à quelle fréquence la situation exige-t-elle réellement l'utilisation de (1) sur (2) (c.-à-d. où (2) Ne suffirait pas..en dehors du "codage aux interfaces" et des meilleures pratiques, etc.)
le " Combien de fois" partie de la question est, objectivement, sans réponse.
(et puis-je s'il vous plaît obtenir un exemple)
à l'occasion, l'application peut exiger que vous utilisiez des méthodes dans L'API ArrayList
qui sont et non dans L'API List
. Par exemple, ensureCapacity(int)
, trimToSize()
ou removeRange(int, int)
. (Et le dernier ne se produira que si vous avez créé un sous-type D'ArrayList qui déclare la méthode être public
.)
C'est la seule bonne raison pour le codage de la classe plutôt que de l'interface, de l'OMI.
(il est théoriquement possible que vous obtiendrez une légère amélioration de performance ... sous certaines circonstances ... sur certaines plates-formes ... mais à moins que vous ayez vraiment besoin de ce dernier 0,05%, cela ne vaut pas la peine de faire cela. Ce n'est pas une bonne raison, IMO.)
You 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 un point valable. Cependant, Java fournit de meilleures façons de traiter cela; 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 aussi tester dynamiquement ... en utilisant instanceof
... si la saisie statique est trop embarrassante. Et vous pourriez même écrire votre code pour utiliser des algorithmes différents (dynamiquement) selon si oui ou non une liste supportée l'accès aléatoire.
notez que ArrayList
n'est pas la seule classe de liste qui implémente RandomAccess
. D'autres comprennent CopyOnWriteList
, Stack
et Vector
.
j'ai vu des gens faire le même argument à propos de Serializable
(parce que List
ne l'implémente pas) ... mais l'approche ci-dessus résout ce problème aussi. (À l' mesure dans laquelle il est soluble à tous les 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 pourriez décider qu'un LinkedList
est le meilleur choix pour votre application, mais ensuite décider ArrayList
pourrait être un meilleur choix pour des raisons de performance.
Utiliser:
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:
(affiché principalement pour le diagramme de collecte)
Il est considéré comme bon style pour stocker une référence à un HashSet
ou TreeSet
dans une variable de type Ensemble.
Set<String> names = new HashSet<String>();
de cette façon, vous devez changer une seule ligne si vous décidez d'utiliser un TreeSet
à la place.
en outre, les méthodes qui fonctionnent sur des ensembles doivent spécifier les paramètres de type Set:
public static void print(Set<String> s)
puis la méthode peut être utilisée pour toutes les implémentations .
En théorie, nous devrions faire la même recommandation pour les listes liées, à savoir sauver des
Les références de liste de liens dans les variables de liste de type. Cependant, dans la bibliothèque Java, L'interface List est commune aux classes ArrayList
et LinkedList
. En particulier, il dispose de méthodes get et set pour l'accès aléatoire, même si ces les méthodes sont très inefficaces pour les listes lié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 l'interface de liste pour cette raison.
pour voir à quel point cette erreur est embarrassante, regardez
le code source de la binarySearch
méthode de la classe Collections ". Cette méthode nécessite
Liste paramètre, mais la recherche binaire n'a pas de sens pour une liste liée. Le code ensuite maladroitement
essaie de découvrir si la liste est une liste chaînée, et passe ensuite à une recherche linéaire!
l'interface Set
et l'interface 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 seulement. 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);
}
}
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 ils doivent vraiment utiliser ArrayList
.
quand vous écrivez List
, vous dites en fait, que votre objet implémente l'interface List
seulement, mais vous ne spécifiez pas à quelle classe votre objet appartient.
lorsque vous écrivez ArrayList
, vous spécifiez que votre classe d'objet est un tableau redimensionnable.
ainsi, la première version rend votre code plus flexible à l'avenir.
Regardez Java docs:
Classe ArrayList
- mise en œuvre du 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 où, dans la liste de chaque élément est inséré.
Array
- objet de conteneur qui détient un fixe nombre de valeurs d'un type unique.
en fait il y a des occasions où (2) est non seulement préférable mais obligatoire et je suis très surpris, que personne ne mentionne ceci ici.
Serialization!
si vous avez une classe sérialisable et que vous voulez qu'elle contienne une liste, alors vous devez déclarer que le champ est d'un type concret et sérialisable comme ArrayList
parce que l'interface List
ne s'étend pas java.io.Serializable
évidemment la plupart des gens ne besoin de sérialisation et oublier 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<>();
(3) Collection myCollection = new ArrayList ();
j'utilise ceci typiquement. Et seulement si j'ai besoin des méthodes List, J'utiliserai List. Pareil pour ArrayList. Vous pouvez toujours passer à plus "étroite" de l'interface, mais vous ne pouvez pas passer pour plus "large".
sur les deux suivants:
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
La mention est généralement préférée. Comme vous utiliserez les méthodes de List
interface seulement, il vous fournit la liberté d'utiliser une autre mise en œuvre de List
par exemple LinkedList
à l'avenir. Il vous dissocie donc de l'implémentation spécifique. Maintenant, il y a deux points qui valent la peine d'être mentionnés:
- nous devrions toujours programmer l'interface. Plus ici .
- vous finirez presque toujours par utiliser
ArrayList
au lieu deLinkedList
. Plus ici .
je me demande si quelqu'un utilise (2)
Oui, parfois (rarement lu). Quand nous avons besoin de méthodes qui font partie de l'implémentation de ArrayList
mais pas de l'interface List
. Par exemple ensureCapacity
.
aussi, comment souvent (et puis-je s'il vous plaît obtenir un exemple) la situation effectivement besoin de l'aide des (1) (2)
presque toujours vous préférez l'option (1). Il s'agit d'un modèle de conception classique en OOP où vous essayez toujours de découpler votre code de l'implémentation spécifique et le programme à l'interface.
le seul cas où je sais où (2) peut être mieux est lors de L'utilisation de GWT, parce qu'il réduit l'empreinte de l'application (pas mon idée, mais l'équipe Google Web toolkit le dit). Mais pour un java régulier, courir à l'intérieur de la JVM (1) est probablement toujours mieux.
List est une interface.Il n'y a pas de méthodes. Lorsque vous appelez méthode sur une référence de liste. Il appelle en fait la méthode de 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 met en œuvre interface de liste.
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 on utilise un viewer bytecode (j'ai utilisé http://asm.ow2.org/eclipse/index.html ) nous avons vu ce qui suit (seulement l'initialisation de liste et l'attribution) pour notre liste 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 accédez à la liste :
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 tandis que aList calls INVOKEVIRTUAL . D'après le Plugin de référence de Bycode Outline,
invokeinterface est utilisé pour invoquer une méthode déclarée dans un Java interface
alors que invokevirtual
invoque toutes les méthodes sauf interface méthodes (qui utilisent invokeinterface), les méthodes statiques (qui utilisent invokestatic), et les quelques cas spéciaux traités par invokespecial.
En résumé, invokevirtual pop objectref hors de la pile alors que pour invokeinterface
l'interpréteur sort ' n ' items de la pile des opérandes, où 'n' est un 8-bit non signé paramètre entier tiré 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 cela, la différence est essentiellement comment chaque chemin récupère objectref .
je dirais que 1 est préférable, sauf si
- vous dépendez de l'implémentation du comportement optionnel* dans ArrayList, dans ce cas l'utilisation explicite de ArrayList est plus claire
- vous utiliserez L'ArrayList dans un appel de méthode qui nécessite ArrayList, éventuellement pour un comportement optionnel ou des caractéristiques de performance
ma conjecture est que dans 99% des cas, vous pouvez obtenir avec la liste, qui est préféré.
- par exemple
removeAll
, ouadd(null)
List
interface ont plusieurs classes différentes - ArrayList
et LinkedList
. LinkedList
est utilisée pour créer des collections indexées et ArrayList
- pour créer des listes triées. Ainsi, vous pouvez utiliser l'une dans vos arguments, mais vous pouvez permettre à d'autres développeurs qui utilisent votre code, bibliothèque, etc. pour utiliser différents types de listes, non seulement que vous utilisez, ainsi, dans cette méthode
ArrayList<Object> myMethod (ArrayList<Object> input) {
// body
}
vous pouvez l'utiliser seulement avec ArrayList
, pas LinkedList
, mais vous pouvez permettre d'utiliser n'importe laquelle des classes List
sur d'autres endroits où la méthode utilise, c'est juste votre choix, donc utiliser une interface peut le permettre:
List<Object> myMethod (List<Object> input) {
// body
}
dans cette méthode arguments vous pouvez utiliser n'importe laquelle des classes List
que vous voulez utiliser:
List<Object> list = new ArrayList<Object> ();
list.add ("string");
myMethod (list);
CONCLUSION:
utilisez les interfaces partout où c'est possible, ne vous limitez pas à utiliser différents les méthodes qu'ils veulent utiliser.