Perché Java Map non estende la raccolta?


146

Sono stato sorpreso dal fatto che Map<?,?>non è un Collection<?>.

Ho pensato che avrebbe avuto molto senso se fosse stato dichiarato come tale:

public interface Map<K,V> extends Collection<Map.Entry<K,V>>

Dopo tutto, a Map<K,V>è una raccolta di Map.Entry<K,V>, non è vero?

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


Grazie a Cletus per la risposta più autorevole, ma mi chiedo ancora perché, se puoi già visualizzare un Map<K,V>as Set<Map.Entries<K,V>>(via entrySet()), non si limita a estendere quell'interfaccia.

Se a Mapè a Collection, quali sono gli elementi? L'unica risposta ragionevole è "Coppie chiave-valore"

Esatto, interface Map<K,V> extends Set<Map.Entry<K,V>>sarebbe fantastico!

ma ciò fornisce Mapun'astrazione molto limitata (e non particolarmente utile) .

Ma se è così, allora perché lo è entrySet specificato dall'interfaccia? Deve essere utile in qualche modo (e penso che sia facile discutere per quella posizione!).

Non puoi chiedere a quale valore viene mappata una determinata chiave, né puoi eliminare la voce per una determinata chiave senza sapere a quale valore è mappata.

Non sto dicendo che è tutto quello che c'è da fare Map! Può e dovrebbe mantenere tutti gli altri metodi (tranne entrySet, che è ridondante ora)!


1
evento Set <Map.Entry <K, V >> attualmente
Maurice Perry

3
Semplicemente no. Si basa su un'opinione (come descritto nelle Domande frequenti sulla progettazione), piuttosto che essere una conclusione logica. Per un approccio alternativo, guarda il design dei contenitori in C ++ STL ( sgi.com/tech/stl/table_of_contents.html ) che si basa sull'analisi approfondita e brillante di Stepanov.
Beldaz,

Risposte:


123

Dalle domande frequenti sulla progettazione dell'API delle raccolte Java :

Perché Map non estende la raccolta?

Questo è stato progettato. Riteniamo che le mappature non siano raccolte e che le raccolte non siano mappature. Pertanto, ha poco senso per Map estendere l'interfaccia Collection (o viceversa).

Se una mappa è una raccolta, quali sono gli elementi? L'unica risposta ragionevole è "coppie chiave-valore", ma ciò fornisce un'astrazione della Mappa molto limitata (e non particolarmente utile). Non puoi chiedere a quale valore viene mappata una determinata chiave, né puoi eliminare la voce per una determinata chiave senza sapere a quale valore è mappata.

La raccolta potrebbe essere fatta per estendere Map, ma questo solleva la domanda: quali sono le chiavi? Non esiste una risposta davvero soddisfacente e forzarne una 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 Mappe (keySet, entrySet e valori). Mentre, in linea di principio, è possibile visualizzare un Elenco come Mappa che mappa gli indici sugli elementi, questa ha la cattiva proprietà che l'eliminazione di un elemento dall'Elenco modifica la Chiave associata a ogni elemento prima dell'elemento eliminato. Ecco perché non abbiamo un'operazione di visualizzazione della mappa negli elenchi.

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

Set<Map.Entry<String,String>>

consentirebbe:

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

(presupponendo un entry()metodo che crea Map.Entryun'istanza)

Maprichiedono chiavi univoche quindi ciò violerebbe questo. O se imponi chiavi univoche su una Setdelle voci, non è davvero una Setin senso generale. È un Setcon ulteriori restrizioni.

Probabilmente si potrebbe dire che la relazione equals()/ era puramente fondamentale, ma anche questo ha dei problemi. Ancora più importante, aggiunge davvero qualche valore? Potresti scoprire che questa astrazione si interrompe una volta che inizi a guardare i casi angolari.hashCode()Map.Entry

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

Il motivo principale per entrySet()esistere è semplificare l'attraversamento in modo da non dover attraversare le chiavi e quindi cercare una chiave. Non prenderlo come prova prima facie che a Mapdovrebbe essere una Setdelle voci (imho).


1
L'aggiornamento è molto convincente, ma in effetti la vista Set restituita entrySet()supporta remove, mentre non supporta add(probabilmente genera UnsupportedException). Quindi vedo il tuo punto, ma poi di nuovo, vedo anche il punto del PO .. (La mia opinione è come affermato nella mia risposta ..)
Enno Shioji

1
Zwei solleva un buon punto. Tutte queste complicazioni dovute addpossono essere gestite proprio come fa la entrySet()vista: consentire alcune operazioni e non consentire ad altre. Una risposta naturale, ovviamente, è "Che tipo di Setnon supporta add?" - bene, se tale comportamento è accettabile per entrySet(), allora perché non lo è per this? Detto questo, per lo più sono già convinto del perché questa non sia una grande idea come una volta pensavo, ma penso ancora che sia degno di ulteriore dibattito, se non altro per arricchire la mia comprensione di ciò che rende un buon design API / OOP.
poligenelubrificanti

3
Il primo paragrafo della citazione dalle FAQ è divertente: "Sentiamo ... quindi ... ha poco senso". Non sento che "sentire" e "senso" formino una conclusione; o)
Peter Wippermann,

La raccolta potrebbe essere fatta per estendere la mappa ? Non sono sicuro, perché l'autore pensa per Collectionesteso Map?
Scambio eccessivo

11

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 per guardare in modo specifico al modo in cui viene scritta la libreria Java, e per capire perché è scritta in quel modo.

Il problema qui è che l'eredità modella solo un tipo di comunanza. Se scegli due cose che sembrano entrambe "simili a raccolte", probabilmente puoi scegliere 8 o 10 cose che hanno in comune. Se scegli una coppia diversa di cose "simili a raccolte", avranno anche 8 o 10 cose in comune, ma non saranno le stesse 8 o 10 cose della prima coppia.

Se si guarda a una decina di diverso "raccolta-like" le cose, praticamente ognuno di loro avrà probabilmente qualcosa come 8 o 10 caratteristiche in comune con almeno un altro - ma se si guarda a ciò che è condiviso tra tutti uno di loro, ti rimane praticamente nulla.

Questa è una situazione in cui l'ereditarietà (in particolare quella singola) non modella bene. Non esiste una netta linea di demarcazione tra quali di queste sono realmente raccolte e quali non lo sono - ma se vuoi definire una classe di raccolta significativa, sei bloccato a lasciarne fuori alcune. Se ne lasci solo alcuni, la tua classe Collection sarà solo in grado di fornire un'interfaccia piuttosto scarsa. Se lasci di più, sarai in grado di dargli un'interfaccia più ricca.

Alcuni hanno anche la possibilità di dire sostanzialmente: "questo tipo di raccolta supporta l'operazione X, ma non ti è consentito usarla, derivando da una classe base che definisce X, ma il tentativo di utilizzare la classe derivata 'X non riesce (ad es. , generando un'eccezione).

Ciò lascia ancora un problema: quasi a prescindere da ciò che si lascia fuori e da quale si inserisce, si dovrà tracciare una linea dura tra ciò che le classi sono dentro e ciò che sono fuori. Indipendentemente da dove tracciate quella linea, rimarrete con una chiara, piuttosto artificiale, divisione tra alcune cose che sono abbastanza simili.


10

Immagino che il perché sia soggettivo.

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

public class Dictionary<TKey, TValue> : IDictionary<TKey, TValue>, 
    ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, 
    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:will accetta l'associazione (l'equivalente di una voce), mentre accetta do:i valori. Forniscono un altro metodo keysAndValuesDo:per iterare il dizionario per voce. Add:prende un'associazione, ma remove:è stato "soppresso":

remove: anObject
self shouldNotImplement 

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

Ciò che è meglio è soggettivo.


3

La risposta di Cletus è buona, ma voglio aggiungere un approccio semantico. Per combinare entrambi non ha senso, pensa al caso in cui aggiungi una coppia chiave-valore tramite l'interfaccia di raccolta e la chiave esiste già. L'interfaccia della mappa consente un solo valore associato alla chiave. Ma se rimuovi automaticamente la voce esistente con la stessa chiave, la raccolta ha dopo l'aggiunta delle stesse dimensioni di prima - molto imprevista per una raccolta.


4
Ecco come Setfunziona, e Set non implementare Collection.
Lii

2

Le raccolte Java sono interrotte. C'è un'interfaccia mancante, quella di Relation. Quindi, la mappa si estende La relazione estende il set. Le relazioni (chiamate anche multi-mappe) hanno coppie nome-valore univoche. Le mappe (dette anche "Funzioni") hanno nomi (o chiavi) univoci che ovviamente mappano su valori. Le sequenze estendono le mappe (dove ogni tasto è un numero intero> 0). I sacchetti (o più set) estendono le mappe (in cui ogni chiave è un elemento e ogni valore è il numero di volte in cui l'elemento viene visualizzato nel sacchetto).

Questa struttura consentirebbe l'intersezione, l'unione ecc. Di una serie di "raccolte". Quindi, la gerarchia dovrebbe essere:

                                Set

                                 |

                              Relation

                                 |

                                Map

                                / \

                             Bag Sequence

Sun / Oracle / Java ppl - per favore, fallo la prossima volta. Grazie.


4
Mi piacerebbe vedere le API per queste interfacce immaginarie dettagliate. Non sono sicuro che mi piacerebbe una sequenza (aka Lista) in cui la chiave era un numero intero; sembra un grande successo di performance.
Lawrence Dol,

Esatto, una sequenza è un elenco, quindi Sequence estende l'elenco. Non so se puoi accedervi, ma potrebbe essere interessante portal.acm.org/citation.cfm?id=1838687.1838705
xagyg

@SoftwareMonkey ecco un altro link. L'ultimo collegamento è il collegamento al javadoc dell'API. zedlib.sourceforge.net
xagyg

@SoftwareMonkey Ammetto spudoratamente che il design è la mia principale preoccupazione qui. Suppongo che i guru di Oracle / Sun possano ottimizzarlo.
xagyg,

Tendo a non essere d'accordo. Come può sostenere che "Map is-a Set" se non puoi sempre aggiungere elementi non appartenenti al dominio al tuo set (come nell'esempio nella risposta di @cletus).
einpoklum,

1

Se osservi la rispettiva struttura dei dati, puoi facilmente indovinare perché Mapnon fa parte di Collection. Ciascuno Collectionmemorizza un singolo valore in cui viene Mapmemorizzata una coppia chiave-valore. Quindi i metodi Collectionnell'interfaccia sono incompatibili per l' Mapinterfaccia. Ad esempio in Collectionabbiamo add(Object o). Quale sarebbe tale implementazione in Map. Non ha senso avere un tale metodo Map. Invece abbiamo un put(key,value)metodo in Map.

Lo stesso ragionamento vale per addAll(), remove()e removeAll()metodi. Quindi il motivo principale è la differenza nel modo in cui i dati sono archiviati Mape Collection. Inoltre, se si richiama l' Collectioninterfaccia implementata Iterabledall'interfaccia, ovvero qualsiasi interfaccia con .iterator()metodo dovrebbe restituire un iteratore che deve consentirci di scorrere i valori memorizzati in Collection. Ora cosa restituirebbe questo metodo per un Map? Key iterator o un valore iteratore? Neanche questo ha senso.

Ci sono modi in cui possiamo iterare su chiavi e valori store in a Maped è così che fa parte del Collectionframework.


There are ways in which we can iterate over keys and values stores in a Map and that is how it is a part of Collection framework.Puoi mostrare un esempio di codice per questo?
RamenChef,

Questo è stato spiegato molto bene. Ora capisco. Grazie!
Emir Memic,

Un'implementazione di add(Object o)sarebbe aggiungere un Entry<ClassA, ClassB>oggetto. A Mappuò essere considerato unCollection of Tuples
LIvanov il

0

Esatto, interface Map<K,V> extends Set<Map.Entry<K,V>>sarebbe fantastico!

In realtà, se lo fosse implements Map<K,V>, Set<Map.Entry<K,V>>, allora tendo ad essere d'accordo .. Sembra persino naturale. Ma questo non funziona molto bene, giusto? Diciamo che abbiamo HashMap implements Map<K,V>, Set<Map.Entry<K,V>, LinkedHashMap implements Map<K,V>, Set<Map.Entry<K,V>ecc ... va bene, ma se lo hai fatto entrySet(), nessuno dimenticherà di implementare quel metodo, e puoi essere sicuro di poter ottenere entrySet per qualsiasi mappa, mentre non lo sei se speri che l'implementatore ha implementato entrambe le interfacce ...

Il motivo che non voglio avere interface Map<K,V> extends Set<Map.Entry<K,V>>è semplicemente, perché ci saranno più metodi. E dopo tutto, sono cose diverse, giusto? Inoltre, in pratica, se colpisco map.in IDE, non voglio vedere .remove(Object obj), e .remove(Map.Entry<K,V> entry)perché non posso farlo hit ctrl+space, r, returne averlo finito.


Mi sembra che se si potesse affermare, semanticamente, che "Una mappa è un set su Map.Entry", allora ci si preoccuperebbe di implementare il metodo pertinente; e se uno non può fare questa affermazione, allora non lo farebbe - cioè dovrebbe essere per il disturbo.
einpoklum,

0

Map<K,V>non dovrebbe estendersi Set<Map.Entry<K,V>>poiché:

  • Non puoi aggiungere diversi Map.Entrys con la stessa chiave allo stesso Map, ma
  • È possibile aggiungere diversi Map.Entrys con la stessa chiave per la stessa Set<Map.Entry>.

... questa è un'affermazione sintetica del fantasma della seconda parte della risposta di @cletus.
einpoklum,

-2

Dritto e semplice. Collection è un'interfaccia che prevede solo un oggetto, mentre Map richiede due.

Collection(Object o);
Map<Object,Object>

Bella Trinad, le tue risposte sono sempre semplici e brevi.
Sairam,
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.