Perché java.util.Set non ha get (int index)?


237

Sono sicuro che ci sia una buona ragione, ma qualcuno potrebbe spiegare perché java.util.Setmanca l' interfaccia get(int Index)o un get()metodo simile ?

Sembra che i set siano perfetti per mettere le cose, ma non riesco a trovare un modo elegante per recuperare un singolo oggetto da esso.

Se so di volere il primo oggetto, posso usarlo set.iterator().next(), ma per il resto sembra che debba eseguire il cast su un array per recuperare un elemento in un indice specifico?

Quali sono i modi appropriati per recuperare i dati da un set? (diverso dall'uso di un iteratore)

Sono sicuro che il fatto che sia escluso dall'API significa che c'è una buona ragione per non farlo - qualcuno potrebbe illuminarmi, per favore?

EDIT: Alcune risposte estremamente grandi qui, e alcuni dicono "più contesto". Lo scenario specifico era un test dbUnit, in cui potevo ragionevolmente affermare che il set restituito da una query aveva solo 1 articolo e stavo cercando di accedere a quell'elemento.

Tuttavia, la domanda è più valida senza lo scenario, poiché rimane più focalizzata:

Qual è la differenza tra set e list .

Grazie a tutti per le fantastiche risposte qui sotto.


1
Perché dovresti ottenere un elemento da un set per indice? Stai cercando di usare un set come un array ordinato?
MSN

L'istanza particolare qui è un test dbUnit contro un set restituito da una chiamata di ibernazione. Nel mio test, è ragionevole presumere (perché lo asserisco) che l'oggetto restituito sia in un ordine specifico, a causa del mio IDataSet che ho usato per configurarlo. È un caso non tipico, ma porta alla mia curiosità sull'API.
Marty Pitt,

1
L'aggiunta di elementi in un ordine specifico non significa che rimarranno tali, a meno che non si stia utilizzando un'implementazione Set personalizzata.
Michael Myers

1
"Se so che voglio il primo oggetto, posso usare set.iterator (). Next ()" - Questa riga non ha davvero senso. Stai davvero dicendo "Se so che voglio il primo oggetto, secondo la definizione di implementazione del primo oggetto, allora posso ...". Set stesso non è ordinato, quindi l'accesso indicizzato non ha senso. Ora, se ci fosse un ArrayListSet, avrebbe più senso (basta lanciare su "Elenco" ed essere felice). Forse potresti dare più contesto alla domanda?
jsight,

Il set non è in ordine! Alcune implementazioni sono, ma alcune implementazioni sono esplicitamente ordinate in un modo particolare.
reinierpost,

Risposte:


176

Perché i set non hanno ordini. Alcune implementazioni lo fanno (in particolare quelle che implementano l' java.util.SortedSetinterfaccia), ma questa non è una proprietà generale degli insiemi.

Se stai cercando di usare i set in questo modo, dovresti considerare invece di utilizzare un elenco.


10
@matt b: No, penso che dovrebbe considerarlo. Pensare è buono ;)
Michael Myers

10
Consideralo, poi fallo.
Joe Phillips,

21
"Considerare" è il fraseggio corretto. Esistono due possibili problemi (a) Sta usando un set quando dovrebbe usare qualcos'altro, oppure (b) Sta cercando di fare cose con Set che non supportano ma che potrebbe fare diversamente. È bene considerare quale di questi è il caso.
kenj0418,

6
Potrebbe essere la risposta più semplice è utilizzare un set ordinato. (Suppongo che l'unicità abbia avuto un ruolo nella scelta del set). Ma ho una domanda, dato che SortedSet è stato ordinato, perché non esiste un metodo get nell'API.
uncaught_exceptions,

5
@HDave: No, il fatto che più implementazioni di una struttura di dati condividano una proprietà non la rende una proprietà della stessa struttura di dati. Due delle tre implementazioni comunemente utilizzate di List (ArrayList e Vector) sono ad accesso casuale, ma ciò non rende l'accesso casuale una proprietà di Elenchi.
Michael Myers

74

In realtà questa è una domanda ricorrente quando si scrivono applicazioni JavaEE che usano la mappatura relazionale di oggetti (ad esempio con Hibernate); e da tutte le persone che hanno risposto qui, Andreas Petersson è l'unico che ha capito il vero problema e ha offerto la risposta corretta ad esso: Java manca a UniqueList! (oppure puoi anche chiamarlo OrderedSet o IndexedSet).

Maxwing ha menzionato questo caso d'uso (in cui sono necessari dati ordinati E unici) e ha suggerito il SortedSet, ma non è questo ciò di cui Marty Pitt ha veramente bisogno.

Questo "IndexedSet" NON è lo stesso di un SortedSet - in un SortedSet gli elementi vengono ordinati utilizzando un comparatore (o il loro ordinamento "naturale").

Ma invece è più vicino a un LinkedHashSet (che anche altri hanno suggerito), o ancora di più a un (anche inesistente) "ArrayListSet", perché garantisce che gli elementi vengano restituiti nello stesso ordine in cui sono stati inseriti.

Ma LinkedHashSet è un'implementazione, non un'interfaccia! Ciò che serve è un'interfaccia IndexedSet (o ListSet, o OrderedSet o UniqueList)! Ciò consentirà al programmatore di specificare che ha bisogno di una raccolta di elementi che hanno un ordine specifico e senza duplicati, e quindi istanziarlo con qualsiasi implementazione (ad esempio un'implementazione fornita da Hibernate).

Dato che JDK è open-source, forse questa interfaccia sarà finalmente inclusa in Java 7 ...


3
Ottima risposta fino in fondo, ma cosa facciamo nel frattempo?
HDave

certo che lo e. ho usato list come manytomany e onetomany ORM in ibernazione prima. ho riscontrato un problema (o un difetto) quando una query di join sinistro che coinvolge più di 3 entità correlate, è stata lanciata un'eccezione. guarda qui per maggiori dettagli ( jroller.com/eyallupu/entry/… ). per aggirare questo problema, è necessario utilizzare set as ORM mapping collection. ma onestamente dire, set non è conveniente per accedere alla programmazione e anche quando è necessaria una raccolta ordini. ciò di cui abbiamo veramente bisogno è "indicizzati" come quello che ha detto Sorin Postelnicu,
ORDINANTE

2
Le collezioni Apache Commons hanno ListOrderedSetciò di cui l'OP aveva bisogno 7 anni fa (e di cui avevo bisogno oggi).
Paul,

@Paul: Questo è davvero qualcosa che sembra davvero buono. Purtroppo ha ancora 3 svantaggi: 1) È una classe, non un'interfaccia. 2) Non è nel JDK. 3) Non è ciò che le query Hibernate stanno restituendo.
Sorin Postelnicu,

Sì, ma a parte questi 3 svantaggi principali è perfetto! :) Con il senno di poi avrei dovuto postare il mio commento alla domanda e non la tua risposta - ho cancellato What is needed is an IndexedSet (or ListSet, or OrderedSet, or UniqueList)...e ignorato ...interface. Mi dispiace per quello!
Paul,

29

Basta aggiungere un punto che non è stato menzionato nella risposta di mmyers .

Se so di volere il primo elemento, posso usare set.iterator (). Next (), ma per il resto sembra che debba eseguire il cast su un array per recuperare un elemento in un indice specifico?

Quali sono i modi appropriati per recuperare i dati da un set? (diverso dall'uso di un iteratore)

Dovresti anche familiarizzare con l' SortedSetinterfaccia (la cui implementazione più comune è TreeSet).

Un SortedSet è un Set (ovvero gli elementi sono univoci) che viene mantenuto ordinato dall'ordinamento naturale degli elementi o utilizzando alcuni Comparator. Puoi accedere facilmente al primo e all'ultimo elemento usando first()e last()metodi. A SortedSetè utile di tanto in tanto, quando è necessario conservare la propria raccolta sia duplicata che ordinata in un certo modo.

Modifica : se hai bisogno di un set i cui elementi sono tenuti in ordine di inserimento (proprio come un elenco), dai un'occhiata LinkedHashSet.


Mi piace LinkedHashSet me stesso. Ma sì, è bene menzionarlo. +1
Michael Myers

Grazie, ho modificato leggermente la risposta. (Sembra che alcuni aspetti di TreeSet siano confusi con quelli di LinkedHashSet.)
Jonik

25

Questo tipo di domanda porta alla domanda su quando utilizzare un set e quando utilizzare un elenco. Di solito, il consiglio è:

  1. Se hai bisogno di dati ordinati, usa un Elenco
  2. Se hai bisogno di dati univoci, usa un set
  3. Se sono necessari entrambi, utilizzare: un SortedSet (per i dati ordinati dal comparatore) o un OrderedSet / UniqueList (per i dati ordinati per inserimento). Sfortunatamente l'API Java non ha ancora OrderedSet / UniqueList.

Un quarto caso che appare spesso è che non è necessario nessuno dei due. In questo caso vedi alcuni programmatori che vanno con le liste e alcuni con i set. Personalmente trovo molto dannoso vedere il set come un elenco senza ordinare - perché è davvero un'altra bestia. A meno che tu non abbia bisogno di cose come impostare l'unicità o impostare l'uguaglianza, favorisci sempre le liste.


2
se non si è specifici, accettare la raccolta <T> o persino Iterable <T> e inizializzare come elenco.
Andreas Petersson,

Questa sarebbe una borsa o multiset. Ma Java non supporta quelli; dicono che dovresti semplicemente usare Collection <T> direttamente.
Lumaca meccanica

4. hai bisogno di dati non univoci e non ti importa dell'ordine. NON PUOI usare un set. Una lista, borsa o multiset funzionerà.
Andrew Gallasch,

17

Non sono sicuro che qualcuno l'abbia spiegato esattamente in questo modo, ma è necessario comprendere quanto segue:

Non esiste un "primo" elemento in un set.

Perché, come altri hanno già detto, i set non hanno ordini. Un set è un concetto matematico che specificamente non include l'ordinamento.

Ovviamente, il tuo computer non può davvero tenere un elenco di cose che non sono state ordinate in memoria. Deve avere degli ordini. Internamente è un array o un elenco collegato o qualcosa del genere. Ma non sai davvero di cosa si tratta, e non ha davvero un primo elemento; l'elemento che esce "primo" viene fuori per caso e potrebbe non essere il primo la prossima volta. Anche se hai preso provvedimenti per "garantire" un primo elemento particolare, viene comunque fuori per caso, perché ti è capitato di farlo bene per una particolare implementazione di un Set; un'implementazione diversa potrebbe non funzionare in questo modo con quello che hai fatto. E, in effetti, potresti non conoscere l'implementazione che stai usando così come pensi di fare.

Le persone si imbattono in questo ALL. IL. TEMPO. con i sistemi RDBMS e non capisco. Una query RDBMS restituisce un set di record. Questo è lo stesso tipo di set della matematica: una raccolta non ordinata di articoli, solo in questo caso gli articoli sono record. Un risultato di query RDBMS non ha alcun ordine garantito a meno che non si usi la clausola ORDER BY, ma tutte le volte che le persone lo credono e quindi si inciampano un giorno quando la forma dei loro dati o codice cambia leggermente e fa funzionare Query Optimizer per funzionare in un modo diverso e improvvisamente i risultati non escono nell'ordine previsto. Queste sono in genere le persone che non hanno prestato attenzione alla classe del database (o durante la lettura della documentazione o delle esercitazioni) quando gli è stato spiegato, in anticipo, che i risultati della query non hanno un ordinamento garantito.


Eh, e ovviamente l'ordinamento di solito cambia subito dopo che il codice è entrato in produzione, quando è troppo lento, quindi aggiungono un indice per accelerare la query. Ora il codice scorre veloce, ma dà le risposte sbagliate. E nessuno se ne accorge per tre o quattro giorni ... se sei fortunato. Se non sei fortunato, nessuno se ne accorge per un mese ...
TMN,

Non credo che gli sia mancato (forse era sciatto con la notazione). Non vuole il primo elemento dall'insieme, vuole un elemento arbitrario dall'insieme. Puoi dargli un elemento arbitrario poiché lo Setè Iterable.
Elazar Leibovich,

Stai parlando di get (indice) per indice. Che dire di un get (Object) per uguaglianza?
Kumar Manish,

10

alcune strutture di dati mancano dalle raccolte java standard.

Borsa (come set ma può contenere elementi più volte)

UniqueList (elenco ordinato, può contenere ogni elemento una sola volta)

sembra che avresti bisogno di un elenco univoco in questo caso

se hai bisogno di strutture di dati flessibili, potresti essere interessato a Google Collections


1
Guva fornisce un "UniqueList"?
Mike Rylander,

no, ma puoi avere un java.util.LinkedHashSet con proprietà simili.
Andreas Petersson,

7

È vero, gli elementi in Set non sono ordinati, per definizione della Collezione Set. Quindi non possono essere accessibili da un indice.

Ma perché non abbiamo un metodo get (oggetto), non fornendo l'indice come parametro, ma un oggetto uguale a quello che stiamo cercando? In questo modo, possiamo accedere ai dati dell'elemento all'interno del Set, semplicemente conoscendo i suoi attributi usati con il metodo uguale.


7

Se hai intenzione di fare molti accessi casuali per indice in un set, puoi ottenere una vista array dei suoi elementi:

Object[] arrayView = mySet.toArray();
//do whatever you need with arrayView[i]

Ci sono due principali inconvenienti:

  1. Non è efficiente in termini di memoria, poiché è necessario creare un array per l'intero set.
  2. Se il set viene modificato, la vista diventa obsoleta.

5

Questo perché Set garantisce solo unicità, ma non dice nulla sull'accesso ottimale o sui modelli di utilizzo. Vale a dire, un set può essere un elenco o una mappa, ognuno dei quali ha caratteristiche di recupero molto diverse.


5

L'unica ragione che mi viene in mente per l'utilizzo di un indice numerico in un set sarebbe per l'iterazione. Per quello, usa

for(A a : set) { 
   visit(a); 
}

Non è vero, che dire dell'accesso a un elemento casuale?
Jeremy Salwen,

Ah ah ah buon punto :) ma sarebbe molto incline a un uso improprio, ne sono sicuro.
Hugo,

3

Mi sono imbattuto in situazioni in cui volevo effettivamente un set ordinato con accesso tramite indice (concordo con altri poster che l'accesso a un set non ordinato con un indice non ha senso). Un esempio potrebbe essere un albero in cui volevo che i bambini fossero ordinati e ai bambini duplicati non era permesso.

Avevo bisogno dell'accesso tramite indice per visualizzarli e gli attributi impostati sono stati utili per eliminare in modo efficiente i duplicati.

Non trovando alcuna raccolta adatta nelle raccolte java.util o google, ho trovato semplice implementarlo da solo. L'idea di base è di avvolgere un SortedSet e creare un Elenco quando è richiesto l'accesso tramite indice (e dimenticare l'elenco quando viene modificato il SortedSet). Ovviamente, ciò funziona in modo efficiente solo quando si cambia il SortedSet spostato e si accede all'elenco è separato nel corso della vita della Collezione. Altrimenti si comporta come un elenco che viene ordinato spesso, cioè troppo lento.

Con un gran numero di bambini, questo ha migliorato molto le prestazioni rispetto a un elenco che ho tenuto ordinato tramite Collections.sort.


2

Nota: è possibile accedere solo a 2 strutture di dati di base tramite indice.

  • La struttura dei dati dell'array è accessibile tramite indice con O(1)complessità temporale per ottenere il get(int index)funzionamento.
  • È possibile accedere alla struttura dei dati di LinkedList anche tramite indice, ma con O(n)complessità temporale per raggiungere il get(int index)funzionamento.

In Java, ArrayListviene implementato utilizzando la struttura dati dell'array .

Mentre la struttura dei dati di Set di solito può essere implementata tramite la struttura di dati HashTable / HashMap o BalancedTree , per rilevare rapidamente se esiste un elemento e aggiungere elementi inesistenti, di solito un Set ben implementato può ottenere un'operazione di O(1)complessità temporale contains. In Java, HashSetè l'implementazione utilizzata più comunemente di Set , viene implementata chiamando HashMapAPI e HashMapviene implementata utilizzando un concatenamento separato con elenchi collegati (una combinazione di array e LinkedList ).

Poiché Set può essere implementato tramite una diversa struttura di dati, non esiste un get(int index)metodo per farlo.


Gli alberi delle dita (Vedi la Data.Sequence.lookupfunzione di Haskell ) consentono anche l'accesso tramite indice ( O(1)vicino alle estremità O(log n)vicino al centro, più accuratamente O(min(log(k), log(n-k)))), anche gli alberi binari (vedi la Data.Set.lookupIndexfunzione di Haskell ). Quindi la tua affermazione iniziale secondo cui "Si noti che solo 2 strutture di dati di base sono accessibili tramite indice" non è corretta.
punto

1

Il motivo per cui l' interfaccia Set non ha una chiamata di tipo indice o addirittura qualcosa di ancora più semplice, come first () o last (), è perché si tratta di un'operazione ambigua e quindi un'operazione potenzialmente pericolosa. Se un metodo restituisce un set e tu chiami, dì il primo () metodo su di esso, qual è il risultato atteso, dato che un set generico non fornisce alcuna garanzia sull'ordine? L'oggetto risultante potrebbe benissimo variare tra una chiamata e l'altra del metodo, oppure potrebbe non portarti in un falso senso di sicurezza, fino a quando la libreria che stai utilizzando modifica l'implementazione sottostante e ora scopri che tutto il tuo codice si rompe per nessun motivo particolare.

I suggerimenti sulle soluzioni alternative elencati qui sono buoni. Se è necessario un accesso indicizzato, utilizzare un elenco. Prestare attenzione all'utilizzo di iteratori o di Array con un set generico, poiché a) non esiste alcuna garanzia sull'ordinamento eb) non esiste alcuna garanzia che l'ordinamento non cambierà con successive invocazioni o con diverse implementazioni sottostanti. Se hai bisogno di qualcosa nel mezzo, un SortedSet o un LinkedHashSet è ciò che desideri.

// Vorrei che l'interfaccia Set avesse un elemento get-random-element.


1

java.util.Setè una raccolta di articoli non ordinati. Non ha alcun senso se Set ha un get (indice int), perché Set non ha un indice e anche tu puoi solo indovinare il valore.

Se lo vuoi davvero, codifica un metodo per ottenere un elemento casuale da Set.


0

Tu puoi fare new ArrayList<T>(set).get(index)


Ciò restituisce un elenco di insiemi e get (indice) restituisce un insieme. Piuttosto, ho usato: new ArrayList<T>(t).get(0) penso che ci sia una valida opposizione all'idea di ottenere un elemento particolare da un set da un indice. Ma sarebbe bello se Set avesse una funzione only () membro che, per Set di dimensioni 1, fornisse un facile accesso al solo elemento nel Set. Ciò salverebbe il suddetto new ArrayListofor (Foo foo : foos) { return foo; }
Doug Moscrop,

0

Se non ti dispiace il set da ordinare, potresti essere interessato a dare un'occhiata al progetto della mappa ad albero indicizzata .

Il TreeSet / TreeMap migliorato fornisce l'accesso agli elementi per indice o ottenendo l'indice di un elemento. E l'implementazione si basa sull'aggiornamento dei pesi dei nodi nella struttura RB. Quindi nessuna iterazione o backup da un elenco qui.


0

Set è un'interfaccia e alcune delle sue classi di implementazione sono HashSet, TreeSet e LinkedHashSet. Utilizza HashMap sotto il cofano per memorizzare i valori. Poiché HashMap non conserva l'ordine, non è possibile ottenere valore per indice.

Ora devi pensare a come Set sta usando HashMap poiché HashMap memorizza una coppia chiave, valore ma Set non lo fa. domanda valida. quando si aggiunge un elemento in Set, internamente, mantiene una HashMap in cui la chiave è l'elemento che si desidera inserire in Set e il valore è la costante fittizia. Di seguito è un'implementazione interna della funzione di aggiunta. Quindi, tutte le chiavi in ​​HashMap avranno lo stesso valore costante.

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

Tutte le Setimplementazioni di s stanno usando HashMapsotto il cofano per memorizzare i valori per cui puoi avvalorare questa richiesta TreeSet?
Greybeard,

1
the keys in the HashMap will have the same constant value le chiavi del HashMaptestamento saranno mappate su una stessa immutabileObject
greybeard, il


-3

Per ottenere un elemento in un set, lo uso come segue:

public T getElement(Set<T> set, T element) {
T result = null;
if (set instanceof TreeSet<?>) {
    T floor = ((TreeSet<T>) set).floor(element);
    if (floor != null && floor.equals(element))
    result = floor;
} else {
    boolean found = false;
    for (Iterator<T> it = set.iterator(); !found && it.hasNext();) {
    if (true) {
        T current = it.next();
        if (current.equals(element)) {
        result = current;
        found = true;
        }
    }
    }
}
return result;
}

la funzione non è quella che la domanda ha posto. abbiamo bisogno dell'indice, non del valore. cosa sta facendo la tua funzione? sembra che restituisca solo l'elemento se fosse uguale a un elemento all'interno. cosa fa questo che contiene () no?
Janus Troelsen,

Dov'è Tdefinito? Perché if (true)?
quantico
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.