2015-04-14 11 views
5

Ho trovato qualcosa di interessante con Maps, rawtype e generici. seguente codice:Perché sto perdendo informazioni sul tipo?

static { 
      Map map = new HashMap(); 
      Set <Map.Entry> set = map.entrySet(); 
      for (Map.Entry entry : set) {} // fine 
      for (Map.Entry entry : map.entrySet()) {} // compilation error 
} 

sto ottenendo un errore di compilazione di un tipo di incompatibilità, vale a dire: "L'oggetto non può essere gettato a voce".

Ideone for convenience

Perché l'iteratore su entrySet() perdere le informazioni di tipo se non c'è alcuna variabile di riporla di nuovo?

I tipi rawtypes non devono influire sul tipo in modo che Map.Entry sia improvvisamente un oggetto. O mi sbaglio?

+4

'Set set = map.entrySet();' è un "incarico incontrollato", che è consentito. Questo è ciò che consente al primo iteratore di funzionare. Puoi ottenere lo stesso risultato rendendo esplicito il cast nel secondo caso: 'for (Map.Entry entry: (Set ) map.entrySet()) {}' – Richard

+0

La regola base da seguire è: mai usare raw tipi. Sono piuttosto facili da evitare (di solito sostituiti con un parametro jolly, ad esempio "Set" si trasforma in "Set ") e l'unica ragione per la loro esistenza è la retrocompatibilità con il codice pre-generico. –

+0

@JoachimSauer, d'accordo, ma presumo che questa sia una domanda per lo più accademica. edit: Ok, quindi non è accademico, ma comunque è una domanda divertente. – DavidS

risposta

7

Il tuo esempio ti fa sembrare che tu abbia informazioni sul tipo che non hai mai avuto. Lei ha scritto:

Map map = new HashMap(); 
Set <Map.Entry> set = map.entrySet(); 
for (Map.Entry entry : set) {} // fine 
for (Map.Entry entry : map.entrySet()) {} // compilation error 

Ma map.entrySet() sta tornando Set, non Set <Map.Entry>. Hai eseguito un compito non controllato che "aggiunge" informazioni sul tipo.

Nel secondo ciclo for, non sappiamo cosa c'è all'interno dello Set, quindi non possiamo eseguire iterazioni su Set <Map.Entry> senza un cast esplicito.

Ad esempio, confrontare l'esempio originale con quello in cui non "aggiungere" le informazioni sul tipo con l'assegnazione deselezionata.

Map map = new HashMap(); 
Set set = map.entrySet(); 
for (Map.Entry entry : set) { 
} // Object cannot be cast to Entry 
for (Map.Entry entry : map.entrySet()) { 
} // Object cannot be cast to Entry 

In questo caso, entrambi per cicli producono un errore di compilazione.

Questo comportamento è documentato nel linguaggio Java Specification, paragrafo 4.8:

Il tipo di un costruttore (§8.8), metodo istanza (§8.8, §9.4), o non statico campo (§8.3) M di un tipo non elaborato C non ereditato da le sue superclassi o superinterfacce è la cancellazione del suo tipo nella dichiarazione generica corrispondente a C. Il tipo di un membro statico di un tipo non elaborato C è il come il suo tipo nella dichiarazione generica corrispondente a C.

+2

"Ma map.entrySet() restituisce Set, non Set ." -> Questo sembra contraddire con la documentazione, che afferma esplicitamente che il tipo di ritorno di 'entrySet()' è 'Set '. – Unihedron

+0

@Unihedro: la differenza è che 'map' ha un tipo grezzo, il che significa che tutte le informazioni generiche verranno ignorate quando si chiama un metodo su di esso. Quindi 'map.entrySet()' restituisce effettivamente un 'Set' e non un' Set '. –

+2

@Unihedro, puoi solo fare riferimento a 'Map.Entry' se hai dichiarato i tipi generici nella dichiarazione. Usando rawtype hai cambiato il contratto del metodo. Javadoc per le classi generiche non menziona che restituiscono rawtype se non vengono fornite le informazioni sul tipo (ad esempio [List.subList] (https://docs.oracle.com/javase/7/docs/api/java/util /List.html#subList(int,%20int)), ma questo è il modo in cui sono progettati. – DavidS

4

Penso che la risposta breve sia che Java consente "cast non controllato" in alcune situazioni ma non in altre. Gestire i tipi non elaborati (tipi generici senza un tipo specificato) è una di queste istanze.

Tenete a mente che for (Map.Entry entry : set) è equivalente a:

Iterator it = set.iterator(); 
while (it.hasNext()) 
{ 
    Map.Entry entry = it.next(); 
} 

L'assegnazione:

Set set = map.entrySet(); 

è consentito e non genererà alcun preavviso, come non si stanno introducendo qualsiasi nuovo tipo, ma in il ciclo forit.next() restituirà il tipo Object e si otterrà l'eccezione del compilatore se lo si assegna senza un cast esplicito.

L'assegnazione:

Set <Map.Entry> set = map.entrySet(); 

è consentito, ma genera un avviso "incontrollato cast" a causa del tipo esplicita Map.Entry e nel for ciclo it.next() tipo Map.Entry ritornerà e l'assegnazione funzionerà bene.

si può mettere il cast esplicito nel ciclo for in questo modo:

for(Map.Entry entry : (Set<Map.Entry>) map.entrySet())