¿Hay alguna buena razón para usar la interfaz de la Colección de Java?

11

He escuchado el argumento de que debe usar la interfaz más genérica disponible para que no esté vinculado a una implementación particular de esa interfaz. ¿Se aplica esta lógica a interfaces como java.util.Collection ?

Preferiría ver algo como lo siguiente:

List<Foo> getFoos()

o

Set<Foo> getFoos()

en vez de

Collection<Foo> getFoos()

En el último caso, no sé con qué tipo de conjunto de datos estoy tratando, mientras que en los primeros dos casos puedo hacer algunas suposiciones sobre el orden y la unicidad. ¿Tiene java.util.Collection una utilidad fuera de ser un padre lógico tanto para conjuntos como para listas?

Si se encuentra con un código que empleó Collection cuando realiza una revisión de código, ¿cómo determinaría si su uso está justificado y qué sugerencias haría para su reemplazo por una interfaz más específica?

Fil
fuente
3
¿Notaste en java.security.cert que un tipo de retorno es Collection<List<?>>? ¡Habla sobre codificar el horror!
Macneil
1
@ Macneil No sé a qué clase te refieres, pero ese tipo de retorno podría ser bastante sensato. Básicamente, le dice que tiene una colección (es decir, que contiene un montón de cosas que no tiene un orden razonable) de listas (es decir, que contiene cosas que tienen un orden razonable) de objetos (es decir, elementos cuyos tipos no conocemos estáticamente para cualquier razón). No me parece irrazonable.
Zero3

Respuestas:

13

Las abstracciones viven más que las implementaciones

En general, cuanto más abstracto sea su diseño, más tiempo es probable que siga siendo útil. Entonces, dado que Collection es más abstracto que sus subinterfaces, es más probable que un diseño de API basado en Collection siga siendo útil que uno basado en List.

Sin embargo, el principio general es utilizar la abstracción más apropiada . Entonces, si su colección debe admitir elementos ordenados, entonces ordene una Lista, si no debe haber duplicados, entonces ordene un Conjunto, y así sucesivamente.

Una nota sobre diseño genérico de interfaz

Dado que está interesado en utilizar la interfaz de la Colección con genéricos, puede ser útil lo siguiente. Java eficaz por Joshua Bloch recomienda el siguiente enfoque al diseñar una interfaz que dependerá de genéricos: Producers Extend, Consumers Super

Esto también se conoce como la regla PECS . Esencialmente, si las colecciones genéricas que producen datos se pasan a su clase, la firma debería verse así:

public void pushAll(Collection<? extends E> producerCollection) {}

Por lo tanto, el tipo de entrada puede ser E o cualquier subclase de E (E se define como una superclase y una subclase de sí mismo en el lenguaje Java).

Por el contrario, una colección genérica que se pasa para consumir datos debe tener una firma como esta:

public void popAll(Collection<? super E> consumerCollection) {}

El método será tratar correctamente con cualquier superclase de E. En general, el uso de este enfoque hará que su interfaz de menos sorprendente a sus usuarios, ya que será capaz de pasar en Collection<Number>y Collection<Integer>y hacer que se tratan correctamente.

Gary Rowe
fuente
6

La Collectioninterfaz, y la forma más permisiva Collection<?>, es excelente para los parámetros que acepta. Según el uso en la propia biblioteca de Java , es más común como tipo de parámetro que como tipo de retorno.

Para los tipos de devolución, creo que su punto es válido: si se espera que las personas accedan a él, deben conocer el orden (en el sentido Big-O) de la operación que se realiza. Repetiría un Collectiondevuelto y lo agregaría a otra Colección, pero parecería un poco loco recurrir containsa él, sin saber si es una operación O (1), O (log n) u O (n). Por supuesto, solo porque tenga un Setno significa que es un hashset o un conjunto ordenado, pero en algún momento hará suposiciones de que la interfaz se ha implementado razonablemente (y luego deberá ir al plan B si su suposición se muestra que es incorrecto).

Como menciona Tom, a veces debe devolver un Collectionpara mantener la encapsulación: no desea que se filtren los detalles de implementación, incluso si pudiera devolver algo más específico. O, en el caso que Tom mencionó, podría devolver el contenedor más específico, pero luego tendría que construirlo.

Macneil
fuente
2
Creo que ese segundo punto es un poco débil. No sabe cómo funcionará la colección, independientemente de si es Colección o Lista, ya que son solo abstracciones. A menos que tenga una clase final concreta, realmente no puede saberlo.
Mark H
Además, si todo lo que sabe es que algo es una Colección, no tiene idea de si puede contener duplicados o no. Cambiando eso, una vez el caso en que devolver la Colección podría ser apropiado es si tiene una colección que no contiene duplicados y no tiene un orden significativo (que naturalmente sería un Conjunto), pero donde por alguna buena razón, la implementación del método de devolución usa una lista. No querrá devolver una Lista, porque eso implica un orden significativo, pero no puede devolver un Conjunto sin pasar por el rigor de hacer uno. Entonces devuelves una Colección.
Tom Anderson el
@Tom: ¡Buen punto!
Macneil
5

Lo miraría desde el punto de vista completamente opuesto y preguntaría:

Si se encuentra con un código que empleó la Lista <> cuando realiza una revisión del código, ¿cómo determinaría si su uso está justificado?

Es bastante fácil justificar esto. Utiliza la Lista cuando necesita alguna funcionalidad que no ofrece una Colección. Si no necesita esa funcionalidad adicional, ¿qué justificación tiene? (Y no compraré, "Prefiero verlo")

Hay muchos casos en los que usará una colección con fines de solo lectura, rellenándola de una vez, repitiéndola por completo: ¿alguna vez necesita indexar la cosa manualmente?

Para dar un ejemplo real. Digamos que realizo una consulta simple en una base de datos. ( SELECT id,name,rep FROM people WHERE name LIKE '%squared') Recojo los datos relevantes, relleno los objetos Person y los pongo en una PersonList)

  • ¿Necesito acceder por índice? - No tendría sentido. No hay mapeo entre índice e ID.
  • ¿Necesito insertar en un índice? - No, el DBMS decidirá dónde colocarlo, si agrego algo.

Entonces, ¿qué justificación tendría para esos métodos adicionales? (que de todos modos quedaría sin implementar en mi PersonList)

Mark H
fuente
Punto justo. Supongo que mi pregunta se refiere a una instancia específica en la que, mientras reviso el código, sigo viendo DAO que devuelven colecciones, pero sé que estas llamadas DAO siempre van a devolver conjuntos de entidades; mi opinión es que en estos casos el tipo de retorno indica unicidad, y esta información es útil para quien tenga que usar ese método (por ejemplo, no tengo que verificar si hay duplicados).
Fil
Si ha consultado una base de datos, comparar 2 objetos resultantes con equals () no debería producir verdadero en absoluto, por lo que necesitaría otra forma de comparar los objetos para la duplicación (por ejemplo, ¿tienen el mismo nombre, la misma ID, ¿ambos?). Debería decirle a su DAO cómo compararlos si es para eliminar duplicados, pero dado que usted es el usuario que decide si existen duplicados, es más fácil hacerlo con la recopilación del código de llamada. (Para evitar más capas de abstracción para informar al DAO cómo realizar todas las verificaciones de igualdad posibles en el planeta.)
Mark H
De acuerdo, pero estamos usando Hibernate y nos aseguramos de que nuestras entidades implementen equals (). Entonces, cuando un DAO devuelve entidades, podemos hacer rápidamente HashSet (). AddAll (resultados) y devolverlo nuevamente al método llamado.
Fil