Perché Java Map non estende la raccolta?

Sono stato sorpreso dal fatto che Map Non è una Collection .

Ho pensato che sarebbe stato molto sensato se fosse stato dichiarato come tale:

 public interface Map extends Collection<Map.Entry> 

Dopo tutto, una Map è una raccolta di Map.Entry , non è vero?

Quindi c’è una buona ragione per cui non è implementato come tale?


Grazie a Cletus per una risposta molto autorevole, ma mi chiedo ancora perché, se riesci già a visualizzare una Map come Set<Map.Entries> (tramite entrySet() ), non lo fa semplicemente estendere quell’interfaccia.

Se una Map è una Collection , quali sono gli elementi? L’unica risposta ragionevole è “Coppie valore-chiave”

Esattamente, la interface Map extends Set<Map.Entry> sarebbe fantastico!

ma ciò fornisce un’astrazione di Map molto limitata (e non particolarmente utile).

Ma se questo è il caso, allora perché entrySet è specificato dall’interfaccia? Deve essere utile in qualche modo (e penso che sia facile argomentare per quella posizione!).

Non è ansible chiedere a quale valore viene assegnata una determinata chiave, né si può cancellare la voce per una determinata chiave senza sapere a quale valore si associ.

Non sto dicendo che questo è tutto ciò che c’è da fare per Map ! Può e dovrebbe mantenere tutti gli altri metodi (eccetto entrySet , che è ridondante ora)!

Dalle domande frequenti sulla progettazione dell’API Java Collections :

Perché Map non estrae la raccolta?

Questo era di design. Riteniamo che i mapping non siano raccolte e le raccolte non siano mappature. Pertanto, ha poco senso che Map estenda l’interfaccia Collection (o viceversa).

Se una mappa è una collezione, quali sono gli elementi? L’unica risposta ragionevole è “coppie chiave-valore”, ma ciò fornisce un’astrazione di mappa molto limitata (e non particolarmente utile). Non è ansible chiedere a quale valore viene assegnata una determinata chiave, né si può cancellare la voce per una determinata chiave senza sapere a quale valore si associ.

È ansible effettuare la raccolta per estendere la mappa, ma ciò solleva la domanda: quali sono le chiavi? Non c’è una risposta veramente soddisfacente, e forzare uno porta a un’interfaccia innaturale.

Le mappe possono essere visualizzate come raccolte (di chiavi, valori o coppie) e questo fatto si riflette nelle tre “operazioni di visualizzazione della raccolta” su Maps (keySet, entrySet e valori). Mentre è, in linea di principio, ansible visualizzare una Lista come una mappa che associa gli indici agli elementi, questa ha la ctriggers proprietà che l’eliminazione di un elemento dalla Lista cambi la Chiave associata ad ogni elemento prima dell’elemento eliminato. Ecco perché non abbiamo un’operazione di visualizzazione della mappa sugli elenchi.

Aggiornamento: penso che la citazione risponda alla maggior parte delle domande. Vale la pena sottolineare la parte relativa a una raccolta di voci che non è un’astrazione particolarmente utile. Per esempio:

 Set> 

consentirebbe:

 set.add(entry("hello", "world")); set.add(entry("hello", "world 2"); 

(presupponendo un metodo entry() che crea un’istanza Map.Entry )

Map richiedono chiavi univoche, quindi questo violerebbe questo. O se imponi chiavi univoche su un Set di voci, non è in realtà un Set in senso generale. È un Set con ulteriori restrizioni.

Probabilmente si potrebbe dire che la relazione equals() / hashCode() per Map.Entry era puramente sulla chiave, ma anche questo ha problemi. Ancora più importante, aggiunge davvero qualche valore? Potresti scoprire che questa astrazione si rompe quando inizi a guardare i casi d’angolo.

Vale la pena notare che HashSet è effettivamente implementato come HashMap , non viceversa. Questo è puramente un dettaglio di implementazione, ma è comunque interessante.

Il motivo principale per cui entrySet() esiste è di semplificare l’attraversamento in modo da non dover attraversare le chiavi e quindi eseguire una ricerca della chiave. Non prendere come prova prima facie che una Map dovrebbe essere un Set di voci (imho).

Credo che il motivo sia soggettivo.

In C #, penso che Dictionary estenda o almeno implementa una raccolta:

 public class Dictionary : IDictionary, ICollection>, IEnumerable>, IDictionary, ICollection, IEnumerable, ISerializable, IDeserializationCallback 

Anche in Pharo Smalltak:

 Collection subclass: #Set Set subclass: #Dictionary 

Ma c’è un’asimmetria con alcuni metodi. Ad esempio, collect: prenderà associazione (l’equivalente di una voce), mentre do: prendere i valori. Forniscono un altro metodo keysAndValuesDo: per iterare il dizionario per voce. Add: accetta un’associazione, ma remove: è stata “soppressa”:

 remove: anObject self shouldNotImplement 

Quindi è definitivamente fattibile, ma porta ad altri problemi riguardanti la gerarchia delle classi.

Ciò che è meglio è soggettivo.

Mentre hai ottenuto un numero di risposte che coprono la tua domanda in modo abbastanza diretto, penso che potrebbe essere utile fare un passo indietro e guardare la domanda un po ‘più in generale. Cioè, non guardare in modo specifico a come la libreria Java capita di essere scritta, e guardare perché è scritta in quel modo.

Il problema qui è che l’ereditarietà modella solo un tipo di comunanza. Se scegli due cose che sembrano entrambe “da collezione”, probabilmente puoi scegliere tra 8 o 10 cose che hanno in comune. Se scegli un altro paio di cose tipo “collezione”, avranno anche 8 o 10 cose in comune – ma non saranno le stesse 8 o 10 cose della prima coppia.

Se guardi una dozzina di “cose ​​simili a raccolte”, praticamente ognuna di esse avrà probabilmente qualcosa come 8 o 10 caratteristiche in comune con almeno un’altra, ma se guardi a ciò che è condiviso su ognuna di esse di loro, non ti rimane praticamente nulla.

Questa è una situazione in cui l’ereditarietà (in particolare l’ereditarietà singola) non si modella bene. Non esiste una linea di separazione pulita tra quali di questi sono realmente raccolte e quali no – ma se vuoi definire una significativa raccolta di class, sei bloccato a lasciarne alcuni. Se ne esci solo alcuni, la tua class Collection sarà in grado di fornire un’interfaccia piuttosto sparsa. Se esci di più, sarai in grado di dargli un’interfaccia più ricca.

Alcuni hanno anche la possibilità di dire fondamentalmente: “questo tipo di raccolta supporta l’operazione X, ma non ti è permesso utilizzarlo, derivando da una class base che definisce X, ma tentando di usare la class derivata ‘X non riesce (ad es. , lanciando un’eccezione).

Questo lascia ancora un problema: quasi a prescindere da ciò che tralascia e che metti in campo, dovrai tracciare una linea dura tra ciò che le classi sono e quelle che sono fuori. Non importa dove si disegna quella linea, ti verrà lasciata una divisione chiara, piuttosto artificiale, tra alcune cose che sono abbastanza simili.

La risposta del cleto è buona, ma voglio aggiungere un approccio semantico. Combinare entrambi non ha senso, pensate al caso in cui aggiungete una coppia chiave-valore tramite l’interfaccia di raccolta e la chiave esiste già. L’interfaccia Mappa consente solo un valore associato alla chiave. Ma se si rimuove automaticamente la voce esistente con la stessa chiave, la collezione ha dopo l’aggiunta la stessa dimensione di prima – molto inaspettata per una collezione.

Le raccolte Java sono rotte. C’è un’interfaccia mancante, quella di Relazione. Quindi, la mappa estende la relazione estende Set. Le relazioni (chiamate anche multi-mappe) hanno coppie nome-valore univoche. Le mappe (dette anche “Funzioni”), hanno nomi univoci (o chiavi) che ovviamente mappano ai valori. Le sequenze estendono le mappe (dove ogni chiave è un intero> 0). Sacchetti (o multi-set) estendono Mappe (dove ogni chiave è un elemento e ogni valore è il numero di volte che l’elemento appare nel sacchetto).

Questa struttura consentirebbe l’intersezione, l’unione, ecc. Di una serie di “collezioni”. Quindi, la gerarchia dovrebbe essere:

  Set | Relation | Map / \ Bag Sequence 

Sun / Oracle / Java ppl: si prega di farlo subito la prossima volta. Grazie.

Se osservi la rispettiva struttura dei dati puoi facilmente intuire perché Map non fa parte della Collection . Ogni Collection memorizza un singolo valore dove, in una Map memorizzata una coppia chiave-valore. Quindi i metodi nell’interfaccia di Collection sono compatibili con l’interfaccia Map . Ad esempio in Collection abbiamo add(Object o) . Quale sarebbe tale implementazione in Map . Non ha senso avere un tale metodo in Map . Invece abbiamo un metodo put(key,value) in Map .

Lo stesso argomento vale per i addAll() , remove() e removeAll() . Quindi la ragione principale è la differenza nel modo in cui i dati vengono archiviati in Map e Collection . Inoltre, se si richiama un’interfaccia Iterable implementata nell’interfaccia Collection , qualsiasi interfaccia con il metodo .iterator() deve restituire un iteratore che deve consentirci di scorrere i valori memorizzati nella Collection . Ora, cosa restituirebbe un tale metodo per una Map ? Key iterator o Value iterator? Anche questo non ha senso.

Ci sono modi in cui possiamo iterare su chiavi e valori memorizza in una Map ed è così che fa parte del framework Collection .

Esattamente, la interface Map extends Set

>

sarebbe fantastico!

In realtà, se fosse implements Map, Set

>

, quindi tendo ad essere d’accordo .. Sembra naturale. Ma non funziona molto bene, giusto? Diciamo che abbiamo HashMap implements Map, Set

, LinkedHashMap implements Map, Set

ecc … che è tutto buono, ma se si avesse entrySet() , nessuno dimenticherà di implementare quel metodo, e si può essere sicuri che è ansible ottenere entrySet per qualsiasi mappa, mentre non lo è se si spera che l’implementatore abbia implementato entrambe le interfacce .. .

La ragione per cui non voglio avere l’ interface Map extends Set

>

è semplicemente, perché ci saranno più metodi. E dopo tutto, sono cose diverse, giusto? Anche molto praticamente, se colpisco la map. in IDE, non voglio vedere .remove(Object obj) , e .remove(Map.Entry entry) perché non riesco a .remove(Map.Entry entry) hit ctrl+space, r, return ed essere fatto con esso .

Una mappa ha tre raccolte diverse: un set di chiavi, un set di voci e un insieme di coppie di valori-chiave .
Puoi ottenere uno qualsiasi dei tre con una singola chiamata al metodo.

Solo l’insieme delle coppie chiave-valore potrebbe ragionevolmente essere definito come “la raccolta”, ed è piuttosto innaturale.
Si potrebbe dire che una mappa è una collezione (in effetti, un set) di Map.Entry, ma è solo una specie di imbarazzante e strano.
È ancora meno azzeccato identificare il set con il suo set di valori o il suo set di chiavi. Quindi, piuttosto che essere confusi, hanno reso tutti e tre facilmente e ugualmente accessibili.

Map non dovrebbe estendere Set

>

poiché:

  • Non è ansible aggiungere diversi Map.Entry con la stessa chiave per la stessa Map , ma
  • Puoi aggiungere diversi Map.Entry con la stessa chiave allo stesso Set

    .

Dritto e semplice. Collection è un’interfaccia che si aspetta solo un object, dove Map richiede Two.

 Collection(Object o); Map