2015-11-23 12 views
14

Sto cercando di capire perché questo codice ha un avviso di cast non controllato. I primi due calchi hanno alcun avvertimento, ma il terzo fa:Un cast non controllato nella classe generica che implementa Mappa <String, V>

class StringMap<V> extends HashMap<String, V> { 
} 

class StringToIntegerMap extends HashMap<String, Integer> { 
} 

Map<?, ?> map1 = new StringToIntegerMap(); 
if (map1 instanceof StringToIntegerMap) { 
    StringToIntegerMap stringMap1 = (StringToIntegerMap)map1; //no unchecked cast warning 
} 

Map<String, Integer> map2 = new StringMap<>(); 
if (map2 instanceof StringMap) { 
    StringMap<Integer> stringMap2 = (StringMap<Integer>)map2; //no unchecked cast warning 
} 

Map<?, Integer> map3 = new StringMap<>(); 
if (map3 instanceof StringMap) { 
    StringMap<Integer> stringMap3 = (StringMap<Integer>)map3; //unchecked cast warning 
} 

Questa è la piena avvertimento per il stringMap3 Cast:

Tipo di sicurezza: Non selezionata lanciato dalla Map<capture#3-of ?,Integer> a StringMap<Integer>

Tuttavia, la dichiarazione di classe StringMap specifica il primo parametro di tipo di Map (ad esempio, String) ed entrambi map3 e il cast StringMap<Integer> utilizzano lo stesso tipo per il secondo parametro di tipo di Map (ad esempio, Integer). Da quanto ho capito, fino a quando il cast non getta ClassCastException (e non dovrebbe, dal momento che c'è un controllo instanceof), stringMap3 sarebbe un valido Map<String, Integer>.

Si tratta di una limitazione del compilatore Java? Oppure esiste uno scenario in cui i metodi di chiamata di map3 o stringMap3 con determinati argomenti possono causare un inaspettato ClassCastException se l'avviso viene ignorato?

risposta

8

Il comportamento è come specificato.In Section 5.5.2 of the Java Language Specification è definito un cast incontrollato come:

Un cast da un tipo S ad un tipo parametrico T è selezionata a meno che almeno una delle seguenti condizioni:

  • S <: T

  • Tutti gli argomenti di tipo di T sono caratteri jolly senza limite

  • T <: S e S non ha alcun sottotipo X diverso da T dove gli argomenti tipo di X non sono contenuti negli argomenti di tipo T.

(dove A <: B significa: "A è un sottotipo di B").

Nel primo esempio, il tipo di destinazione non ha caratteri jolly (e quindi tutti sono illimitati). Nel secondo esempio, StringMap<Integer> è in realtà un sottotipo di Map<String, Integer> (e non esiste alcun sottotipo X come menzionato nella terza condizione).

Nel tuo terzo esempio, tuttavia, hai un cast da Map<?, Integer> a StringMap<Integer> e, a causa del carattere jolly ?, nessuno dei due è un sottotipo dell'altro. Inoltre, ovviamente, non tutti i parametri di tipo sono caratteri jolly illimitati, quindi nessuna delle condizioni si applica: si tratta di un'eccezione non controllata.

Se un cast non controllato si verifica nel codice, è necessario un compilatore Java conforme per emettere un avviso.

Come te, non vedo nessuno scenario in cui il cast non sia valido, quindi potresti obiettare che si tratta di una limitazione del compilatore Java, ma almeno è una limitazione specificata.

+0

Questa era la conclusione a cui stavo venendo dopo aver letto la risposta di @ pifta e varie sezioni nel JLS. Suppongo che il JLS possa essere aggiornato per aggiungere migliori regole di casting tra due tipi parametrizzati in cui le rispettive classi generiche usano un diverso numero di parametri di tipo, ma non saprei quanto sia fattibile questo o gli effetti collaterali. Questa domanda sembra essere correlata, a meno che non mi sbagli: [Trasmissione a sottotipi generici di una classe generica] (// stackoverflow.com/questions/17883536/casting-to-generic-subtypes-of-a-generic-class) . – blurredd

3

Questo cast non è sicuro. Supponiamo di avere:

Map<?, Integer> map3 = new HashMap<String,Integer>(); 
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3; 

Questo farà un'eccezione. Non importa che tu sappia che hai registrato un StringMap<Integer> e lo hai assegnato alla mappa3. Quello che stai facendo è noto come down-casting Downcasting in Java per maggiori informazioni.

EDIT: Stai anche complicando il problema con tutti i generici, avrai lo stesso identico problema senza alcun tipo generico.

+0

Esattamente. In sostanza, stai provando a trasmettere una "Mappa " in una "HashMap ", quindi a? in una stringa ... – JayC667

+0

L'uso di generici è centrale nella domanda. Con "sicuro" intendevo dire se era possibile chiamare un metodo (o un insieme di metodi) con determinati argomenti per map3 o stringMap3 che avrebbe causato un ClassCastException imprevisto se l'avvertimento era stato ignorato. Non stavo chiedendo se il cast iniziale su StringMap potrebbe causare una ClassCastException. Ho aggiornato la domanda. – blurredd

+1

@PaulBoddington hai ragione. Scuse –

3

Attualmente la risposta è nella specifica del linguaggio Java. Section 5.1.10 indica che se si utilizza un carattere jolly, sarà un nuovo tipo di acquisizione.

Ciò implica che, Map<String, Integer> non è una sottoclasse di Map<?, Integer>, e pertanto anche se il StringMap<Integer> è assegnabile a un tipo Map<?, Integer>, perché Map<?, Integer> rappresenta una mappa con un certo tipo di chiave e valori interi, che è vero per StringMap<Integer>, il cast è pericoloso Quindi questo è il motivo per cui ricevi un avviso di cast non controllato.

Dal punto compilatore di vista tra l'assegnazione e il cast ci potrebbe essere qualsiasi cosa, quindi, anche se map3 è un'istanza StringMap<Integer> al funzionamento cast, map3 ha Map<?, Integer> come il tipo, quindi l'avviso è completamente legittimo.

E per rispondere alla tua domanda: sì, fino a quando l'istanza map3 ha solo stringhe come chiavi, e il tuo codice non potrebbe garantirlo.

vedo perché no:

Map<?, Integer> map3 = new StringMap<Integer>(); 

// valid operation to use null as a key. Possible NPE depending on the map implementation 
// you choose as superclass of the StringMap<V>. 
// if you do not cast map3, you can use only null as the key. 
map3.put(null, 2); 

// but after an other unchecked cast if I do not want to create an other storage, I can use 
// any key type in the same map like it would be a raw type. 
((Map<Double, Integer>) map3).put(Double.valueOf(0.3), 2); 

// map3 is still an instance of StringMap 
if (map3 instanceof StringMap) { 
    StringMap<Integer> stringMap3 = (StringMap<Integer>) map3; // unchecked cast warning 
    stringMap3.put("foo", 0); 
    for (String s : stringMap3.keySet()){ 
     System.out.println(s+":"+map3.get(s)); 
    } 
} 

Il risultato:

null:2 
foo:0 
Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.String 
+0

Oh, a proposito, si può anche abusare della prima versione in questo modo, che ha bisogno di un altro cast non controllato. E fino a quando non si esegue il cast non controllato, è possibile utilizzare solo il metodo put della mappa con null come chiave e null come valore, che è sicuro in un modo, perché se l'implementazione della mappa supporta chiavi Null e valori Null, allora le tue operazioni di lettura dovrebbero considerare l'eventuale chiave nullo e il valore nullo nella mappa. – pifta

+0

Non intendevo implicare che ci sarebbero altri cast non controllati oltre a quello di 'StringMap '. Puoi potenzialmente corrompere qualsiasi mappa - o qualsiasi oggetto generico per quella materia - con il giusto cast non controllato, quindi il tuo esempio non risponde se il cast da 'Map ' a 'StringMap ' potrebbe mai consentire l'inquinamento dell'heap a differenza, ad esempio , eseguendo il cast dalla 'Mappa ' a 'Map '. È possibile creare uno scenario in cui la mappa assegnata a 'map3' è popolata altrove o utilizza un'implementazione di mappa diversa se ciò aiuta. – blurredd

+0

Ok, dopo il tuo commento ho trovato la stessa risposta che mi è stata data da Hoopje solo pochi minuti fa, ho ignorato quella parte del jls prima. Ad ogni modo, è un cast non controllato basato su 5.5.2. Tuttavia continuo a pensare che non sia una buona pratica usare questo tipo di calchi. – pifta

3

Ovviamente, non è possibile fornire una risposta che sia "più corretta" (correctier?) di una che spiega il comportamento osservato in termini della specifica del linguaggio Java. Tuttavia:

  • Una spiegazione del comportamento osservato data in termini pratici può essere più facile da seguire e facile da ricordare che una spiegazione, che getta solo il JLS a voi. Di conseguenza, una spiegazione pratica è spesso più utilizzabile di una spiegazione in termini di JLS.

  • Il JLS è esattamente il modo in cui è e non in altro modo, perché deve soddisfare vincoli pratici. Date le scelte fondamentali fatte dal linguaggio, molto spesso i dettagli del JLS non potevano essere diversamente da come lo sono. Ciò significa che le ragioni pratiche per un comportamento specifico possono essere considerate come più importanti della JLS, perché hanno modellato il JLS, il JLS non le ha modellate.

Quindi, una spiegazione pratica di ciò che sta accadendo segue.


la seguente:

Map<?, ?> map1 = ...; 
StringToIntegerMap stringMap1 = (StringToIntegerMap)map1; 

non avverte getto incontrollato perché non state lanciando - un tipo generico. E 'lo stesso che nel seguente modo:

Map map4 = ...; //gives warning "raw use of generic type"; bear with me. 
StringToIntegerMap stringMap4 = (StringToIntegerMap)map4; //no unchecked warning! 

la seguente:

Map<String, Integer> map2 = ...; 
StringMap<Integer> stringMap2 = (StringMap<Integer>)map2; 

non avverte fusione incontrollata, perché gli argomenti generici della mano sinistra partita lato argomenti generici di destra. (Entrambi sono <String,Integer>)


la seguente:

Map<?, Integer> map3 = ...; 
StringMap<Integer> stringMap3 = (StringMap<Integer>)map3; 

dà un avvertimento getto incontrollato perché sinistra è <String,Integer> ma destra è <?,Integer> e si può sempre aspettare un tale avviso quando si trasmettere un carattere jolly a un tipo specifico. (O un tipo limitato a un tipo più strettamente limitato, più specifico.) Si noti che "Intero" in questo caso è una falsa pista, si otterrebbe la stessa cosa con Map<?,?> map3 = new HashMap<>();

+0

Ma il cast in realtà non proviene da 'Map ' a 'Map ' (la maggior parte delle risposte indirizzo), ma da 'Mappa ' a 'StringMap '. Questo rende la domanda molto più interessante, perché se un oggetto è un 'StringMap' (un controllo che può essere eseguito in fase di runtime), allora deve essere un' Mappa '. Quindi, intuitivamente, il cast non è affatto deselezionato. – Hoopje

Problemi correlati