Tipo di sicurezza: cast non selezionato


266

Nel mio file di contesto dell'applicazione Spring, ho qualcosa del tipo:

<util:map id="someMap" map-class="java.util.HashMap" key-type="java.lang.String" value-type="java.lang.String">
    <entry key="some_key" value="some value" />
    <entry key="some_key_2" value="some value" />   
</util:map>

Nella classe java, l'implementazione è simile a:

private Map<String, String> someMap = new HashMap<String, String>();
someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

In Eclipse, vedo un avviso che dice:

Tipo di sicurezza: cast non selezionato da Object a HashMap

Cos'ho fatto di sbagliato? Come posso risolvere il problema?


Mi è venuta in mente una routine per controllare effettivamente il cast di HashMap con parametri, che elimina l'avvertimento di cast non controllato: link Direi che questa è la soluzione "corretta", ma se ne valga la pena o meno potrebbe essere discutibile. :)
skiphoppy,


Risposte:


249

Bene, prima di tutto, stai sprecando memoria con la nuova HashMapchiamata di creazione. La tua seconda riga ignora completamente il riferimento a questa hashmap creata, rendendola quindi disponibile per il Garbage Collector. Quindi, non farlo, usa:

private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");

In secondo luogo, il compilatore si lamenta di aver lanciato l'oggetto su a HashMapsenza controllare se è a HashMap. Ma, anche se dovessi fare:

if(getApplicationContext().getBean("someMap") instanceof HashMap) {
    private Map<String, String> someMap = (HashMap<String, String>)getApplicationContext().getBean("someMap");
}

Probabilmente riceveresti ancora questo avviso. Il problema è, getBeanritorna Object, quindi non si sa quale sia il tipo. La conversione HashMapdiretta in non causerebbe il problema con il secondo caso (e forse non ci sarebbe un avviso nel primo caso, non sono sicuro di quanto sia pedante il compilatore Java con avvisi per Java 5). Tuttavia, lo stai convertendo in aHashMap<String, String> .

Le hashmap sono in realtà mappe che prendono un oggetto come chiave e hanno un oggetto come valore, HashMap<Object, Object>se vuoi. Pertanto, non vi è alcuna garanzia che, quando si ottiene il proprio bean, possa essere rappresentato come HashMap<String, String>perché si potrebbe avereHashMap<Date, Calendar> perché la rappresentazione non generica che viene restituita può contenere qualsiasi oggetto.

Se il codice viene compilato e puoi eseguirlo String value = map.get("thisString");senza errori, non preoccuparti per questo avviso. Ma se la mappa non è completamente composta da chiavi di stringa e valori di stringa, otterrai un ClassCastExceptionruntime, poiché i generici non possono impedire che ciò accada in questo caso.


12
Questo era un po 'di tempo fa, ma stavo cercando una risposta sul tipo che controllava un Set <CustomClass> prima di un cast, e non è possibile istanza di un generico parametrizzato. ad es. if (event.getTarget instanceof Set <CustomClass>) Puoi solo digitare un generico con un? e questo non rimuoverà l'avvertimento cast. ad es. if (event.getTarget instanceof Set <?>)
garlicman

315

Il problema è che un cast è un controllo di runtime, ma a causa della cancellazione del tipo, in fase di runtime non c'è in realtà alcuna differenza tra a HashMap<String,String>e HashMap<Foo,Bar>per qualsiasi altro Fooe Bar.

Usa @SuppressWarnings("unchecked")e tieni il naso. Oh, e campagna per generici reificati in Java :)


14
Prenderò i generici reificati di Java su NSMutableWype non tipizzato, che sembra un salto di dieci anni all'indietro, in qualsiasi giorno della settimana. Almeno Java ci sta provando.
Dan Rosenstark,

12
Esattamente. Se si insiste sul controllo dei tipi, può essere fatto solo con HashMap <?,?> E ciò non rimuoverà l'avviso poiché è lo stesso del non controllo dei tipi generici. Non è la fine del mondo, ma è fastidioso che tu sia sorpreso a sopprimere un avvertimento o a conviverci.
garlicman,

5
@JonSkeet Che cos'è un generico reificato?
SasQ,

89

Come indicato dai messaggi sopra, l'elenco non può essere differenziato tra a List<Object>e a List<String>oList<Integer> .

Ho risolto questo messaggio di errore per un problema simile:

List<String> strList = (List<String>) someFunction();
String s = strList.get(0);

con il seguente:

List<?> strList = (List<?>) someFunction();
String s = (String) strList.get(0);

Spiegazione: La prima conversione di tipo verifica che l'oggetto sia un Elenco senza preoccuparsi dei tipi contenuti (poiché non è possibile verificare i tipi interni a livello Elenco). La seconda conversione è ora richiesta perché il compilatore conosce solo che l'elenco contiene una sorta di oggetti. Ciò verifica il tipo di ciascun oggetto nell'elenco mentre vi si accede.


3
hai ragione amico mio. Invece di lanciare la lista, basta iterarla e lanciare ogni elemento, l'avvertimento non apparirà, fantastico.
Juan Isaza,

2
Questo ha rimosso l'avvertimento, ma non sono ancora sicuro: P
mamma

1
Si sente come bendare il compilatore, ma non runtime: D Quindi non vedo alcuna differenza tra questo e @SuppressWarnings ( "incontrollato")
channae

1
È fantastico! La differenza principale nell'uso di @SupressWarning è che l'uso dell'annotazione elimina l'avvertenza dal tuo IDE e dagli strumenti di analisi del codice ma se stai usando la compilazione del flag -Werror finirai con un errore. Utilizzando questo approccio entrambi gli avvisi sono stati corretti.
Edu Costa,

30

Un avvertimento è proprio questo. Un avvertimento. A volte gli avvisi sono irrilevanti, a volte no. Sono abituati a richiamare la tua attenzione su qualcosa che il compilatore ritiene possa essere un problema, ma potrebbe non esserlo.

Nel caso dei cast, in questo caso fornirà sempre un avviso. Se sei assolutamente certo che un determinato cast sarà sicuro, allora dovresti considerare di aggiungere un'annotazione come questa (non sono sicuro della sintassi) appena prima della riga:

@SuppressWarnings (value="unchecked")

14
-1: un avviso non dovrebbe mai essere accettato. O sopprimere questo tipo di avvisi o risolverlo. Verrà il momento in cui dovrai ricevere molti avvisi e non vedrai il relativo una volta.
ezdazuzena,

10
Non puoi davvero evitare gli avvisi di cast di classe quando lanci generici parametrici, ad esempio Mappa, quindi questa è la risposta migliore per la domanda originale.
muttonUp

9

Stai ricevendo questo messaggio perché getBean restituisce un riferimento a un oggetto e lo stai lanciando nel tipo corretto. Java 1.5 ti dà un avvertimento. Questa è la natura dell'uso di Java 1.5 o superiore con un codice che funziona in questo modo. Spring ha la versione typesafe

someMap=getApplicationContext().getBean<HashMap<String, String>>("someMap");

nella sua lista delle cose da fare.


6

Se vuoi davvero sbarazzarti degli avvisi, una cosa che puoi fare è creare una classe che si estende dalla classe generica.

Ad esempio, se stai cercando di utilizzare

private Map<String, String> someMap = new HashMap<String, String>();

Puoi creare una nuova classe come questa

public class StringMap extends HashMap<String, String>()
{
    // Override constructors
}

Quindi quando lo usi

someMap = (StringMap) getApplicationContext().getBean("someMap");

Il compilatore conosce quali sono i tipi (non più generici) e non ci saranno avvisi. Questa potrebbe non essere sempre la soluzione perfetta, alcuni potrebbero obiettare che questo tipo di sconfigge lo scopo delle classi generiche, ma stai ancora riutilizzando tutto lo stesso codice della classe generica, stai solo dichiarando in fase di compilazione quale tipo vuoi usare.


3

La soluzione per evitare l'avviso non selezionato:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");

Sembra un hack non una soluzione.
Malwinder Singh,

1
- La classe serializzabile MyMap non dichiara un campo serialVersionUID finale statico di tipo long: {
Ulterior

1

Un'altra soluzione, se ti ritrovi a lanciare molto lo stesso oggetto e non vuoi sporcare il tuo codice @SupressWarnings("unchecked"), sarebbe creare un metodo con l'annotazione. In questo modo stai centralizzando il cast e, si spera, riduci la possibilità di errore.

@SuppressWarnings("unchecked")
public static List<String> getFooStrings(Map<String, List<String>> ctx) {
    return (List<String>) ctx.get("foos");
}

1

Il codice seguente indica il tipo Avviso di sicurezza

Map<String, Object> myInput = (Map<String, Object>) myRequest.get();

Soluzione

Crea un nuovo oggetto mappa senza menzionare i parametri perché il tipo di oggetto contenuto nell'elenco non è verificato.

Passaggio 1: crea una nuova mappa temporanea

Map<?, ?> tempMap = (Map<?, ?>) myRequest.get();

Passaggio 2: creare un'istanza della mappa principale

Map<String, Object> myInput=new HashMap<>(myInputObj.size());

Passaggio 3: alterna la mappa temporanea e imposta i valori nella mappa principale

 for(Map.Entry<?, ?> entry :myInputObj.entrySet()){
        myInput.put((String)entry.getKey(),entry.getValue()); 
    }

0

Cos'ho fatto di sbagliato? Come posso risolvere il problema?

Qui :

Map<String,String> someMap = (Map<String,String>)getApplicationContext().getBean("someMap");

Si utilizza un metodo legacy che generalmente non si desidera utilizzare poiché restituisce Object:

Object getBean(String name) throws BeansException;

Il metodo preferito per ottenere (per singleton) / creare (per prototipo) un bean da una fabbrica di bean è:

<T> T getBean(String name, Class<T> requiredType) throws BeansException;

Usandolo come:

Map<String,String> someMap = app.getBean(Map.class,"someMap");

verrà compilato ma comunque con un avviso di conversione non selezionato poiché tutti gli Mapoggetti non sono necessariamenteMap<String, String> oggetti.

Ma <T> T getBean(String name, Class<T> requiredType) throws BeansException; non è sufficiente nelle classi generiche bean come le raccolte generiche poiché ciò richiede di specificare più di una classe come parametro: il tipo di raccolta e i suoi tipi generici.

In questo tipo di scenario e in generale, un approccio migliore non è quello di utilizzare direttamente i BeanFactorymetodi, ma consentire al framework di iniettare il bean.

La dichiarazione del fagiolo:

@Configuration
public class MyConfiguration{

    @Bean
    public Map<String, String> someMap() {
        Map<String, String> someMap = new HashMap();
        someMap.put("some_key", "some value");
        someMap.put("some_key_2", "some value");
        return someMap;
    }
}

L'iniezione di fagioli:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
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.