Come posso indirizzare gli avvisi di cast non controllati?


611

Eclipse mi sta avvisando del seguente modulo:

Tipo di sicurezza: cast non selezionato da Object a HashMap

Questo è da una chiamata a un'API su cui non ho alcun controllo su quale restituisce Object:

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
  HashMap<String, String> theHash = (HashMap<String, String>)session.getAttribute("attributeKey");
  return theHash;
}

Vorrei evitare gli avvisi di Eclipse, se possibile, poiché teoricamente indicano almeno un potenziale problema di codice. Tuttavia, non ho ancora trovato un buon modo per eliminarlo. Posso estrarre da sola la singola linea coinvolta in un metodo e aggiungerla @SuppressWarnings("unchecked")a quel metodo, limitando così l'impatto di avere un blocco di codice in cui ignoro gli avvisi. Qualche opzione migliore? Non voglio disattivare questi avvisi in Eclipse.

Prima di arrivare al codice, era più semplice, ma provocava ancora avvisi:

HashMap getItems(javax.servlet.http.HttpSession session) {
  HashMap theHash = (HashMap)session.getAttribute("attributeKey");
  return theHash;
}

Il problema si verificava altrove quando si tentava di utilizzare l'hash e si ricevevano avvisi:

HashMap items = getItems(session);
items.put("this", "that");

Type safety: The method put(Object, Object) belongs to the raw type HashMap.  References to generic type HashMap<K,V> should be parameterized.

Se stai usando HttpSession in questo modo, leggi l'articolo di Brian Goetz sull'argomento: ibm.com/developerworks/library/j-jtp09238.html
Tom Hawtin - tackline

Se un cast non controllato è inevitabile, una buona idea è accoppiarlo strettamente a qualcosa che rappresenta logicamente il suo tipo (come un enumesempio o addirittura di Class<T>), in modo da poterlo guardare e sapere che è sicuro.
Philip Guin,


3
possibile duplicato della sicurezza
Raedwald,

Vorrei aggiungere, ho scoperto che potevo solo aggiungere @SuppressWarnings ("deselezionato") a livello di metodo che contiene il codice offensivo. Quindi ho dato il codice a una routine in cui dovevo farlo. Ho sempre pensato che potessi farlo immediatamente sopra la linea in questione.
JGFMK,

Risposte:


557

La risposta ovvia, ovviamente, non è quella di fare il cast incontrollato.

Se è assolutamente necessario, almeno prova a limitare l'ambito @SuppressWarningsdell'annotazione. Secondo i suoi Javadocs , può andare su variabili locali; in questo modo, non influisce nemmeno sull'intero metodo.

Esempio:

@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();

Non c'è modo di determinare se i Mapparametri generici debbano essere realmente <String, String>. Devi sapere in anticipo quali dovrebbero essere i parametri (o scoprirai quando otterrai a ClassCastException). Questo è il motivo per cui il codice genera un avviso, perché il compilatore non può sapere se è sicuro.


112
+1 per sottolineare che può andare su variabili locali. Eclipse si offre solo di aggiungerlo all'intero metodo ...
Dal

17
Eclipse 3.7 (Indigo) supporta l'aggiunta di variabili non selezionate alle variabili locali.
Sweetfa,

78
L'avvertimento non è solo perché il compilatore non sa che il cast è sicuro. Ad esempio String s = (String) new Object() ;non riceve alcun avviso, anche se il compilatore non sa che il cast è sicuro. L'avvertimento è perché il compilatore (a) non sa che il cast è sicuro E (b) non genererà un controllo completo del tempo di esecuzione nel punto del cast. Ci sarà un controllo che è un Hashmap, ma non ci sarà un controllo che sia un HashMap<String,String>.
Theodore Norvell,

9
Purtroppo, anche se il cast e l'avvertimento sono per l' assegnazione , l'annotazione deve andare sulla dichiarazione variabile ... Quindi se la dichiarazione e l'assegnazione si trovano in luoghi diversi (diciamo, all'esterno e all'interno di un blocco 'prova' rispettivamente) , Eclipse ora genera due avvisi: il cast originale non controllato e una nuova diagnostica "annotazione non necessaria".
Ti Strga,

6
Una soluzione alternativa per l'annotazione che deve accompagnare la dichiarazione della variabile locale, che può trovarsi in un ambito diverso su una riga diversa rispetto al cast effettivo, consiste nel creare una variabile locale nell'ambito del cast in modo specifico per eseguire il cast sulla stessa linea come la dichiarazione. Quindi assegnare questa variabile alla variabile effettiva che si trova in un ambito diverso. Questo è il metodo che ho usato anche per sopprimere l'avviso di un cast su una variabile di istanza poiché l'annotazione non può essere neanche applicata qui.
Jeff Lockhart,

169

Sfortunatamente, non ci sono grandi opzioni qui. Ricorda, l'obiettivo di tutto ciò è preservare la sicurezza dei tipi. " Java Generics " offre una soluzione per la gestione di librerie legacy non generiche e ce n'è una in particolare chiamata "tecnica a ciclo vuoto" nella sezione 8.2. Fondamentalmente, crea il cast non sicuro e sopprimi l'avvertimento. Quindi scorrere la mappa in questo modo:

@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());

Se si incontra un tipo imprevisto, si otterrà un runtime ClassCastException, ma almeno accadrà vicino alla fonte del problema.


6
Risposta molto migliore di quella fornita da skiphoppy, per molteplici ragioni: 1) Questo codice è molto, molto più breve. 2) Questo codice genera effettivamente ClassCastException come previsto. 3) Questo codice non esegue una copia completa della mappa di origine. 4) I loop possono essere facilmente avvolti in un metodo separato utilizzato in un'asserzione, che rimuove facilmente l'hit di prestazioni nel codice di produzione.
Stijn de Witt,

6
Non esiste la possibilità che il compilatore Java o il compilatore JIT decida che i risultati di questo codice non vengano utilizzati e che "ottimizzino" non compilandolo?
RenniePet,

1
Non è davvero un codice morto se può potenzialmente generare un'eccezione. Non so abbastanza dei compilatori JIT in uso oggi per garantire che nessuno di loro rovinerebbe tutto, ma sono abbastanza fiducioso nel dire che non dovrebbero.
GrandOpener,

3
Questo non garantisce ancora la sicurezza del tipo in quanto viene ancora utilizzata la stessa mappa. Potrebbe essere stato originariamente definito come Mappa <Oggetto, Oggetto> in cui sono presenti Stringhe e Numeri e in seguito se viene aggiunto un valore booleano, l'utente di questo codice otterrà una sorpresa confusa e piuttosto difficile da rintracciare. L'unico modo per garantire la sicurezza del tipo è copiarlo in una nuova mappa con il tipo richiesto che garantisca ciò che gli è permesso di entrare.
user2219808,

112

Wow; Penso di aver capito la risposta alla mia domanda. Non sono sicuro che ne valga la pena! :)

Il problema è che il cast non è stato verificato. Quindi, devi controllarlo da solo. Non puoi semplicemente controllare un tipo con parametri con instanceof, poiché le informazioni sul tipo con parametri non sono disponibili in fase di esecuzione, essendo state cancellate al momento della compilazione.

Tuttavia, puoi eseguire un controllo su ogni singolo elemento nell'hash, con instanceof, e così facendo, puoi costruire un nuovo hash che sia sicuro per i tipi. E non provocherai alcun avvertimento.

Grazie a mmyer ed Esko Luontola, ho parametrizzato il codice che ho scritto qui in origine, quindi può essere racchiuso in una classe di utilità da qualche parte e utilizzato per qualsiasi HashMap con parametri. Se vuoi capirlo meglio e non hai molta familiarità con i generici, ti incoraggio a visualizzare la cronologia delle modifiche di questa risposta.

public static <K, V> HashMap<K, V> castHash(HashMap input,
                                            Class<K> keyClass,
                                            Class<V> valueClass) {
  HashMap<K, V> output = new HashMap<K, V>();
  if (input == null)
      return output;
  for (Object key: input.keySet().toArray()) {
    if ((key == null) || (keyClass.isAssignableFrom(key.getClass()))) {
        Object value = input.get(key);
        if ((value == null) || (valueClass.isAssignableFrom(value.getClass()))) {
            K k = keyClass.cast(key);
            V v = valueClass.cast(value);
            output.put(k, v);
        } else {
            throw new AssertionError(
                "Cannot cast to HashMap<"+ keyClass.getSimpleName()
                +", "+ valueClass.getSimpleName() +">"
                +", value "+ value +" is not a "+ valueClass.getSimpleName()
            );
        }
    } else {
        throw new AssertionError(
            "Cannot cast to HashMap<"+ keyClass.getSimpleName()
            +", "+ valueClass.getSimpleName() +">"
            +", key "+ key +" is not a " + keyClass.getSimpleName()
        );
    }
  }
  return output;
}

È un sacco di lavoro, forse per una ricompensa molto piccola ... Non sono sicuro che lo userò o no. Gradirei qualsiasi commento sul fatto che la gente pensi che ne valga la pena o no. Inoltre, apprezzo i suggerimenti di miglioramento: c'è qualcosa di meglio che posso fare oltre a lanciare AssertionErrors? C'è qualcosa di meglio che potrei lanciare? Devo renderlo un'eccezione controllata?


68
questa roba è confusa, ma penso che tutto ciò che hai fatto siano state scambiate ClassCastExceptions per AssertionErrors.
Dustin Getz,

59
Amico, sicuramente non ne vale la pena! Immagina la povera linfa che deve tornare e modificare un po 'di codice con quel casino lì dentro. Non mi piace sopprimere gli avvisi, ma penso che qui sia il male minore.
Craig B,

69
Non è solo un brutto, confuso, pasticcio (quando non si può evitare un commento copioso può passare attraverso il programmatore di manutenzione); iterando su ogni elemento della raccolta trasforma il cast da un'operazione O (1) a un'operazione O (n). Questo è qualcosa che non ci si aspetterebbe mai e può facilmente trasformarsi in un orribile rallentamento del mistero.
Dan è Fiddling By Firelight il

22
@DanNeely hai ragione. In generale, nessuno dovrebbe mai farlo.
skiphoppy,

4
Alcuni commenti ... la firma del metodo è sbagliata perché non "lancia" una dannata cosa, copia semplicemente la mappa esistente in una nuova mappa. Inoltre, potrebbe essere probabilmente sottoposto a refactoring per accettare qualsiasi mappa e non fare affidamento su HashMap stesso (ovvero prendere Map e restituire Map nella firma del metodo, anche se il tipo interno è HashMap). Non è davvero necessario eseguire il casting o l'archiviazione in una nuova mappa: se non si genera un errore di asserzione, la mappa fornita ha i tipi giusti al suo interno al momento. La creazione di una nuova mappa con i tipi generici è inutile in quanto potresti comunque renderla grezza e mettere qualunque cosa.
MetroidFan2002,

51

In Preferenze Eclipse, vai su Java-> Compilatore-> Errori / Avvertenze-> Tipi generici e seleziona la Ignore unavoidable generic type problemscasella di controllo.

Questo soddisfa l'intento della domanda, vale a dire

Vorrei evitare gli avvisi Eclipse ...

se non lo spirito.


1
Ah, grazie per questo :) Stavo ricevendo un " uses unchecked or unsafe operations." errore javac, ma l'aggiunta di @SuppressWarnings("unchecked")Eclipse rendeva infelice, affermando che la soppressione non era necessaria. Deselezionando questa casella, Eclipse si javaccomporta e si comporta allo stesso modo, che è quello che volevo. Sopprimere esplicitamente l'avvertimento nel codice è molto più chiaro che sopprimerlo ovunque all'interno di Eclipse.
dimo414,

26

È possibile creare una classe di utilità come la seguente e utilizzarla per sopprimere l'avviso non selezionato.

public class Objects {

    /**
     * Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type.
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }
}

Puoi usarlo come segue:

import static Objects.uncheckedCast;
...

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
      return uncheckedCast(session.getAttribute("attributeKey"));
}

Altre discussioni su questo sono qui: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html


18
non downvoting, ma il wrapper non aggiunge esattamente nulla oltre a sopprimere l'avviso.
Dustin Getz,

3
+1 poiché questa soluzione non spreca preziose righe di codice.
Tino,

1
@ErikE Troppo. Monitor molto più costosi e di risoluzione più elevata per dare spazio a tutte quelle linee sprecate, una scrivania più grande su cui appoggiare tutti quei monitor più grandi, una stanza più grande su cui inserire la scrivania più grande e un boss perspicace.
Tino

1
@ErikE Scrollbars, per vi? Stai scherzando?
Tino,

21

Questa roba è difficile, ma ecco i miei pensieri attuali:

Se la tua API restituisce Object, allora non c'è niente che tu possa fare, indipendentemente dal fatto, lancerai ciecamente l'oggetto. Consenti a Java di lanciare ClassCastExceptions o puoi controllare tu stesso ogni elemento e lanciare Assertions o IllegalArgumentExceptions o alcuni di questi, ma questi controlli di runtime sono tutti equivalenti. Devi sopprimere il cast di deselezione del tempo di compilazione, indipendentemente da ciò che fai in fase di esecuzione.

Preferirei solo eseguire il cast cieco e consentire a JVM di eseguire il controllo di runtime per me poiché "sappiamo" cosa dovrebbe restituire l'API e di solito siamo disposti ad assumere che l'API funzioni. Usa generici ovunque sopra il cast, se ne hai bisogno. Non stai davvero acquistando nulla lì poiché hai ancora il cast singolo cieco, ma almeno puoi usare i generici da lì in poi in modo che JVM possa aiutarti a evitare i cast ciechi in altri pezzi del tuo codice.

In questo caso particolare, presumibilmente puoi vedere la chiamata a SetAttribute e vedere il tipo sta entrando, quindi semplicemente lanciare alla cieca il tipo allo stesso modo non è immorale. Aggiungi un commento che fa riferimento a SetAttribute ed esegui l'operazione.


16

Ecco un esempio abbreviato che evita l'avvertimento "non controllato" utilizzando due strategie menzionate in altre risposte.

  1. Trasmettere la Classe del tipo di interesse come parametro in fase di esecuzione ( Class<T> inputElementClazz). Quindi puoi usare:inputElementClazz.cast(anyObject);

  2. Per la trasmissione di tipi di una raccolta, utilizzare il carattere jolly? invece di un generico tipo T per riconoscere che in effetti non si conosce quale tipo di oggetti aspettarsi dal codice legacy ( Collection<?> unknownTypeCollection). Dopotutto, questo è ciò che l'avviso "cast non controllato" vuole dirci: non possiamo essere sicuri di ottenere un Collection<T>, quindi la cosa onesta da fare è usare un Collection<?>. Se assolutamente necessario, è ancora possibile creare una raccolta di tipo noto ( Collection<T> knownTypeCollection).

Il codice legacy interfacciato nell'esempio seguente ha un attributo "input" in StructuredViewer (StructuredViewer è un widget albero o tabella, "input" è il modello dati dietro di esso). Questo "input" potrebbe essere qualsiasi tipo di raccolta Java.

public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
    IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
    // legacy code returns an Object from getFirstElement,
    // the developer knows/hopes it is of type inputElementClazz, but the compiler cannot know
    T firstElement = inputElementClazz.cast(selection.getFirstElement());

    // legacy code returns an object from getInput, so we deal with it as a Collection<?>
    Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();

    // for some operations we do not even need a collection with known types
    unknownTypeCollection.remove(firstElement);

    // nothing prevents us from building a Collection of a known type, should we really need one
    Collection<T> knownTypeCollection = new ArrayList<T>();
    for (Object object : unknownTypeCollection) {
        T aT = inputElementClazz.cast(object);
        knownTypeCollection.add(aT);
        System.out.println(aT.getClass());
    }

    structuredViewer.refresh();
}

Naturalmente, il codice sopra riportato può dare errori di runtime se utilizziamo il codice legacy con tipi di dati errati (ad es. Se impostiamo un array come "input" di StructuredViewer anziché una raccolta Java).

Esempio di chiamata del metodo:

dragFinishedStrategy.dragFinished(viewer, Product.class);

13

Nel mondo della sessione HTTP non puoi davvero evitare il cast, poiché l'API è scritta in questo modo (accetta e restituisce solo Object).

Con un po 'di lavoro puoi facilmente evitare il cast incontrollato', comunque. Ciò significa che si trasformerà in un cast tradizionale dando un ClassCastExceptiondiritto lì in caso di errore). Un'eccezione non selezionata potrebbe trasformarsi in a CCEin qualsiasi momento in un secondo momento anziché nel punto del cast (questo è il motivo per cui è un avviso separato).

Sostituisci HashMap con una classe dedicata:

import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Attributes extends AbstractMap<String, String> {
    final Map<String, String> content = new HashMap<String, String>();

    @Override
    public Set<Map.Entry<String, String>> entrySet() {
        return content.entrySet();
    }

    @Override
    public Set<String> keySet() {
        return content.keySet();
    }

    @Override
    public Collection<String> values() {
        return content.values();
    }

    @Override
    public String put(final String key, final String value) {
        return content.put(key, value);
    }
}

Quindi esegui il cast in quella classe anziché Map<String,String>e tutto verrà controllato nel punto esatto in cui scrivi il codice. Nessun imprevisto in ClassCastExceptionsseguito.


Questa è una risposta davvero utile.
GPrathap,

10

In Android Studio se si desidera disabilitare l'ispezione è possibile utilizzare:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();

2
Funziona anche nell'IDE IntelliJ
neXus

8

In questo caso particolare, non memorizzerei Maps direttamente in HttpSession, ma piuttosto un'istanza della mia classe, che a sua volta contiene una mappa (un dettaglio di implementazione della classe). Quindi puoi essere sicuro che gli elementi nella mappa sono del tipo giusto.

Ma se vuoi comunque verificare che i contenuti della Mappa siano del tipo giusto, puoi usare un codice come questo:

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("a", 1);
    map.put("b", 2);
    Object obj = map;

    Map<String, Integer> ok = safeCastMap(obj, String.class, Integer.class);
    Map<String, String> error = safeCastMap(obj, String.class, String.class);
}

@SuppressWarnings({"unchecked"})
public static <K, V> Map<K, V> safeCastMap(Object map, Class<K> keyType, Class<V> valueType) {
    checkMap(map);
    checkMapContents(keyType, valueType, (Map<?, ?>) map);
    return (Map<K, V>) map;
}

private static void checkMap(Object map) {
    checkType(Map.class, map);
}

private static <K, V> void checkMapContents(Class<K> keyType, Class<V> valueType, Map<?, ?> map) {
    for (Map.Entry<?, ?> entry : map.entrySet()) {
        checkType(keyType, entry.getKey());
        checkType(valueType, entry.getValue());
    }
}

private static <K> void checkType(Class<K> expectedType, Object obj) {
    if (!expectedType.isInstance(obj)) {
        throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj);
    }
}

1
Eccezionale; Penso di poterlo combinare con la mia risposta per parametrizzarla ed evitare di dover eliminare del tutto gli avvisi!
skiphoppy,

1
+1 probabilmente la migliore ricetta (facile da capire e mantenere) per farlo in sicurezza con i controlli di runtime
Tino

8

La funzione di utilità Objects.Unchecked nella risposta sopra di Esko Luontola è un ottimo modo per evitare il disordine del programma.

Se non si desidera SuppressWarnings su un intero metodo, Java ti costringe a inserirlo in un locale. Se hai bisogno di un cast su un membro, può portare a un codice come questo:

@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;

L'uso dell'utilità è molto più pulito ed è ancora ovvio cosa stai facendo:

this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());

NOTA: ritengo sia importante aggiungere che a volte l'avviso significa che stai facendo qualcosa di sbagliato come:

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1);
Object intListObject = intList; 

 // this line gives an unchecked warning - but no runtime error
ArrayList<String> stringList  = (ArrayList<String>) intListObject;
System.out.println(stringList.get(0)); // cast exception will be given here

Ciò che il compilatore ti sta dicendo è che questo cast NON verrà verificato in fase di runtime, quindi non verrà generato alcun errore di runtime fino a quando non provi ad accedere ai dati nel contenitore generico.


5

La soppressione degli avvisi non è una soluzione. Non dovresti fare un casting a due livelli in una frase.

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {

    // first, cast the returned Object to generic HashMap<?,?>
    HashMap<?, ?> theHash = (HashMap<?, ?>)session.getAttribute("attributeKey");

    // next, cast every entry of the HashMap to the required type <String, String>
    HashMap<String, String> returingHash = new HashMap<>();
    for (Entry<?, ?> entry : theHash.entrySet()) {
        returingHash.put((String) entry.getKey(), (String) entry.getValue());
    }
    return returingHash;
}

1
La sua domanda di cinque anni? Hai bisogno di fare così tanto lavoro? Dato che Java ha la cancellazione del tipo, la seconda hashmap dovrebbe essere identica alla prima in fase di esecuzione; Penso che sarebbe più efficiente ed eviterei la copia se avessi semplicemente passato le voci e verificato che fossero tutte istanze di stringhe. Oppure, TBH, ispeziona l'origine del JAR servlet che stai utilizzando e verifica che metta sempre stringhe.
Rup,

1
Ad oggi vedo questo avviso nei progetti. Il suo problema non era la verifica del tipo, ma un avvertimento causato da un "inserimento" in una mappa non registrata.
abbas

2

Un'ipotesi se pubblichi il tuo codice può dirlo con certezza ma potresti aver fatto qualcosa del genere

HashMap<String, Object> test = new HashMap();

che produrrà l'avvertimento quando è necessario farlo

HashMap<String, Object> test = new HashMap<String, Object>();

potrebbe valere la pena guardarlo

Generici nel linguaggio di programmazione Java

se non hai familiarità con ciò che deve essere fatto.


1
Sfortunatamente non è una situazione così facile. Codice aggiunto
skiphoppy,

1
Sono venuto qui in cerca di una risposta a un problema leggermente diverso: e mi hai detto esattamente di cosa avevo bisogno! Grazie!
staticsan

2

Potrei aver frainteso la domanda (un esempio e un paio di righe circostanti sarebbero carine), ma perché non usi sempre un'interfaccia appropriata (e Java5 +)? Non vedo alcun motivo per cui vorresti mai lanciare un HashMapinvece di un Map<KeyType,ValueType>. In realtà, non riesco a immaginare alcun motivo per impostare il tipo di una variabile HashMapinvece di Map.

E perché la fonte è Object? È un tipo di parametro di una raccolta legacy? In tal caso, utilizzare generici e specificare il tipo desiderato.


2
Sono abbastanza sicuro che il passaggio a Map in questo caso non cambierebbe nulla, ma grazie per il suggerimento di programmazione, che potrebbe cambiare il modo in cui faccio alcune cose, in meglio. La fonte dell'oggetto è un'API su cui non ho alcun controllo (codice aggiunto).
skiphoppy,

2

Se devo usare un'API che non supporta Generics .. Provo a isolare quelle chiamate nelle routine wrapper con il minor numero di linee possibile. Quindi uso l'annotazione SuppressWarnings e allo stesso tempo aggiungo anche i cast di sicurezza del tipo.

Questa è solo una preferenza personale per mantenere le cose il più pulite possibile.


2

Prendi questo, è molto più veloce della creazione di una nuova HashMap, se è già una, ma comunque sicura, poiché ogni elemento viene verificato rispetto al suo tipo ...

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> toHashMap(Object input, Class<K> key, Class<V> value) {
       assert input instanceof Map : input;

       for (Map.Entry<?, ?> e : ((HashMap<?, ?>) input).entrySet()) {
           assert key.isAssignableFrom(e.getKey().getClass()) : "Map contains invalid keys";
           assert value.isAssignableFrom(e.getValue().getClass()) : "Map contains invalid values";
       }

       if (input instanceof HashMap)
           return (HashMap<K, V>) input;
       return new HashMap<K, V>((Map<K, V>) input);
    }

key.isAssignableFrom(e.getKey().getClass())può essere scritto comekey.isInstance(e.getKey())
user102008 il

1

Basta selezionarlo prima di lanciarlo.

Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;  

E per chiunque lo chieda, è abbastanza comune ricevere oggetti in cui non si è sicuri del tipo. Molte implementazioni "SOA" legacy passano intorno a vari oggetti di cui non dovresti sempre fidarti. (Gli orrori!)

MODIFICA Una volta modificato il codice di esempio in modo che corrisponda agli aggiornamenti del poster, e dopo alcuni commenti vedo che instanceof non funziona bene con i generici. Tuttavia, la modifica del controllo per convalidare l'oggetto esterno sembra funzionare bene con il compilatore della riga di comando. Esempio rivisto ora pubblicato.


8
Sfortunatamente, i generici lo rendono impossibile. Non è solo una HashMap, è una HashMap con informazioni sul tipo. E se elimino tali informazioni, spingerò gli avvisi altrove.
skiphoppy,

1

Quasi ogni problema in Informatica può essere risolto aggiungendo un livello di riferimento indiretto * o qualcosa del genere.

Quindi introdurre un oggetto non generico di livello superiore a Map. Senza contesto non sembrerà molto convincente, ma comunque:

public final class Items implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private Map<String,String> map;
    public Items(Map<String,String> map) {
        this.map = New.immutableMap(map);
    }
    public Map<String,String> getMap() {
        return map;
    }
    @Override public String toString() {
        return map.toString();
    }
}

public final class New {
    public static <K,V> Map<K,V> immutableMap(
        Map<? extends K, ? extends V> original
    ) {
        // ... optimise as you wish...
        return Collections.unmodifiableMap(
            new HashMap<String,String>(original)
        );
    }
}

static Map<String, String> getItems(HttpSession session) {
    Items items = (Items)
        session.getAttribute("attributeKey");
    return items.getMap();
}

* Tranne troppi livelli di riferimento indiretto.


1
La citazione è attribuita al defunto professor David Wheeler. en.wikipedia.org/wiki/…
Stephen C

1

Ecco un modo per gestirlo quando eseguo l'override equals()dell'operazione.

public abstract class Section<T extends Section> extends Element<Section<T>> {
    Object attr1;

    /**
    * Compare one section object to another.
    *
    * @param obj the object being compared with this section object
    * @return true if this section and the other section are of the same
    * sub-class of section and their component fields are the same, false
    * otherwise
    */       
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            // this exists, but obj doesn't, so they can't be equal!
            return false;
        }

        // prepare to cast...
        Section<?> other;

        if (getClass() != obj.getClass()) {
            // looks like we're comparing apples to oranges
            return false;
        } else {
            // it must be safe to make that cast!
            other = (Section<?>) obj;
        }

        // and then I compare attributes between this and other
        return this.attr1.equals(other.attr1);
    }
}

Questo sembra funzionare in Java 8 (anche compilato con -Xlint:unchecked)


0

Se si è sicuri che il tipo restituito da session.getAttribute () sia HashMap, non è possibile eseguire il typecast in quel tipo esatto, ma fare affidamento solo sul controllo della HashMap generica

HashMap<?,?> getItems(javax.servlet.http.HttpSession session) {  
    HashMap<?,?> theHash = (HashMap<?,?>)session.getAttribute("attributeKey");
    return theHash;
} 

Eclipse sorprenderà quindi gli avvisi, ma ovviamente questo può portare a errori di runtime che possono essere difficili da eseguire il debug. Uso questo approccio non solo in contesti operativi critici.


0

Due modi, uno che evita completamente il tag, l'altro che utilizza un metodo di utilità cattivo ma carino.
Il problema sono le collezioni pre-generiche ...
Credo che la regola empirica sia: "lanciare oggetti una cosa alla volta" - ciò che significa quando si tenta di utilizzare le classi non elaborate in un mondo generico è che, poiché non si sa cosa è in questa mappa <?,?> (e in effetti la JVM potrebbe persino scoprire che non è nemmeno una mappa!), è ovvio quando ci pensi che non puoi lanciarla. Se avevi una mappa <String,?> Map2, allora HashSet <String> keys = (HashSet <String>) map2.keySet () non ti avvisa, nonostante sia un "atto di fede" per il compilatore (perché potrebbe rivelarsi un TreeSet) ... ma è solo un singolo atto di fede.

PS all'obiezione che iterando come nel mio primo modo "è noioso" e "richiede tempo", la risposta è "no pain no gain": una raccolta generica è garantita per contenere Map.Entry <String, String> s, e niente altro. Devi pagare per questa garanzia. Quando si usano sistemicamente i generici questo pagamento, magnificamente, assume la forma di conformità alla codifica, non di tempo macchina!
Una scuola di pensiero potrebbe dire che dovresti configurare le impostazioni di Eclipse per commettere errori di cast non controllati, piuttosto che avvertimenti. In tal caso dovresti usare il mio primo modo.

package scratchpad;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

public class YellowMouse {

    // First way

    Map<String, String> getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) {
      Map<?, ?> theHash = (Map<?, ?>)session.getAttribute("attributeKey");

      Map<String, String> yellowMouse = new HashMap<String, String>();
      for( Map.Entry<?, ?> entry : theHash.entrySet() ){
        yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() );
      }

      return yellowMouse;
    }


    // Second way

    Map<String, String> getHashMapUsingNaughtyButNiceUtilityMethod(HttpSession session) {
      return uncheckedCast( session.getAttribute("attributeKey") );
    }


    // NB this is a utility method which should be kept in your utility library. If you do that it will
    // be the *only* time in your entire life that you will have to use this particular tag!!

    @SuppressWarnings({ "unchecked" })
    public static synchronized <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }


}

il fatto che non possiedi i privilegi di commento non ti consente di modificare le risposte degli altri per aggiungere i tuoi commenti; modifichi le risposte degli altri per migliorarle nella formattazione, nella sintassi, ..., non per aggiungere la tua opinione su di esse. Quando raggiungerai i 50 rappresentanti sarai in grado di commentare ovunque, nel frattempo sono abbastanza sicuro che puoi resistere (o, se proprio non puoi, scrivi i tuoi commenti alle risposte esistenti nel tuo post). (nota per gli altri: scrivo perché ho visto - e respinto - i suoi proposti commenti-modifiche ad altri post negli strumenti di moderazione)
Matteo Italia

-1

In questo modo gli avvisi scompaiono ...

 static Map<String, String> getItems(HttpSession session) {
        HashMap<?, ?> theHash1 = (HashMap<String,String>)session.getAttribute("attributeKey");
        HashMap<String,String> theHash = (HashMap<String,String>)theHash1;
    return theHash;
}

1
No non lo fa. In realtà, questo crea due avvisi in cui prima ce n'era uno.
Stijn de Witt,

Ah ok. Non so perché l'ho pensato.
lukewm,

-3

Soluzione: disabilitare questo avviso in Eclipse. Non @SuppressWarnings, disabilitalo completamente.

Molte delle "soluzioni" presentate sopra sono fuori linea, rendendo il codice illeggibile per sopprimere un avviso sciocco.


9
Posso chiedere perchè? la disabilitazione globale di un avviso nasconderà altri luoghi in cui questo problema è reale. l'aggiunta di a @SuppressWarningsnon rende affatto illeggibile il codice.
MByD,
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.