Pregunta ¿Por qué Enumeration se convierte a ArrayList y no a List en java.utils?


¿Hay una buena razón para que Collections.list () método en el java.utils paquete devuelve un ArrayList<T> en lugar de List<T>?

Obviamente un ArrayList es un List, pero tengo la impresión de que generalmente es una buena práctica devolver el tipo de interfaz en lugar del tipo de implementación.


74
2017-09-02 15:05


origen


Respuestas:


Descargo de responsabilidad: no soy un autor JDK.

Estoy de acuerdo en que es correcto escribir tu propio código a las interfaces, pero si vas a regresar a mudable colección a un tercero, es importante que el tercero sepa qué tipo de List ellos están regresando

LinkedList y ArrayList son muy diferentes, en cuanto al rendimiento, para diversas operaciones. Por ejemplo, eliminar el primer elemento de un ArrayList es O(n), pero eliminando el primer elemento de un LinkedList es O(1).

Al especificar completamente el tipo de devolución, los autores de JDK son comunicando información extra, en un código inequívoco, sobre qué tipo de objeto le están devolviendo, para que pueda escribir su código para usar este método correctamente. Si realmente necesitas un LinkedList, sabes que tienes que especificar uno aquí.

Finalmente, la razón principal para codificar una interfaz sobre una implementación es si usted piensa que la implementación cambiará. Los autores de JDK probablemente creen que son Nunca va a cambiar este método; nunca va a devolver un LinkedList o una Collections.UnmodifiableList. Sin embargo, en la mayoría de los casos, probablemente aún lo haga:

List<T> list = Collections.list(enumeration);

54
2017-09-02 15:24



Al regresar List, estarás promocionando programa a una interfaz, que es una muy buena practica Sin embargo, este enfoque tiene su limitación. Por ejemplo, no puede usar algunos métodos que están definidos para ArrayList y no existen en el List interfaz - Ver esta respuesta para detalles.

Estoy citando el Diseño de API de los Tutoriales de Java:

... Está bien devolver un objeto de cualquier tipo ese implementos o se extiende una de las interfaces de colección. Esta puede ser una de las interfaces o un tipo de propósito especial que amplíe o implemente una de estas interfaces.

.. En cierto sentido, los valores de retorno deberían tener el comportamiento opuesto de los parámetros de entrada: es mejor para devolver el interfaz de colección más específica aplicable en lugar de la más general. Por ejemplo, si está seguro de que siempre devolverá un SortedMap, debe darle al método relevante el tipo de devolución de SortedMap más bien que Map. SortedMap las instancias consumen más tiempo que las ordinarias Map instancias y también son más poderosos. Dado que su módulo ya ha invertido el tiempo para construir un SortedMap, tiene sentido dar al usuario acceso a su potencia aumentada. Además, el usuario podrá pasar el objeto devuelto a métodos que exijan un SortedMap, así como aquellos que aceptan cualquier Map.

Ya que ArrayList es esencialmente una matriz, son mi primera opción cuando necesito tener una "colección-matriz". Entonces, si quiero convertir la enumeración a una lista, mi elección sería una lista de matriz.

En cualquier otro caso, sigue siendo válido para escribir:

List<T> list = Collections.list(e);

31
2017-09-02 15:24



Las funciones que devuelven "propiedad exclusiva" de objetos mutables recién creados a menudo deben ser el tipo más específico práctico; aquellos que devuelven objetos inmutables, especialmente si pueden ser compartidos, a menudo deben devolver tipos menos específicos.

El motivo de la distinción es que, en el primer caso, un objeto siempre será capaz de producir un nuevo objeto del tipo indicado, y dado que el destinatario será el propietario del objeto y no se sabe qué acciones podría realizar el destinatario allí. En general, no sería posible que el código devuelva el objeto para saber si alguna implementación de interfaz alternativa podría satisfacer las necesidades del destinatario.

En este último caso, el hecho de que el objeto sea inmutable significa que la función puede identificar un tipo alternativo que puede hacer todo lo que un tipo más complicado podría hacer. dado su contenido exacto. Por ejemplo, un Immutable2dMatrix la interfaz puede ser implementada por un ImmutableArrayBacked2dMatrix clase y una ImmutableDiagonal2dMatrix clase. Una función que se supone que devuelve un cuadrado Immutable2dMatrix podría decidir devolver un ImmutableDiagonalMatrix ejemplo si todos los elementos fuera de la diagonal principal resultan ser cero, o una ImmutableArrayBackedMatrix si no. El primer tipo tomaría mucho menos espacio de almacenamiento, pero al destinatario no debería importarle la diferencia entre ellos.

Regresando Immutable2dMatrix en lugar de un hormigón ImmutableArrayBackedMatrix permite que el código elija el tipo de devolución en función de lo que contenga la matriz; También significa que si el código que se supone que devuelve la matriz tiene una implementación adecuada de Immutable2dMatrix simplemente puede devolver eso, en lugar de tener que construir una nueva instancia. Ambos factores pueden ser importantes "ganancias" cuando se trabaja con objetos inmutables.

Sin embargo, al trabajar con objetos mutables, ninguno de los factores entra en juego. El hecho de que una matriz mutable no tenga elementos fuera de la diagonal principal cuando se genera no significa que nunca tendrá dichos elementos. En consecuencia, mientras ImmutableDiagonalMatrix es efectivamente un subtipo de un Immutable2dMatrix, un MutableDiagonalMatrix no es un subtipo de un Mutable2dMatrix, ya que este último podría aceptar tiendas fuera de la diagonal principal mientras que el primero no puede. Además, aunque los objetos inmutables a menudo pueden y deben ser compartidos, los objetos mutables generalmente no pueden. Una función que se solicita para una nueva colección mutable inicializada con cierto contenido deberá crear una nueva colección, independientemente de si su almacén de respaldo coincide con el tipo que se solicita.


6
2017-09-02 18:48



Hay un pequeña sobrecarga en los métodos de llamada var una interfaz en lugar de directamente en un objeto.

Esta sobrecarga a menudo no es más que 1 o 2 instrucciones del procesador. La sobrecarga de llamar a un método es aún menor si el JIT sabe que el método es final. Esto no se puede medir para la mayoría de los códigos nosotros y para nosotros, pero para los métodos de bajo nivel en java.utils se puede usar en algunos códigos donde sea un problema.

También como se ha señalado en otras respuestas, el tipo concreto del objeto que se devuelve (incluso cuando está oculto detrás de una interfaz) afecta el rendimiento del código que lo usa. Este cambio en el rendimiento puede ser muy grande, por lo que el software de llamada no funciona.

Claramente los autores de java.utils No tenemos forma de saber qué hace todo el software que llama Collections.list () con el resultado y no hay forma de volver a probar este software si cambian la implantación de Collections.list (). Por lo tanto, no van a cambiar la implantación de Collections.list () para devolver un tipo diferente de List, incluso si el sistema de tipos lo permite.

Al escribir su propio software, usted (con suerte) tiene una prueba automatizada que cubre todo su código y una buena comprensión de cómo se interrelaciona su código, que incluye saber dónde el rendimiento es un problema. Ser capaz de hacer un cambio en un método, sin tener que cambiar las personas que llaman es de gran valor, mientras que el diseño del software está cambiando.

Por lo tanto, los dos conjuntos de compensaciones son muy diferentes.


1
2017-09-03 08:29