Tipo Lista vs tipo ArrayList en Java

(1) List myList = new ArrayList(); (2) ArrayList myList = new ArrayList(); 

Entiendo que con (1), las implementaciones de la interfaz de la Lista pueden intercambiarse. Parece que (1) se usa típicamente 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 encoding de interfaces y mejores prácticas, etc.)

Casi siempre, el primero es preferido sobre el segundo. El primero tiene la ventaja de que la implementación de la List puede cambiar (por ejemplo, a una LinkedList ), sin afectar el rest del código. Esta será una tarea difícil de hacer con una ArrayList , no solo porque tendrá que cambiar ArrayList a LinkedList todas partes, sino también porque puede haber usado métodos específicos de ArrayList .

Puede leer sobre implementaciones de List aquí . Puede comenzar con una ArrayList , pero poco después descubrirá que otra implementación es más apropiada.

Me pregunto si alguien usa (2)?

Sí. Pero rara vez por una buena razón.

Y a veces las personas se queman porque usaron ArrayList cuando deberían haber usado List :

  • Los métodos de utilidad como Collections.singletonList(...) o Arrays.asList(...) no devuelven una ArrayList .

  • Los métodos en la List API no garantizan devolver una lista del mismo tipo.

Por ejemplo, en https://stackoverflow.com/a/1481123/139985 el póster tuvo problemas con “slicing” porque ArrayList.sublist(...) no devuelve ArrayList … y él había diseñado su código para use ArrayList como el tipo de todas sus variables de lista. Terminó “resolviendo” el problema copiando la sublista en una nueva ArrayList .

El argumento de que necesita saber cómo se comporta la List se trata en gran medida mediante el uso de la interfaz de marcador RandomAccess . Sí, es un poco torpe, pero la alternativa es peor.

Además, ¿con qué frecuencia la situación realmente requiere usar (1) sobre (2) (es decir, donde (2) no sería suficiente … además de ‘codificar las interfaces’ y las mejores prácticas, etc.)

La parte “con qué frecuencia” de la pregunta es objetivamente incontestable.

(y puedo darme un ejemplo)

Ocasionalmente, la aplicación puede requerir que use métodos en la API de 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 de sonido para codificar a la clase en lugar de la interfaz, IMO.

(En teoría es posible que obtengas una ligera mejora en el rendimiento … en ciertas circunstancias … en algunas plataformas … pero a menos que realmente necesites ese último 0.05%, no vale la pena hacerlo. Esto no es una razón de sonido, 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 ofrece mejores formas de lidiar con eso; p.ej

 public  void test(T list) { // do stuff } 

Si llamas eso con una lista que no implementa RandomAccess , obtendrás un error de comstackción.

También podría probar dinámicamente … usando instanceof … si el tipado estático es demasiado incómodo. 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 personas que hacen la misma discusión sobre Serializable (porque List no lo implementa) … pero el enfoque anterior también resuelve este problema. (En la medida en que se puede resolver utilizando tipos de tiempo de ejecución. Una ArrayList fallará la serialización si algún elemento no es serializable).

Por ejemplo, puede decidir que LinkedList es la mejor opción para su aplicación, pero luego decide que ArrayList podría ser una mejor opción por razones 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:

enter image description here

(publicado principalmente para el diagtwig de Colección)

Se considera buen estilo almacenar una referencia a un HashSet o TreeSet en una variable de tipo Set.

Set names = new HashSet();

De esta manera, debe cambiar solo una línea si decide usar 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 s)

Entonces el método se puede usar para todas las implementaciones establecidas .

En teoría, deberíamos hacer la misma recomendación para las listas enlazadas, es decir, para guardar referencias de LinkedList en variables de tipo List. Sin embargo, en la biblioteca de Java, la interfaz de lista es común tanto para la clase ArrayList como para la clase LinkedList . En particular, tiene métodos get y set 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 grave error de diseño en la biblioteca estándar, y no puedo recomendar el uso de la interfaz de la lista por ese motivo.

Para ver cuán vergonzoso es ese error, eche un vistazo al código fuente del método binarySearch de la clase Colecciones . Ese método toma un parámetro de lista, pero la búsqueda binaria no tiene sentido para una lista vinculada. El código luego trata torpemente de descubrir si la lista es una lista enlazada, y luego cambia a una búsqueda lineal.

La interfaz Set y la interfaz Map están bien diseñadas, y debe usarlas.

Uso (2) si el código es el “propietario” de la lista. Esto es válido, por ejemplo, para las variables solo locales. No hay ninguna razón para usar el tipo abstracto List lugar de ArrayList . Otro ejemplo para demostrar la propiedad:

 public class Test { // This object is the owner of strings, so use the concrete type. private final ArrayList strings = new ArrayList<>(); // This object uses the argument but doesn't own it, so use abstract type. public void addStrings(List 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 getStrings() { return Collections.unmodifiableList(strings); } // Here we create a new list and give ownership to the caller. Use concrete type. public ArrayList getStringsCopy() { return new ArrayList<>(strings); } } 

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

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 la List .

List interfaz : una colección ordenada (también conocida como secuencia). El usuario de esta interfaz tiene un control preciso sobre en qué parte de la lista se inserta cada elemento.

Array : objeto contenedor que contiene un número fijo de valores de un solo tipo.

De hecho, hay ocasiones en que (2) no solo es preferible sino obligatorio y estoy muy sorprendido de que nadie lo mencione aquí.

¡Publicación por entregas!

Si tiene una clase serializable y desea que contenga una lista, debe declarar que el campo es de tipo concreto y serializable, como ArrayList porque la interfaz de la List no extiende java.io.Serializable

Obviamente, la mayoría de la gente no necesita ser serializada y olvidarse de esto.

Un ejemplo:

 public class ExampleData implements java.io.Serializable { // The following also guarantees that strings is always an ArrayList. private final ArrayList strings = new ArrayList<>(); 

(3) Colección myCollection = new ArrayList ();

Estoy usando esto típicamente Y solo si necesito métodos de lista, usaré List. Lo mismo con ArrayList. Siempre puede cambiar a una interfaz más “estrecha”, pero no puede cambiar a más “amplia”.

De los siguientes dos:

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

Primero es generalmente preferido. Como utilizará solo métodos de la interfaz de la List , le brinda la libertad de utilizar alguna otra implementación de List por ejemplo, LinkedList en el futuro. Por lo tanto, te desacopla de una implementación específica. Ahora hay dos puntos que vale la pena mencionar:

  1. Siempre debemos progtwigr para la interfaz. Más aquí .
  2. Casi siempre terminará usando ArrayList sobre LinkedList . Más aquí .

Me pregunto si alguien usa (2)

Sí a veces (leer raramente). Cuando necesitamos métodos que son parte de la implementación de ArrayList pero que no forman parte de la List interfaz. 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 donde siempre intentas desacoplar tu código de una implementación y progtwig específicos a la interfaz.

El único caso que conozco donde (2) puede ser mejor es cuando se usa GWT, porque reduce la huella de la aplicación (no es mi idea, pero el equipo de google web toolkit lo dice). Pero para una Java stream que se ejecuta dentro de la JVM (1) probablemente sea siempre mejor.

List es una interfaz. No tiene métodos. Cuando llamas al método en una referencia de lista. De hecho, llama al 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 la Lista.

Alguien preguntó esto de nuevo (duplicado) lo que me hizo profundizar un poco más en este tema.

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

Si utilizamos un visor de código de bytes (utilicé http://asm.ow2.org/eclipse/index.html ) veremos lo siguiente (solo listar la inicialización y la asignación) para nuestro fragmento de la lista :

  L0 LINENUMBER 9 L0 NEW ArrayList DUP INVOKESPECIAL ArrayList. () : 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 alist :

  L3 LINENUMBER 13 L3 NEW java/util/ArrayList DUP INVOKESPECIAL java/util/ArrayList. ()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 . Contabilización de la referencia del complemento del esquema Bycode,

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 muestra un objeto fuera de la stack, mientras que para invokeinterface

el intérprete saca ‘n’ elementos de la stack de operandos, donde ‘n’ es un parámetro entero sin signo de 8 bits tomado del bytecode. 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 .

Yo diría que se prefiere 1, a menos que

  • depende de la implementación del comportamiento opcional * en ArrayList, en ese caso el uso explícito de ArrayList es más claro
  • Utilizará ArrayList en una llamada a método que requiere ArrayList, posiblemente para comportamiento opcional o características de rendimiento

Mi suposición es que en el 99% de los casos se puede salir adelante con List, que es lo preferido.

  • por ejemplo removeAll , o add(null)

List interfaz de List tiene varias clases diferentes: ArrayList y LinkedList . LinkedList se usa para crear colecciones indexadas y ArrayList – para crear listas ordenadas. Por lo tanto, puede usar algo de eso en sus argumentos, pero puede permitir que otros desarrolladores que usen su código, biblioteca, etc. usen diferentes tipos de listas, no solo las que usa, entonces, en este método

 ArrayList myMethod (ArrayList input) { // body } 

solo puede usarlo con ArrayList , no LinkedList , pero puede permitir el uso de cualquiera de las clases de List en otros lugares donde utiliza el método, es solo su elección, por lo que usar una interfaz puede permitirlo:

 List myMethod (List input) { // body } 

En este método, puede usar cualquiera de las clases de la List que quiera usar:

 List list = new ArrayList (); list.add ("string"); myMethod (list); 

CONCLUSIÓN:

Use las interfaces en todas partes cuando sea posible, no le impida a usted ni a los demás usar los diferentes métodos que desean usar.