Type List vs type ArrayList en Java
interface decoupling (15)
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
Entiendo que con (1), se pueden intercambiar las implementaciones de la interfaz de la Lista . Parece que (1) se usa normalmente en una aplicación independientemente de la necesidad (yo siempre uso esto).
Me pregunto si alguien usa (2)?
Además, con qué frecuencia (y puedo obtener un ejemplo) la situación realmente requiere el uso de (1) sobre (2) (es decir, donde (2) no sería suficiente ... además de la codificación de interfaces y las mejores prácticas, etc.)
Me pregunto si alguien usa (2)?
Sí. Pero rara vez por una buena razón (IMO).
Y las personas se queman porque usaron ArrayList
cuando deberían haber usado la List
:
Los métodos de utilidad como
Collections.singletonList(...)
oArrays.asList(...)
no devuelven unaArrayList
.Los métodos en la API de
List
no garantizan devolver una lista del mismo tipo.
Por ejemplo, si alguien se quemó, en https://.com/a/1481123/139985 el cartel tuvo problemas con el "corte en rodajas" porque ArrayList.sublist(...)
no devuelve una ArrayList
... y tuvo diseñó su código para usar ArrayList
como el tipo de todas sus variables de lista. Terminó "resolviendo" el problema copiando la lista secundaria en una nueva ArrayList
.
El argumento de que necesita saber cómo se comporta la List
se aborda en gran medida mediante el uso de la interfaz del marcador RandomAccess
. Sí, es un poco torpe, pero la alternativa es peor.
Además, ¿con qué frecuencia la situación realmente requiere el uso de (1) sobre (2) (es decir, donde (2) no sería suficiente? Además de ''codificación a interfaces'' y mejores prácticas, etc.)
La parte "con qué frecuencia" de la pregunta no tiene respuesta objetiva.
(y puedo por favor obtener un ejemplo)
Ocasionalmente, la aplicación puede requerir que use métodos en la API ArrayList
que no están en la API de List
. Por ejemplo, ensureCapacity(int)
, trimToSize()
o removeRange(int, int)
. (Y el último solo surgirá si ha creado un subtipo de ArrayList que declara que el método es public
).
Esa es la única razón para la codificación de la clase en lugar de la interfaz, IMO.
(En teoría, es posible que obtenga una leve mejora en el rendimiento ... en ciertas circunstancias ... en algunas plataformas ... pero a menos que realmente necesite ese último 0.05%, no vale la pena hacerlo. Esto no es una buena razón, IMO.
No puede escribir código eficiente si no sabe si el acceso aleatorio es eficiente o no.
Ese es un punto valido. Sin embargo, Java proporciona mejores maneras de lidiar con eso; p.ej
public <T extends List & RandomAccess> void test(T list) {
// do stuff
}
Si lo llama con una lista que no implementa RandomAccess
, obtendrá un error de compilación.
También podría probar dinámicamente ... usando instanceof
... si la escritura estática es demasiado incómoda. E incluso podría escribir su código para usar diferentes algoritmos (dinámicamente) dependiendo de si una lista admite o no acceso aleatorio.
Tenga en cuenta que ArrayList
no es la única clase de lista que implementa RandomAccess
. Otros incluyen CopyOnWriteList
, Stack
y Vector
.
He visto a personas hacer el mismo argumento sobre Serializable
(porque List
no lo implementa) ... pero el enfoque anterior resuelve este problema también. (En la medida en que pueda resolverse utilizando tipos de tiempo de ejecución. Un ArrayList
fallará en la serialización si algún elemento no es serializable).
(3) Colección myCollection = new ArrayList ();
Estoy usando esto normalmente. Y solo si necesito los métodos de Lista, usaré Lista. Lo mismo con ArrayList. Siempre se puede cambiar a una interfaz más "estrecha", pero no se puede cambiar a más "ancho".
Alguien volvió a preguntar esto (duplicado), lo que me hizo profundizar un poco más en este tema.
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 usamos un visor de código de bytes (yo usé http://asm.ow2.org/eclipse/index.html ) veremos lo siguiente (solo inicialización y asignación de listas ) para nuestro fragmento de lista :
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
y para alistar :
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 diferencia es que la lista termina llamando a INVOKEINTERFACE mientras que aList llama a INVOKEVIRTUAL . Accoding a la referencia del Bycode Outline Plugin,
invokeinterface se utiliza para invocar un método declarado dentro de una interfaz Java
mientras invokevirtual
invoca todos los métodos, excepto los métodos de interfaz (que usan invokeinterface), los métodos estáticos (que usan invokestatic) y los pocos casos especiales manejados por invokespecial.
En resumen, invokevirtual pops objectref fuera de la pila mientras que para invokeinterface
el intérprete resalta ''n'' elementos de la pila de operandos, donde ''n'' es un parámetro entero sin signo de 8 bits tomado del código de bytes. El primero de estos elementos es objectref, una referencia al objeto cuyo método se está llamando.
Si entiendo esto correctamente, la diferencia es básicamente cómo cada forma recupera objectref .
Casi siempre el primero es el preferido al segundo. El primero tiene la ventaja de que la implementación de la List
puede cambiar (a una LinkedList
por ejemplo), sin afectar el resto del código. Esta será una tarea difícil de hacer con un ArrayList
, no solo porque necesitará cambiar ArrayList
a LinkedList
todas partes, sino también porque puede haber usado métodos específicos de ArrayList
.
Puedes leer acerca de las implementaciones de List
here . Puede comenzar con un ArrayList
, pero pronto descubrirá que otra implementación es más apropiada.
Creo que las personas que usan (2) no conocen el principio de sustitución de Liskov o el principio de inversión de dependencia . O realmente tienen que usar ArrayList
.
Cuando escribe List
, realmente le dice que su objeto implementa solo la interfaz de List
, pero no especifica a qué clase pertenece su objeto.
Cuando escribe ArrayList
, especifica que su clase de objeto es una matriz de tamaño variable.
Entonces, la primera versión hace que su código sea más flexible en el futuro.
Mira los documentos de Java:
Class ArrayList
- Implementación de matriz de ArrayList
variable de la interfaz de List
.
List
interfaces : una colección ordenada (también conocida como secuencia). El usuario de esta interfaz tiene un control preciso sobre dónde se inserta cada elemento en la lista.
Array
: objeto contenedor que contiene un número fijo de valores de un solo tipo.
De los dos siguientes:
(1) List<?> myList = new ArrayList<?>();
(2) ArrayList<?> myList = new ArrayList<?>();
Primero se prefiere generalmente. Como solo utilizará los métodos de la interfaz de la List
, le ofrece la libertad de utilizar alguna otra implementación de la List
por ejemplo, LinkedList
en el futuro. Así que te desvincula de la implementación específica. Ahora hay dos puntos que vale la pena mencionar:
- Siempre debemos programar a la interfaz. Más here .
- Casi siempre terminarás usando
ArrayList
sobreLinkedList
. Más here .
Me pregunto si alguien usa (2)
Sí a veces (leer rara vez). Cuando necesitamos métodos que forman parte de la implementación de ArrayList
pero que no forman parte de la List
interfaces. Por ejemplo, ensureCapacity
.
Además, con qué frecuencia (y puedo obtener un ejemplo) la situación realmente requiere el uso de (1) sobre (2)
Casi siempre prefieres la opción (1). Este es un patrón de diseño clásico en OOP en el que siempre intenta desacoplar su código de la implementación y el programa específicos a la interfaz.
El único caso que conozco donde (2) puede ser mejor es cuando utilizo GWT, porque reduce la huella de la aplicación (no es mi idea, pero el equipo del kit de herramientas web de Google lo dice). Pero para java regular, correr dentro de la JVM (1) es probablemente siempre mejor.
En realidad, hay ocasiones en que (2) no solo es preferible sino también obligatorio y estoy muy sorprendido de que nadie mencione esto aquí.
¡Publicación por entregas!
Si tiene una clase serializable y desea que contenga una lista, entonces debe declarar que el campo es de un tipo concreto y serializable como ArrayList
porque la interfaz de la List
no se extiende java.io.Serializable
Obviamente, la mayoría de las personas no necesitan serialización y se olvidan de esto.
Un ejemplo:
public class ExampleData implements java.io.Serializable {
// The following also guarantees that strings is always an ArrayList.
private final ArrayList<String> strings = new ArrayList<>();
Por ejemplo, puede decidir que LinkedList
es la mejor opción para su aplicación, pero luego decidir que ArrayList
podría ser una mejor opción por motivos de rendimiento.
Utilizar:
List list = new ArrayList(100); // will be better also to set the initial capacity of a collection
En lugar de:
ArrayList list = new ArrayList();
Para referencia:
(publicado principalmente para el diagrama de colección)
Se considera un buen estilo para almacenar una referencia a un HashSet
o TreeSet
en una variable de tipo Set.
Set<String> names = new HashSet<String>();
De esta manera, solo tiene que cambiar una línea si decide utilizar un TreeSet
en TreeSet
lugar.
Además, los métodos que operan en conjuntos deben especificar parámetros de tipo Conjunto:
public static void print(Set<String> s)
Luego, el método se puede utilizar para todas las implementaciones de conjuntos .
En teoría, deberíamos hacer la misma recomendación para las listas vinculadas, es decir, para guardar las referencias de LinkedList en variables de tipo Lista. Sin embargo, en la biblioteca de Java, la interfaz de la Lista es común tanto para la clase ArrayList
como para la clase LinkedList
. En particular, tiene métodos de obtención y configuración para el acceso aleatorio, aunque estos métodos son muy ineficientes para las listas enlazadas.
No puede escribir código eficiente si no sabe si el acceso aleatorio es eficiente o no.
Esto es claramente un error de diseño grave en la biblioteca estándar, y no puedo recomendar el uso de la interfaz de lista por ese motivo.
Para ver qué tan vergonzoso es ese error, eche un vistazo al código fuente del método binarySearch
de la clase Collections . Ese método toma un parámetro de lista, pero la búsqueda binaria no tiene sentido para una lista enlazada. El código luego trata torpemente de descubrir si la lista es una lista vinculada y luego cambia a una búsqueda lineal.
La interfaz del Set
y la interfaz del Map
están bien diseñadas y usted debe usarlas.
Uso (2) si el código es el "propietario" de la lista. Esto es, por ejemplo, cierto para las variables locales solamente. No hay ninguna razón para usar la List
tipos abstractos en lugar de ArrayList
. Otro ejemplo para demostrar propiedad:
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);
}
}
Yo diría que se prefiere 1, a menos que
- depende de la implementación del comportamiento opcional * en ArrayList, en ese caso explícitamente usar ArrayList es más claro
- Utilizará ArrayList en una llamada de método que requiere ArrayList, posiblemente para características de comportamiento o rendimiento opcionales
Mi conjetura es que en el 99% de los casos usted puede arreglárselas con List, lo que es preferible.
- por ejemplo,
removeAll
oadd(null)
List
interfaz de la List
tiene varias clases diferentes: ArrayList
y LinkedList
. LinkedList
se utiliza para crear colecciones indexadas y ArrayList
, para crear listas ordenadas. Por lo tanto, puede usar cualquiera de ellos en sus argumentos, pero puede permitir que otros desarrolladores que usan su código, biblioteca, etc. usen diferentes tipos de listas, no solo las que usted usa, por lo tanto, en este método
ArrayList<Object> myMethod (ArrayList<Object> input) {
// body
}
puede usarlo solo con ArrayList
, no con LinkedList
, pero puede permitir usar cualquiera de las clases de List
en otros lugares donde esté usando el método, es solo su elección, así que usar una interfaz puede permitirlo:
List<Object> myMethod (List<Object> input) {
// body
}
En los argumentos de este método, puedes usar cualquiera de las clases de List
que quieras usar:
List<Object> list = new ArrayList<Object> ();
list.add ("string");
myMethod (list);
CONCLUSIÓN:
Use las interfaces en todas partes cuando sea posible, no le restrinja a usted ni a otros que usen diferentes métodos que ellos quieran usar.
La lista es una interfaz. No tiene métodos. Cuando se llama método en una referencia de lista. De hecho, se llama el método de ArrayList en ambos casos.
Y para el futuro puede cambiar List obj = new ArrayList<>
a List obj = new LinkList<>
u otro tipo que implemente la interfaz de List.