Y a-t-il une bonne raison d'utiliser l'interface Java Collection?

11

J'ai entendu l'argument selon lequel vous devriez utiliser l'interface la plus générique disponible afin de ne pas être lié à une implémentation particulière de cette interface. Cette logique s'applique-t-elle aux interfaces telles que java.util.Collection ?

Je préfère de loin voir quelque chose comme ceci:

List<Foo> getFoos()

ou

Set<Foo> getFoos()

au lieu de

Collection<Foo> getFoos()

Dans le dernier cas, je ne sais pas à quel type d'ensemble de données je fais face, alors que dans les deux premiers cas, je peux faire des hypothèses sur l'ordre et l'unicité. Java.util.Collection a- t -il une utilité en dehors d'être un parent logique pour les ensembles et les listes?

Si vous rencontriez du code qui employait Collection lors d'une révision de code, comment détermineriez-vous si son utilisation est justifiée et quelles suggestions feriez-vous pour son remplacement par une interface plus spécifique?

Fil
la source
3
Avez-vous remarqué dans java.security.cert qu'un type de retour est Collection<List<?>>? Parlez de coder l'horreur!
Macneil
1
@Macneil Je ne sais pas à quelle classe vous vous référez, mais un tel type de retour pourrait en effet être assez judicieux. Il vous dit essentiellement que vous avez une collection (c'est-à-dire contenant un tas de choses qui n'ont pas un ordre sensé) de listes (c'est-à-dire contenant des choses qui ont un ordre sensé) d' objets (c'est-à-dire des éléments dont nous ne connaissons pas statiquement les types quelle que soit la raison). Cela ne me semble pas déraisonnable.
Zero3

Réponses:

13

Les abstractions vivent plus longtemps que les implémentations

En général, plus votre design est abstrait, plus il est susceptible de rester utile. Ainsi, puisque Collection est plus abstrait que ses sous-interfaces, une conception d'API basée sur Collection est plus susceptible de rester utile qu'une autre basée sur List.

Cependant, le principe général est d'utiliser l' abstraction la plus appropriée . Donc, si votre collection doit prendre en charge des éléments ordonnés, puis mandater une liste, s'il n'y a pas de doublons, alors mandater un ensemble, etc.

Une note sur la conception d'interface générique

Étant donné que vous êtes intéressé à utiliser l'interface Collection avec des génériques, vous pouvez être utile ci-dessous. Java efficace par Joshua Bloch recommande l'approche suivante lors de la conception d'une interface qui s'appuiera sur des génériques: Producers Extend, Consumers Super

Ceci est également connu comme la règle PECS . Essentiellement, si des collections génériques qui produisent des données sont transmises à votre classe, la signature devrait ressembler à ceci:

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

Ainsi, le type d'entrée peut être E ou n'importe quelle sous-classe de E (E est défini comme une super et une sous-classe de lui-même dans le langage Java).

Inversement, une collection générique transmise pour consommer des données doit avoir une signature comme celle-ci:

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

La méthode correctement face à toute superclasse de E. Dans l' ensemble, en utilisant cette approche fera l' interface moins surprenante à vos utilisateurs parce que vous serez en mesure de passer Collection<Number>et Collection<Integer>et les ont traités correctement.

Gary Rowe
la source
6

L' Collectioninterface, et la forme la plus permissive Collection<?>, est idéale pour les paramètres que vous acceptez. Basé sur l' utilisation dans la bibliothèque Java elle-même, il est plus courant comme type de paramètre que comme type de retour.

Pour les types de retour, je pense que votre argument est valable: si les personnes sont censées y accéder, elles doivent connaître l'ordre (au sens Big-O) de l'opération en cours. Je voudrais itérer sur un Collectionretour et l'ajouter à une autre collection, mais il semblerait un peu fou de l'appeler contains, sans savoir s'il s'agit d'une opération O (1), O (log n) ou O (n). Bien sûr, ce Setn'est pas parce que vous avez un hashset ou un ensemble trié, mais à un moment donné, vous supposerez que l'interface a été raisonnablement implémentée (et vous devrez ensuite aller au plan B si votre hypothèse s’avère incorrect).

Comme Tom le mentionne, vous devez parfois retourner un Collectionafin de maintenir l'encapsulation: vous ne voulez pas que les détails de l'implémentation fuient, même si vous pouvez retourner quelque chose de plus spécifique. Ou, dans le cas mentionné par Tom, vous pouvez retourner le conteneur plus spécifique, mais vous devrez alors le construire.

Macneil
la source
2
Je pense que ce deuxième point est un peu faible. Vous ne savez pas comment la collection va fonctionner, que ce soit Collection ou List - car ce ne sont que des abstractions. À moins d'avoir un cours final concret, vous ne pouvez pas vraiment le dire.
Mark H
De plus, si tout ce que vous savez, c'est que quelque chose est une collection, vous ne savez pas si elle peut contenir des doublons ou non. Pour inverser la situation, une fois que le retour de Collection pourrait être approprié, c'est si vous avez une collection qui ne contient pas de doublons et n'a pas d'ordre significatif (qui serait naturellement un ensemble), mais où, pour une bonne raison, la mise en œuvre de la méthode de retour utilise une liste. Vous ne voudriez pas retourner une liste, car cela implique un ordre important, mais vous ne pouvez pas retourner un ensemble sans passer par la rigueur d'en créer un. Vous retournez donc une collection.
Tom Anderson
@Tom: Bon point!
Macneil
5

Je regarderais cela d'un point de vue complètement opposé et je demanderais:

Si vous rencontriez du code qui utilisait List <> lors d'une révision de code, comment déterminer si son utilisation est justifiée?

C'est assez facile à justifier. Vous utilisez la liste lorsque vous avez besoin d'une fonctionnalité qui n'est pas offerte par une collection. Si vous n'avez pas besoin de cette fonctionnalité supplémentaire - quelle justification avez-vous? (Et je n'achèterai pas, "je préfère le voir")

Il existe de nombreux cas où vous utiliserez une collection à des fins de lecture seule, en la remplissant à la fois, en l'itérant entièrement - avez-vous besoin d'indexer la chose manuellement?

Pour donner un vrai exemple. Supposons que j'effectue une requête simple sur une base de données. ( SELECT id,name,rep FROM people WHERE name LIKE '%squared') Je récupère les données pertinentes, remplis les objets Person et les place dans une PersonList)

  • Dois-je accéder par index? - Ça n'aurait aucun sens. Il n'y a pas de mappage entre l'index et l'ID.
  • Dois-je insérer un index? - Non, le SGBD décidera où le mettre, si j'ajoute du tout.

Alors, quelle justification aurais-je pour ces méthodes supplémentaires? (qui serait de toute façon non implémenté dans ma PersonList)

Mark H
la source
Bon point. Je suppose que ma question fait référence à un cas spécifique où, tout en faisant un examen de code, je vois toujours des DAO qui renvoient des collections, mais je sais que ces appels DAO vont toujours renvoyer des ensembles d'entités; mon argument est que dans ces cas, le type de retour indique l'unicité, et cette information est utile à quiconque doit utiliser cette méthode (par exemple, je n'ai pas à vérifier les doublons).
Fil
Si vous avez interrogé une base de données, la comparaison de 2 objets résultants avec equals () ne devrait pas produire du tout vrai - vous auriez donc besoin d'une autre façon de comparer les objets pour la duplication (par exemple, ont-ils le même nom, le même ID, tous les deux?). Vous devez dire à votre DAO comment les comparer pour supprimer les doublons lui-même - mais puisque vous êtes l'utilisateur qui décide s'il existe des doublons - il est plus facile de le faire simplement avec la collection du code appelant. (Pour éviter plus de couches d'abstraction pour informer le DAO comment effectuer toutes les vérifications d'égalité possibles sur la planète.)
Mark H
D'accord, mais nous utilisons Hibernate et nous assurons que nos entités implémentent equals (). Donc, quand un DAO retourne des entités, nous pouvons très rapidement faire un nouveau HashSet (). AddAll (results) et le retourner à la méthode appelée.
Fil