java list interface decoupling

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(...) o Arrays.asList(...) no devuelven una ArrayList .

  • 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.



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:

  1. Siempre debemos programar a la interfaz. Más here .
  2. Casi siempre terminarás usando ArrayList sobre LinkedList . 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 o add(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.