C'è una buona ragione per usare l'interfaccia Collection di Java?


11

Ho sentito l'argomentazione secondo cui dovresti usare l'interfaccia più generica disponibile in modo da non essere legato a una particolare implementazione di quell'interfaccia. Questa logica si applica a interfacce come java.util.Collection ?

Preferirei vedere qualcosa di simile al seguente:

List<Foo> getFoos()

o

Set<Foo> getFoos()

invece di

Collection<Foo> getFoos()

Nell'ultimo caso, non so con quale tipo di set di dati ho a che fare, mentre nei primi due casi posso fare alcune ipotesi su ordinazione e unicità. Non java.util.Collection hanno una al di fuori utilità di essere un genitore logica per entrambi i gruppi e le liste?

Se ti imbattessi in un codice che utilizzava Collection durante una revisione del codice, come determineresti se il suo utilizzo è giustificato e quali suggerimenti daresti per la sua sostituzione con un'interfaccia più specifica?


3
Ti è capitato di notare in java.security.cert che un tipo di ritorno è Collection<List<?>>? Parla del codice horror!
Macneil,

1
@Macneil Non so a quale classe ti riferisci, ma un tale tipo di ritorno potrebbe davvero essere abbastanza sensato. In sostanza ti dice che hai una collezione (cioè che contiene un mucchio di cose che non ha un ordinamento ragionevole) di liste (cioè che contiene cose che hanno un ordinamento sensibile) di oggetti (cioè oggetti di cui non conosciamo staticamente i tipi qualunque sia la ragione). Non mi sembra irragionevole.
Zero3

Risposte:


13

Le astrazioni vivono più a lungo delle implementazioni

In generale, più astratto è il tuo design, più è probabile che rimanga utile. Pertanto, poiché Collection è più astratto delle sue interfacce secondarie, è più probabile che una progettazione API basata su Collection rimanga utile rispetto a una basata su List.

Tuttavia, il principio generale è quello di utilizzare l' astrazione più appropriata . Quindi, se la tua collezione deve supportare gli elementi ordinati, allora impegna un Elenco, se non ci devono essere duplicati, impone un Set e così via.

Una nota sul design dell'interfaccia generica

Dato che sei interessato a utilizzare l'interfaccia Collection con generics, potresti essere utile. L'efficace Java di Joshua Bloch raccomanda il seguente approccio quando si progetta un'interfaccia che si affida ai generici: produttori estendono, consumatori super

Questa è anche nota come regola PECS . In sostanza, se le raccolte generiche che producono dati vengono passate alla tua classe, la firma dovrebbe apparire così:

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

Pertanto, il tipo di input può essere E o qualsiasi sottoclasse di E (E è definita come una super e sottoclasse di se stessa nel linguaggio Java).

Al contrario, una raccolta generica che viene passata per consumare dati dovrebbe avere una firma come questa:

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

Il metodo si occuperà in modo corretto con qualsiasi superclasse di E. Nel complesso, utilizzando questo approccio renderà l'interfaccia meno sorprendente per gli utenti, perché sarete in grado di passare in Collection<Number>e Collection<Integer>e li hanno trattati in modo corretto.


6

L' Collectioninterfaccia e la forma più permissiva Collection<?>sono ottime per i parametri che accetti. Basato sull'uso nella stessa libreria Java è più comune come tipo di parametro che come tipo di ritorno.

Per i tipi di ritorno, penso che il tuo punto sia valido: se ci si aspetta che le persone accedano ad esso, dovrebbero conoscere l'ordine (nel senso di Big-O) dell'operazione eseguita. Vorrei ripetere un Collectionritorno e aggiungerlo a un'altra raccolta, ma sembrerebbe un po 'folle chiamarlo contains, senza sapere se si tratta di un'operazione O (1), O (log n) o O (n). Certo, solo perché hai a Setnon significa che sia un hashset o un set ordinato, ma a un certo punto farai ipotesi che l'interfaccia sia stata ragionevolmente implementata (e quindi dovrai andare al piano B se la tua ipotesi viene mostrato come errato).

Come menziona Tom, a volte è necessario restituire Collectiona per mantenere l'incapsulamento: non si desidera che i dettagli dell'implementazione trapelino, anche se si potrebbe restituire qualcosa di più specifico. Oppure, nel caso menzionato da Tom, potresti restituire il contenitore più specifico, ma poi dovrai costruirlo.


2
Penso che il secondo punto sia un po 'debole. Non sai come funzionerà la raccolta, indipendentemente dal fatto che sia Raccolta o Elenco, poiché sono solo astrazioni. A meno che tu non abbia una classe finale concreta, non puoi davvero dirlo.
Segna H il

Inoltre, se tutto ciò che sai è che qualcosa è una raccolta, non hai idea se può contenere duplicati o meno. Per capirlo, una volta il caso in cui la restituzione della Raccolta potrebbe essere appropriata è se si dispone di una raccolta che non contiene duplicati e non ha un ordine significativo (che sarebbe naturalmente un Set), ma dove per qualche buona ragione, l'implementazione del metodo di restituzione utilizza un elenco. Non vorrai restituire un Elenco, perché ciò implica un ordine significativo, ma non puoi restituire un Set senza passare attraverso il rigmarole di crearne uno. Quindi restituisci una collezione.
Tom Anderson,

@ Tom: buon punto!
Macneil,

5

Lo guarderei dal punto di vista completamente opposto e chiedevo:

Se ti imbattessi in un codice che utilizzava List <> durante una revisione del codice, come determineresti se il suo utilizzo è giustificato?

È abbastanza facile giustificarlo. Si utilizza l'elenco quando sono necessarie alcune funzionalità non offerte da una raccolta. Se non hai bisogno di quella funzionalità aggiuntiva, quale giustificazione hai? (E non comprerò "Preferirei vederlo")

Ci sono molti casi in cui utilizzerai una raccolta per scopi di sola lettura, popolandola tutta in una volta, ripetendola interamente - hai mai bisogno di indicizzare la cosa manualmente?

Per fare un vero esempio. Supponiamo di eseguire una semplice query su un database. ( SELECT id,name,rep FROM people WHERE name LIKE '%squared') Riprendo i dati rilevanti, popolare gli oggetti Person e li inserisco in un Elenco persone)

  • Devo accedere per indice? - Sarebbe insignificante. Non esiste alcuna mappatura tra indice e ID.
  • Devo inserire un indice? - No, il DBMS deciderà dove inserirlo, se lo sto aggiungendo.

Quindi quale giustificazione dovrei avere per quei metodi extra? (che rimarrebbe comunque non implementato nella mia lista personale)


Punto valido. Suppongo che la mia domanda si riferisca a un'istanza specifica in cui, mentre eseguo una revisione del codice, continuo a vedere DAO che restituiscono Collezioni, ma so che queste chiamate DAO restituiranno sempre insiemi di entità; la mia tesi è che in questi casi il tipo restituito indica unicità, e questa informazione è utile a chiunque debba usare quel metodo (es. non devo verificare la presenza di duplicati).
Fil

Se hai eseguito una query su un DB, il confronto di 2 oggetti risultanti con uguale a () non dovrebbe produrre affatto true - quindi avresti bisogno di un altro modo di confrontare gli oggetti per la duplicazione (ad esempio, hanno lo stesso nome, lo stesso ID, entrambi?). Dovresti dire al tuo DAO come confrontarli se si tratta di rimuovere i duplicati stessi - ma dal momento che sei l'utente che sta decidendo se esistono duplicati - è più semplice farlo con la raccolta dal codice chiamante. (Per evitare ulteriori strati di astrazione per informare il DAO come eseguire ogni possibile controllo dell'uguaglianza sul pianeta.)
Marco H,

D'accordo, ma stiamo usando Hibernate e assicuriamo che le nostre entità implementino equals (). Quindi, quando un DAO restituisce entità, possiamo fare molto rapidamente nuovo HashSet (). AddAll (risultati) e restituirlo al metodo chiamato.
Fil
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.