Existe um bom motivo para usar a interface de coleção do Java?

11

Ouvi o argumento de que você deve usar a interface mais genérica disponível para não ficar vinculado a uma implementação específica dessa interface. Essa lógica se aplica a interfaces como java.util.Collection ?

Prefiro ver algo como o seguinte:

List<Foo> getFoos()

ou

Set<Foo> getFoos()

ao invés de

Collection<Foo> getFoos()

No último caso, não sei com que tipo de conjunto de dados estou lidando, enquanto nos dois primeiros casos posso fazer algumas suposições sobre ordenação e exclusividade. O java.util.Collection tem uma utilidade além de ser um pai lógico para conjuntos e listas?

Se você encontrasse o código que empregava o Collection ao fazer uma revisão de código, como você determinaria se seu uso é justificado e que sugestões você faria para substituí-lo por uma interface mais específica?

Fil
fonte
3
Você notou em java.security.cert que um tipo de retorno é Collection<List<?>>? Fale sobre horror de codificação!
Macneil
1
@ Macneil Eu não sei a que classe você está se referindo, mas esse tipo de retorno pode ser bastante sensato. Essencialmente, você diz que você tem uma coleção (ou seja, contém um monte de coisas que não têm uma ordem sensata) de listas (ou seja, contém coisas que têm uma ordem sensata) de objetos (ou seja, itens cujos tipos não sabemos estaticamente para qualquer razão). Não me parece irracional.
Zero3

Respostas:

13

Abstrações vivem mais que implementações

Em geral, quanto mais abstrato for o seu design, maior será a probabilidade de permanecer útil. Portanto, como o Collection é mais abstrato do que as subinterfaces, é mais provável que um design de API baseado no Collection permaneça útil do que um baseado no List.

No entanto, o princípio geral é usar a abstração mais apropriada . Portanto, se sua coleção deve suportar elementos ordenados, solicite uma Lista, se não houver duplicatas, solicite um Conjunto e assim por diante.

Uma observação sobre o design genérico da interface

Como você está interessado em usar a interface Collection com genéricos, você pode ajudar o seguinte. O Java eficaz de Joshua Bloch recomenda a seguinte abordagem ao projetar uma interface baseada em genéricos: Producers Extend, Consumers Super

Isso também é conhecido como regra do PECS . Essencialmente, se coleções genéricas que produzem dados são passadas para sua classe, a assinatura deve ter a seguinte aparência:

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

Assim, o tipo de entrada pode ser E ou qualquer subclasse de E (E é definido como uma super e subclasse própria na linguagem Java).

Por outro lado, uma coleção genérica que é passada para consumir dados deve ter uma assinatura como esta:

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

O método lidará corretamente com qualquer superclasse de E. No geral, o uso dessa abordagem tornará sua interface menos surpreendente para seus usuários, pois você será capaz de transmitir Collection<Number>e Collection<Integer>tratá-los corretamente.

Gary Rowe
fonte
6

A Collectioninterface e a forma mais permissiva Collection<?>são ótimas para os parâmetros que você aceita. Com base no uso na própria biblioteca Java , é mais comum como um tipo de parâmetro do que um tipo de retorno.

Para tipos de retorno, acho que seu argumento é válido: se é esperado que as pessoas o acessem, elas devem saber a ordem (no sentido Big-O) da operação que está sendo executada. Eu iterava sobre um Collectionretorno e o adicionava a outra coleção, mas parece um pouco louco chamá contains-lo, sem saber se é uma operação O (1), O (log n) ou O (n). Obviamente, apenas porque você tem um Set, não significa que seja um hashset ou um conjunto classificado, mas em algum momento você fará suposições de que a interface foi implementada razoavelmente (e, em seguida, você precisará ir para o plano B, se você presumir mostrado incorreto).

Como Tom menciona, às vezes você precisa retornar um Collectionpara manter o encapsulamento: Você não deseja que os detalhes da implementação vazem, mesmo se você puder retornar algo mais específico. Ou, no caso de Tom mencionar, você pode retornar o contêiner mais específico, mas precisará construí-lo.

Macneil
fonte
2
Eu acho que o segundo ponto é um pouco fraco. Você não sabe o desempenho da coleção, independentemente de ser coleção ou lista - já que são apenas abstrações. A menos que você tenha uma aula final concreta, você não pode realmente dizer.
Mark H
Além disso, se tudo o que você sabe é que algo é uma coleção, você não tem idéia se pode conter duplicatas ou não. Por outro lado, uma vez que o caso em que devolver a coleção possa ser apropriado é se você tiver uma coleção que não contém duplicatas e não tiver uma ordem significativa (que seria naturalmente um conjunto), mas por algum motivo, a implementação do método de retorno usa uma lista. Você não gostaria de retornar uma lista, porque isso implica uma ordem significativa, mas não é possível devolver um conjunto sem passar pelo rigmarole de fazer um. Então você retorna uma coleção.
Tom Anderson
@ Tom: Bom ponto!
Macneil
5

Eu olhava para ele do ponto de vista oposto completo e perguntava:

Se você se deparar com um código que empregava List <> ao fazer uma revisão de código, como você determinaria se seu uso é justificado?

É muito fácil justificar isso. Você usa a lista quando precisa de alguma funcionalidade que não é oferecida por uma coleção. Se você não precisa dessa funcionalidade extra - que justificativa você tem? (E não compro "prefiro ver")

Existem muitos casos em que você usará uma coleção para fins somente leitura, preenchendo tudo de uma só vez, repetindo-o completamente - você precisa indexar manualmente a coisa?

Para dar um exemplo real. Digamos que eu execute uma consulta simples em um banco de dados. ( SELECT id,name,rep FROM people WHERE name LIKE '%squared') Pego de volta os dados relevantes, preencho objetos Person e os coloco em uma PersonList)

  • Preciso acessar por índice? - Seria sem sentido. Não há mapeamento entre índice e ID.
  • Preciso inserir em um índice? - Não, o DBMS decidirá onde colocá-lo, se eu estiver adicionando alguma coisa.

Então, que justificativa eu ​​teria para esses métodos extras? (que, de qualquer maneira, não seria implementado na minha PersonList)

Mark H
fonte
Ponto justo. Suponho que minha pergunta se refira a uma instância específica em que, ao fazer uma revisão de código, continuo vendo DAOs que retornam coleções, mas sei que essas chamadas do DAO sempre retornarão conjuntos de entidades; Minha opinião é que, nesses casos, o tipo de retorno indica exclusividade, e essas informações são úteis para quem precisa usar esse método (por exemplo, não preciso verificar se há duplicatas).
Fil
Se você consultou um banco de dados, a comparação de dois objetos resultantes com igual a () não deve ser verdadeira; portanto, você precisará de outra maneira de comparar os objetos para duplicação (por exemplo, eles têm o mesmo nome, o mesmo ID, ambos?). Você precisaria informar ao seu DAO como compará-los se for para remover duplicatas em si - mas como você é o usuário que decide se existem duplicatas - é mais fácil fazê-lo com a coleção do código de chamada. (Para evitar mais camadas de abstração para informar o DAO como executar cada cheque igualdade possível no planeta.)
Mark H
Concordamos, mas estamos usando o Hibernate e garantimos que nossas entidades implementem igual a (). Portanto, quando um DAO está retornando entidades, podemos fazer muito rapidamente o novo HashSet (). AddAll (results) e devolvê-lo ao método chamado.
Fil