Typensicherheit: Unkontrollierte Besetzung

264

In meiner Kontextdatei für die Frühlingsanwendung habe ich Folgendes:

<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>

In der Java-Klasse sieht die Implementierung folgendermaßen aus:

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

In Eclipse sehe ich eine Warnung, die besagt:

Typensicherheit: Deaktivierte Umwandlung von Objekt in HashMap

Was habe ich falsch gemacht? Wie löse ich das Problem?

DragonBorn
quelle
Ich habe mir eine Routine ausgedacht, um die Umwandlung in parametrisierte HashMap zu überprüfen, wodurch die ungeprüfte Warnung zur Umwandlung beseitigt wird: Link Ich würde sagen, dies ist die "richtige" Lösung, aber ob es sich lohnt oder nicht, ist fraglich. :)
Skiphoppy
Mögliches Duplikat von Wie gehe ich mit ungeprüften Besetzungswarnungen um?
Angelo Fuchs

Antworten:

248

Zunächst einmal verschwenden Sie mit dem neuen HashMapErstellungsaufruf Speicherplatz . In Ihrer zweiten Zeile wird der Verweis auf diese erstellte Hashmap vollständig ignoriert, sodass sie dem Garbage Collector zur Verfügung steht. Also, tu das nicht, benutze:

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

Zweitens beschwert sich der Compiler, dass Sie das Objekt in a umwandeln HashMap umwandeln, ohne zu überprüfen, ob es a ist HashMap. Aber selbst wenn Sie tun würden:

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

Sie würden diese Warnung wahrscheinlich immer noch erhalten. Das Problem ist, getBeankehrt zurück Object, so dass es unbekannt ist, um welchen Typ es sich handelt. Eine HashMapdirekte Konvertierung würde das Problem im zweiten Fall nicht verursachen (und möglicherweise würde es im ersten Fall keine Warnung geben, ich bin mir nicht sicher, wie pedantisch der Java-Compiler mit Warnungen für Java 5 ist). Sie konvertieren es jedoch in a HashMap<String, String>.

HashMaps sind wirklich Maps, die ein Objekt als Schlüssel nehmen und ein Objekt als Wert haben. HashMap<Object, Object> wenn Sie so wollen. Daher gibt es keine Garantie dafür, dass Ihre Bean, wenn Sie sie erhalten, als dargestellt werden kann, HashMap<String, String>weil dies möglich ist, HashMap<Date, Calendar>da die zurückgegebene nicht generische Darstellung beliebige Objekte enthalten kann.

Wenn der Code kompiliert wird und Sie String value = map.get("thisString");fehlerfrei ausführen können, machen Sie sich keine Sorgen über diese Warnung. Wenn die Zuordnung jedoch nicht vollständig aus Zeichenfolgenschlüsseln zu Zeichenfolgenwerten besteht, erhalten Sie ClassCastExceptionzur Laufzeit eine, da die Generika dies in diesem Fall nicht blockieren können.

MetroidFan2002
quelle
12
Dies ist eine Weile her, aber ich habe nach einer Antwort auf die Typprüfung eines Sets <CustomClass> vor einer Umwandlung gesucht, und Sie können keine Instanz eines parametrisierten Generikums erstellen. zB if (event.getTarget-Instanz von Set <CustomClass>) Sie können nur check a generic mit einem? und das wird die Cast-Warnung nicht entfernen. zB if (event.getTarget Instanz von Set <?>)
Knoblauchman
314

Das Problem ist, dass eine Umwandlung eine Laufzeitprüfung ist - aber aufgrund der Typlöschung gibt es zur Laufzeit tatsächlich keinen Unterschied zwischen a HashMap<String,String>und HashMap<Foo,Bar>für andere Foound Bar.

Benutze @SuppressWarnings("unchecked")und halte deine Nase. Oh, und Kampagne für reifizierte Generika in Java :)

Jon Skeet
quelle
14
Ich werde Javas reifizierte Generika über untypisierte NSMutableWhatever nehmen, was sich an jedem Tag der Woche wie ein zehnjähriger Rücksprung anfühlt. Zumindest versucht es Java.
Dan Rosenstark
12
Genau. Wenn Sie auf einer Typprüfung bestehen, kann dies nur mit HashMap <?,?> Durchgeführt werden. Dadurch wird die Warnung nicht entfernt, da dies mit der Nicht-Typprüfung der generischen Typen identisch ist. Es ist nicht das Ende der Welt, aber ärgerlich, dass Sie erwischt werden, wenn Sie eine Warnung unterdrücken oder damit leben.
Knoblauchmann
5
@ JonSkeet Was ist ein reifiziertes Generikum?
SasQ
88

Wie aus den obigen Meldungen hervorgeht, kann die Liste nicht zwischen a List<Object>und a List<String>oder unterschieden werden List<Integer>.

Ich habe diese Fehlermeldung für ein ähnliches Problem gelöst:

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

mit den folgenden:

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

Erläuterung: Bei der ersten Typkonvertierung wird überprüft, ob es sich bei dem Objekt um eine Liste handelt, ohne dass die darin enthaltenen Typen berücksichtigt werden müssen (da die internen Typen auf Listenebene nicht überprüft werden können). Die zweite Konvertierung ist jetzt erforderlich, da der Compiler nur weiß, dass die Liste bestimmte Objekte enthält. Dadurch wird der Typ jedes Objekts in der Liste beim Zugriff überprüft.

Larry Landry
quelle
3
Du hast recht, mein Freund. Anstatt die Liste zu übertragen, iterieren Sie sie einfach und setzen Sie jedes Element um. Die Warnung wird nicht angezeigt.
Juan Isaza
2
Dies entfernte die Warnung, aber ich bin immer noch nicht sicher: P
mumair
1
Ja, es fühlt sich an, als würde man dem Compiler die Augen verbinden, aber nicht zur Laufzeit: D Ich sehe also keinen Unterschied zwischen diesem und @SuppressWarnings ("nicht
markiert
1
Das ist großartig! Der Hauptunterschied bei der Verwendung von @SupressWarning besteht darin, dass durch die Verwendung der Annotation die Warnung aus Ihren IDE- und Code-Analyse-Tools entfernt wird. Wenn Sie jedoch die Kompilierung des -Werror-Flags verwenden, tritt noch ein Fehler auf. Mit diesem Ansatz werden beide Warnungen behoben.
Edu Costa
30

Eine Warnung ist genau das. Eine Warnung. Manchmal sind Warnungen irrelevant, manchmal nicht. Sie werden verwendet, um Ihre Aufmerksamkeit auf etwas zu lenken, von dem der Compiler glaubt, dass es ein Problem sein könnte, aber möglicherweise nicht.

Im Fall von Casts wird in diesem Fall immer eine Warnung ausgegeben. Wenn Sie absolut sicher sind, dass eine bestimmte Besetzung sicher ist, sollten Sie eine Anmerkung wie diese (ich bin mir der Syntax nicht sicher) kurz vor der Zeile hinzufügen:

@SuppressWarnings (value="unchecked")
David M. Karr
quelle
14
-1: Eine Warnung sollte niemals akzeptiert werden. Oder unterdrücken Sie diese Art von Warnungen oder beheben Sie sie. Es wird der Moment kommen, in dem Sie zu viele Warnungen haben und die relevanten nicht einmal sehen werden.
Ezdazuzena
10
Sie können Klassenumwandlungswarnungen nicht wirklich vermeiden, wenn Sie parametrisierte Generika wie Map umwandeln. Dies ist also die beste Antwort auf die ursprüngliche Frage.
Hammelfleisch
9

Sie erhalten diese Nachricht, weil getBean eine Objektreferenz zurückgibt und Sie sie in den richtigen Typ umwandeln. Java 1.5 gibt Ihnen eine Warnung. Das ist die Natur der Verwendung von Java 1.5 oder besser mit Code, der so funktioniert. Frühling hat die typsichere Version

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

auf seiner Aufgabenliste.

David Nehme
quelle
6

Wenn Sie die Warnungen wirklich entfernen möchten, können Sie eine Klasse erstellen, die von der generischen Klasse ausgeht.

Zum Beispiel, wenn Sie versuchen, zu verwenden

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

Sie können eine neue Klasse wie diese erstellen

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

Dann, wenn Sie verwenden

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

Der Compiler kennt die (nicht mehr generischen) Typen und es wird keine Warnung ausgegeben. Dies ist möglicherweise nicht immer die perfekte Lösung. Einige argumentieren möglicherweise, dass diese Art der Niederlage den Zweck generischer Klassen zunichte macht. Sie verwenden jedoch immer noch denselben Code aus der generischen Klasse. Sie deklarieren lediglich zur Kompilierungszeit, welcher Typ Sie möchten verwenden.

Hase
quelle
3

Die Lösung, um die ungeprüfte Warnung zu vermeiden:

class MyMap extends HashMap<String, String> {};
someMap = (MyMap)getApplicationContext().getBean("someMap");
ochakov
quelle
Sieht aus wie ein Hack, keine Lösung.
Malwinder Singh
1
- Die serialisierbare Klasse MyMap deklariert kein statisches endgültiges serialVersionUID-Feld vom Typ long: {
Ulterior
1

Eine andere Lösung, wenn Sie feststellen, dass Sie dasselbe Objekt häufig umwandeln und Ihren Code nicht verunreinigen @SupressWarnings("unchecked")möchten, besteht darin, eine Methode mit der Anmerkung zu erstellen. Auf diese Weise zentralisieren Sie die Besetzung und reduzieren hoffentlich die Fehlerwahrscheinlichkeit.

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

Der folgende Code verursacht Typensicherheitswarnung

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

Problemumgehung

Erstellen Sie ein neues Kartenobjekt, ohne die Parameter zu erwähnen, da der in der Liste enthaltene Objekttyp nicht überprüft wird.

Schritt 1: Erstellen Sie eine neue temporäre Karte

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

Schritt 2: Instanziieren Sie die Hauptkarte

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

Schritt 3: Iterieren Sie die temporäre Karte und legen Sie die Werte in der Hauptkarte fest

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

Was habe ich falsch gemacht? Wie löse ich das Problem?

Hier :

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

Sie verwenden eine Legacy-Methode, die wir im Allgemeinen nicht verwenden möchten, da diese Folgendes zurückgibt Object:

Object getBean(String name) throws BeansException;

Die Methode, um eine Bohne aus einer Bohnenfabrik zu erhalten (für Singleton) / zu erstellen (für einen Prototyp), ist:

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

Verwenden Sie es wie:

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

wird kompiliert, aber immer noch mit einer ungeprüften Konvertierungswarnung, da nicht alle MapObjekte unbedingt sindMap<String, String> .

Aber <T> T getBean(String name, Class<T> requiredType) throws BeansException; reicht jedoch in generischen Bean-Klassen wie generischen Sammlungen nicht aus, da hierfür mehr als eine Klasse als Parameter angegeben werden muss: der Sammlungstyp und seine generischen Typen.

In einem solchen Szenario und im Allgemeinen besteht ein besserer Ansatz darin, nicht direkt zu verwenden BeanFactory Methoden , sondern das Framework die Bean injizieren zu lassen.

Die Bohnendeklaration:

@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;
    }
}

Die Bohneninjektion:

@Autowired
@Qualifier("someMap")
Map<String, String> someMap;
davidxxx
quelle