example - list string java
¿Por qué la enumeración se convierte a ArrayList y no a List en java.utils? (4)
¿Hay una buena razón por la que el método
Collections.list()
en el paquete
java.utils
devuelve una
ArrayList<T>
lugar de
List<T>
?
Obviamente, una
ArrayList
es una
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.
Al regresar a
List
, promocionará el
programa a una interfaz
, lo cual es una muy buena práctica.
Sin embargo, este enfoque tiene su limitación.
Por ejemplo, no puede usar algunos métodos que están definidos para
ArrayList
y que no existen en la interfaz de
List
. Consulte
esta respuesta
para obtener más detalles.
Cito el diseño de API de los Tutoriales de Java ™:
... Está bien devolver un objeto de cualquier tipo que implemente o extienda una de las interfaces de colección. Esta puede ser una de las interfaces o un tipo de propósito especial que extiende o implementa una de estas interfaces.
.. En cierto sentido, los valores de retorno deben tener el comportamiento opuesto de los parámetros de entrada: es mejor devolver la interfaz de recopilación aplicable más específica en lugar de la más general . Por ejemplo, si está seguro de que siempre devolverá un
SortedMap
, debe dar al método correspondiente el tipo de retorno deSortedMap
lugar deMap
.SortedMap
instancias deSortedMap
más tiempo para crear que las instancias deMap
normales y también son más potentes. Dado que su módulo ya ha invertido el tiempo para construir unSortedMap
, tiene sentido darle al usuario acceso a su mayor potencia. Además, el usuario podrá pasar el objeto devuelto a métodos que exijan unSortedMap
, así como a aquellos que acepten cualquierMap
.
Dado 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 en una lista, mi elección sería una lista de matriz.
En cualquier otro caso, todavía es válido escribir:
List<T> list = Collections.list(e);
Hay una pequeña sobrecarga en los métodos de llamada de una interfaz en lugar de directamente en un objeto.
Esta sobrecarga no suele ser más de 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 es medible para la mayoría de los códigos correctos para usted y para mí, pero para los métodos de bajo nivel en java.utils puede usarse en algunos códigos donde es un problema.
Además, 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 utiliza. Este cambio en el rendimiento puede ser muy grande, de tal manera que el software de llamada no funciona.
Claramente, los autores de
java.utils
no tienen forma de saber qué hace todo el software que llama a 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 Lista, ¡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 incluye saber dónde es un problema el rendimiento. Ser capaz de hacer un cambio en un método, sin tener que cambiar las llamadas, es de gran valor mientras el diseño del software está cambiando.
Por lo tanto, las dos compensaciones son muy diferentes.
Las funciones que devuelven la "propiedad exclusiva" de los objetos mutables recién creados a menudo deberían ser el tipo más específico práctico; aquellos que devuelven objetos inmutables, especialmente si se pueden compartir, a menudo deberían devolver tipos menos específicos.
La razón de la distinción es que en el primer caso, un objeto siempre podrá 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 desear realizar el destinatario, allí Por lo general, no sería posible que el código que devuelve el objeto sepa si alguna implementación de interfaz alternativa podría satisfacer las necesidades del destinatario.
En el último caso, el hecho de que el objeto es 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, una interfaz
Immutable2dMatrix
podría implementarse mediante una clase
ImmutableArrayBacked2dMatrix
y una clase
ImmutableDiagonal2dMatrix
.
Una función que se supone que devuelve un cuadrado
Immutable2dMatrix
podría decidir devolver una instancia
ImmutableDiagonalMatrix
si todos los elementos fuera de la diagonal principal son cero, o un
ImmutableArrayBackedMatrix
si no.
El primer tipo ocuparía mucho menos espacio de almacenamiento, pero al destinatario no debería importarle la diferencia entre ellos.
Devolver
Immutable2dMatrix
lugar de un
ImmutableArrayBackedMatrix
concreto permite que el código elija el tipo de retorno en función de lo que contiene 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 "victorias" cuando se trabaja con objetos inmutables.
Sin embargo, cuando se trabaja 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á tales elementos.
En consecuencia, mientras que una
ImmutableDiagonalMatrix
es efectivamente un subtipo de una
MutableDiagonalMatrix
, una
MutableDiagonalMatrix
no es un subtipo de una
Mutable2dMatrix
, ya que la última podría aceptar tiendas fuera de la diagonal principal mientras que la primera no.
Además, aunque los objetos inmutables a menudo pueden y deben compartirse, los objetos mutables generalmente no pueden.
Una función que solicita una nueva colección mutable inicializada con cierto contenido deberá crear una nueva colección, independientemente de si su almacén de respaldo coincide o no con el tipo solicitado.
Descargo de responsabilidad: no soy un autor de JDK.
Estoy de acuerdo en que es correcto escribir su
propio código
en las interfaces, pero si va a devolver una colección
mutable
a un tercero, es importante que el tercero sepa qué tipo de
List
está recuperando.
LinkedList
y
ArrayList
son muy diferentes, en cuanto al rendimiento, para diversas operaciones.
Por ejemplo, eliminar el primer elemento de una
ArrayList
es
O(n)
, pero eliminar el primer elemento de una
LinkedList
es
O(1)
.
Al especificar completamente el tipo de retorno, los autores de JDK están
comunicando información adicional
, en 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 necesita una
LinkedList
, sabe que debe especificar una aquí.
Finalmente, la razón principal para codificar una interfaz sobre una implementación es si cree que la implementación cambiará.
Los autores de JDK probablemente piensan que
nunca
van a cambiar este método;
nunca va a devolver una
LinkedList
o una
Collections.UnmodifiableList
.
Sin embargo, en la mayoría de los casos, probablemente aún haría:
List<T> list = Collections.list(enumeration);