2015-06-08 23 views
11

Il codice sorgente di HashMap.values() è la seguenteIn che modo HashMap.values ​​() e HashMap.keySet() restituiscono valori e chiavi?

public Collection<V> values() { 
    Collection<V> vs = values; 
    return (vs != null ? vs : (values = new Values())); 
} 

Come si può vedere, quando il metodo values() prima chiamata, semplicemente restituisce un oggetto Values. L'oggetto Values è una sottoclasse di AbstractCollection senza alcun costruttore e, naturalmente, non contiene alcun elemento. Ma quando ho chiamato il metodo, ha restituito una collezione rapidamente

Collection<String> values = map.values(); 
System.out.println(values); 

Questo è così strano. Non solo values(), ma anche il metodo keySet() e entrySet() restituiscono tali oggetti vuoti. Quindi, ecco la mia domanda, quando e in che modo questi metodi restituiscono oggetti con elementi di cui abbiamo bisogno?

risposta

20

È un equivoco che la classe Values sia "ovviamente vuota". Solo perché non esiste un metodo chiamato su di esso e il suo costruttore non ha argomenti non significa che la raccolta è vuota.

La classe Values è una "classe interna" (un non-statico nested class) di HashMap, che significa che ha un implicito riferimento all'oggetto HashMap che lo ha creato. Può quindi accedere a tutti gli elementi dello HashMap, in modo esplicito utilizzando il riferimento HashMap.this o semplicemente accedendo direttamente ai membri. Poiché si tratta di una classe interna, è anche permesso di accedere ai membri privati ​​di HashMap.

Si può vedere che per esempio nel Values class' implementazione del metodo size:

public int size() { 
    return size; 
} 

Il Values classe non ha un membro size, in modo che size si riferisce alla HashMap 'dimensione s.E 'equivalente a:

public int size() { 
    return HashMap.this.size; 
} 

EDIT: Si noti che questo significa anche che la raccolta si riceve non è una copia, ma ancora si riferisce alle HashMap contenuti originali e di conseguenza cambia quando si aggiorna il HashMap:

// Getting the entry set (not a copy!) 
    Set<Entry<String, String>> entries = map.entrySet(); 

    // Add elements to the map afterwards 
    map.put("abc", "def"); 

    // Check out the entries in the collection 
    // (magically containing the elements added after getting the collection) 
    System.out.println(entries); // "[abc=def]" 
+1

'Nota che questo significa anche che la raccolta che ricevi non è una copia, ma si riferisce ancora al contenuto originale di HashMap e quindi cambia quando aggiorni la HashMap:' Che è anche chiaramente enunciato nel contratto di 'Map.values ​​() ', per evitare qualsiasi malinteso. – biziclop

+0

Una classe interna è una classe nidificata non statica, cioè se la classe era statica, non sarebbe una classe interna, ma semplicemente una classe nidificata statica. https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html – Sardtok

+0

@Sardtok: Grazie per il suggerimento, ho appena modificato la risposta per utilizzare la terminologia ufficiale. – mastov

13

La classe Values implementa un Collection supportato dallo HashMap. Come ha commentato mastov, Values è una classe interna di HashMap, che consente l'accesso ai membri dell'istanza HashMap allegata a cui è associata. Quindi non è vuoto. Le sue dimensioni sono le dimensioni di HashMap e, quando si esegue iterazione su di esso, si sta iterando sopra le voci di HashMap.

Quando si chiama System.out.println(values);, si chiedono il metodo toString di AbstractCollection, che utilizza un Iterator per scorrere i valori e ottenere la loro String rappresentazione. Lo Iterator esegue effettivamente un'iterazione delle voci di HashMap e restituisce i relativi valori.

Lo stesso vale per lo Set s restituito da keySet e entrySet.

+0

Forse dovresti menzionare che la classe 'Values' è una classe interna non statica che quindi ha un puntatore implicito' this' sulla classe esterna 'HashMap'. È così che può fare riferimento agli elementi della mappa, anche quando il suo costruttore viene chiamato senza argomenti. – mastov

+0

@mastov buon punto – Eran

+0

Grazie a @Eran e @mastov. So che il metodo iteratore può funzionare bene quando iteriamo "Valori". Tuttavia, quando imposto un breakpoint alla 'Collection values ​​= map.values ​​();' linea e esegui il debug del programma, i valori sono già stati impostati come una raccolta prima che 'System.out.println (valori)' sia stato chiamato, e non c'è iterazione qui. Questo è il punto in cui mi sento strano. – Yohn

2
Collection<V> vs = values; 
return (vs != null ? vs : (values = new Values())); 

Queste due righe rispondono alla tua domanda.

values è una raccolta interna (Ereditata da AbstractMap). Se non è null allora verrà restituito.
Se è null, verrà inizializzato e verrà restituito quello nuovo.

ora penso, il punto principale della tua domanda è
quando e come questi metodi restituiscono oggetti con elementi di cui abbiamo bisogno?

Tecnicamente values restituisce sempre questo oggetto inizializzato. Quindi come otteniamo i nostri valori di entrata da questi oggetti?
Lets andare un po 'in profondità:

valori() in realtà restituisce un oggetto della classe Values che è una classe interna di HashMap e si estende AbstractCollection

private final class Values extends AbstractCollection<V> 

Come avete guardato nel codice sorgente. Allora troverete all'interno della classe Values

public Iterator<V> iterator() { 
     return newValueIterator(); 
    } 

questo newValueIterator() fa il trucco.
Se si va più profondo troverete newValueIterator() rendimenti oggetto di ValueIterator che è una sottoclasse di HashIterator HashIterator implementa funzionalità di base per l'iterazione, che in realtà itterate il table mantenuto dal HashMap.

Problemi correlati