Wie gehe ich mit ungeprüften Besetzungswarnungen um?

611

Eclipse warnt mich vor folgendem Formular:

Typensicherheit: Deaktivierte Umwandlung von Objekt in HashMap

Dies ist von einem Aufruf einer API, über die ich keine Kontrolle habe, welche Objekt zurückgibt:

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

Ich möchte Eclipse-Warnungen nach Möglichkeit vermeiden, da sie theoretisch zumindest auf ein potenzielles Codeproblem hinweisen. Ich habe jedoch noch keinen guten Weg gefunden, diesen zu beseitigen. Ich kann die einzelne betroffene Zeile selbst in eine Methode extrahieren und @SuppressWarnings("unchecked")zu dieser Methode hinzufügen , wodurch die Auswirkungen eines Codeblocks begrenzt werden, in dem ich Warnungen ignoriere. Irgendwelche besseren Optionen? Ich möchte diese Warnungen in Eclipse nicht deaktivieren.

Bevor ich zum Code kam, war es einfacher, aber dennoch provozierte Warnungen:

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

Das Problem war anderswo, als Sie versuchten, den Hash zu verwenden, erhielten Sie Warnungen:

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.
Skiphoppy
quelle
Wenn Sie HttpSession so verwenden, lesen Sie den Artikel von Brian Goetz zu diesem Thema: ibm.com/developerworks/library/j-jtp09238.html
Tom Hawtin - Tackline
Wenn eine ungeprüfte Besetzung unvermeidbar ist, ist es eine gute Idee, sie eng mit etwas zu koppeln, das logisch ihren Typ darstellt (wie eine enumoder sogar Instanzen von Class<T>), damit Sie einen Blick darauf werfen und wissen, dass sie sicher ist.
Philip Guin
4
Verwandte / Betrogene: Typensicherheit: Unkontrollierte Besetzung
blahdiblah
3
Mögliches Duplikat der Typensicherheit: Ungeprüfte Besetzung
Raedwald
Ich würde hinzufügen, ich fand, dass ich @SuppressWarnings ("nicht markiert") nur auf Methodenebene hinzufügen konnte, die den fehlerhaften Code enthält. Also habe ich den Code in eine Routine aufgeteilt, in der ich dies tun musste. Ich dachte immer, Sie könnten dies unmittelbar über der fraglichen Linie tun.
JGFMK

Antworten:

557

Die offensichtliche Antwort ist natürlich nicht, die ungeprüfte Besetzung zu machen.

Wenn dies unbedingt erforderlich ist, versuchen Sie zumindest, den Umfang der @SuppressWarningsAnmerkung einzuschränken. Laut seinen Javadocs kann es auf lokale Variablen gehen; Auf diese Weise wirkt sich dies nicht einmal auf die gesamte Methode aus.

Beispiel:

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

Es gibt keine Möglichkeit zu bestimmen, ob die Mapwirklich die generischen Parameter haben sollten <String, String>. Sie müssen vorher wissen, wie die Parameter lauten sollen (oder Sie werden herausfinden, wann Sie eine erhalten ClassCastException). Aus diesem Grund generiert der Code eine Warnung, da der Compiler möglicherweise nicht wissen kann, ob er sicher ist.

Michael Myers
quelle
112
+1 für den Hinweis, dass es sich um lokale Variablen handeln kann. Eclipse bietet nur an, es der gesamten Methode hinzuzufügen ...
thSoft
17
Eclipse 3.7 (Indigo) unterstützt das Hinzufügen von ungeprüften lokalen Variablen.
Sweetfa
78
Die Warnung ist nicht nur deshalb so, weil der Compiler nicht weiß, dass die Besetzung sicher ist. Beispielsweise String s = (String) new Object() ;wird keine Warnung angezeigt, obwohl der Compiler nicht weiß, dass die Umwandlung sicher ist. Die Warnung ist, weil der Compiler (a) nicht weiß, dass die Umwandlung sicher ist UND (b) zum Zeitpunkt der Umwandlung keine vollständige Laufzeitprüfung generiert. Es wird überprüft, ob es sich um eine handelt Hashmap, aber es wird nicht überprüft, ob es sich um eine handelt HashMap<String,String>.
Theodore Norvell
9
Obwohl die Besetzung und die Warnung für die Zuweisung sind , muss die Anmerkung leider auf der Variablendeklaration stehen ... Wenn sich die Deklaration und die Zuweisung also an verschiedenen Stellen befinden (z. B. außerhalb bzw. innerhalb eines 'try'-Blocks) Eclipse generiert jetzt zwei Warnungen: die ursprüngliche ungeprüfte Besetzung und eine neue Diagnose "Unnötige Anmerkungen".
Ti Strga
6
Eine Problemumgehung für die Annotation, die der lokalen Variablendeklaration beiliegen muss, die sich in einem anderen Bereich in einer anderen Zeile als der tatsächlichen Umwandlung befinden kann, besteht darin, eine lokale Variable innerhalb des Bereichs der Umwandlung zu erstellen, um die Umwandlung in derselben Zeile auszuführen als die Erklärung. Weisen Sie diese Variable dann der tatsächlichen Variablen zu, die sich in einem anderen Bereich befindet. Dies ist die Methode, mit der ich auch die Warnung bei einer Umwandlung in eine Instanzvariable unterdrückt habe, da die Annotation auch hier nicht angewendet werden kann.
Jeff Lockhart
168

Leider gibt es hier keine guten Möglichkeiten. Denken Sie daran, das Ziel all dessen ist es, die Typensicherheit zu gewährleisten. " Java Generics " bietet eine Lösung für den Umgang mit nicht generisierten Legacy-Bibliotheken, und es gibt eine, die in Abschnitt 8.2 als "Leerschleifentechnik" bezeichnet wird. Machen Sie im Grunde die unsichere Besetzung und unterdrücken Sie die Warnung. Dann durchlaufen Sie die Karte wie folgt:

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

Wenn ein unerwarteter Typ auftritt, erhalten Sie eine Laufzeit ClassCastException, die jedoch zumindest in der Nähe der Ursache des Problems auftritt.

Julien Chastang
quelle
6
Viel, viel bessere Antwort als die von Skiphoppy aus mehreren Gründen: 1) Dieser Code ist viel, viel kürzer. 2) Dieser Code löst tatsächlich wie erwartet ClassCastException aus. 3) Dieser Code erstellt keine vollständige Kopie der Quellkarte. 4) Die Schleifen können einfach in eine separate Methode eingeschlossen werden, die in einem Assert verwendet wird, wodurch der Leistungsverlust im Produktionscode leicht beseitigt wird.
Stijn de Witt
6
Gibt es nicht die Möglichkeit, dass der Java-Compiler oder der JIT-Compiler entscheidet, dass die Ergebnisse dieses Codes nicht verwendet werden, und ihn "optimiert", indem er nicht kompiliert wird?
RenniePet
1
Es ist kein wirklich toter Code, wenn er möglicherweise eine Ausnahme auslösen kann. Ich weiß nicht genug über die heute verwendeten JIT-Compiler, um zu garantieren, dass keiner von ihnen dies durcheinander bringen würde, aber ich bin ziemlich zuversichtlich zu sagen, dass sie das nicht sollen.
GrandOpener
3
Dies garantiert immer noch keine Typensicherheit, da immer noch dieselbe Karte verwendet wird. Es könnte ursprünglich als Map <Objekt, Objekt> definiert worden sein, in dem zufällig Zeichenfolgen und Zahlen enthalten sind. Wenn später ein Boolescher Wert hinzugefügt wird, wird der Benutzer dieses Codes eine verwirrende und schwer zu verfolgende Überraschung erhalten. Die einzige Möglichkeit, die Typensicherheit zu gewährleisten, besteht darin, sie in eine neue Karte mit dem angeforderten Typ zu kopieren, die garantiert, was in die Karte aufgenommen werden darf.
user2219808
112

Beeindruckend; Ich glaube, ich habe die Antwort auf meine eigene Frage herausgefunden. Ich bin mir nur nicht sicher, ob es sich lohnt! :) :)

Das Problem ist, dass die Besetzung nicht überprüft wird. Sie müssen es also selbst überprüfen. Sie können einen parametrisierten Typ nicht einfach mit instanceof überprüfen, da die parametrisierten Typinformationen zur Laufzeit nicht verfügbar sind, da sie zur Kompilierungszeit gelöscht wurden.

Sie können jedoch jedes Element im Hash mit instanceof überprüfen und dabei einen neuen Hash erstellen, der typsicher ist. Und Sie werden keine Warnungen provozieren.

Dank mmyers und Esko Luontola habe ich den Code, den ich ursprünglich hier geschrieben habe, parametrisiert, sodass er irgendwo in eine Utility-Klasse eingepackt und für jede parametrisierte HashMap verwendet werden kann. Wenn Sie es besser verstehen möchten und mit Generika nicht sehr vertraut sind, empfehlen wir Ihnen, den Bearbeitungsverlauf dieser Antwort anzuzeigen.

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

Das ist viel Arbeit, möglicherweise für sehr wenig Belohnung ... Ich bin mir nicht sicher, ob ich es verwenden werde oder nicht. Ich würde mich über Kommentare freuen, ob die Leute denken, dass es sich lohnt oder nicht. Ich würde mich auch über Verbesserungsvorschläge freuen: Gibt es etwas Besseres, als AssertionErrors zu werfen? Gibt es etwas Besseres, das ich werfen könnte? Soll ich es zu einer geprüften Ausnahme machen?

Skiphoppy
quelle
68
Dieses Zeug ist verwirrend, aber ich denke, alles, was Sie getan haben, ist, ClassCastExceptions gegen AssertionErrors zu tauschen.
Dustin Getz
59
Alter, das ist es definitiv nicht wert! Stellen Sie sich den armen Trottel vor, der zurückkommen und Code mit diesem Durcheinander ändern muss. Ich mag es nicht, Warnungen zu unterdrücken, aber ich denke, das ist das geringere Übel hier.
Craig B
69
Es ist nicht nur so, dass es ein hässliches, verwirrendes Durcheinander ist (wenn Sie nicht vermeiden können, dass ein reichhaltiger Kommentar den Wartungsprogrammierer durchführt); Durch Iterieren über jedes Element in der Sammlung wird die Umwandlung von einer O (1) in eine O (n) -Operation umgewandelt. Dies ist etwas, das niemals zu erwarten wäre und leicht zu einer schrecklichen Verlangsamung des Mysteriums führen kann.
Dan spielt
22
@ DanNeely du bist richtig. Im Allgemeinen sollte dies niemals jemand tun.
Skiphoppy
4
Einige Kommentare ... die Methodensignatur ist falsch, weil sie nichts verdammt "wirft", sondern nur die vorhandene Karte in eine neue Karte kopiert. Es könnte wahrscheinlich auch überarbeitet werden, um eine Map zu akzeptieren und sich nicht auf HashMap selbst zu verlassen (dh Map nehmen und Map in der Methodensignatur zurückgeben, selbst wenn der interne Typ HashMap ist). Sie müssen das Casting oder die Speicherung in einer neuen Karte nicht wirklich durchführen. Wenn Sie keinen Assertionsfehler auslösen, enthält die angegebene Karte ab sofort die richtigen Typen. Das Erstellen einer neuen Karte mit den generischen Typen ist sinnlos, da Sie sie immer noch roh machen und alles setzen können.
MetroidFan2002
51

Gehen Sie in den Eclipse-Einstellungen zu Java-> Compiler-> Fehler / Warnungen-> Generische Typen und aktivieren Sie das Ignore unavoidable generic type problemsKontrollkästchen.

Dies erfüllt die Absicht der Frage, dh

Ich möchte Eclipse-Warnungen vermeiden ...

wenn nicht der Geist.

Dave
quelle
1
Ah, danke dafür :) Ich habe einen " uses unchecked or unsafe operations." Fehler erhalten javac, aber das Hinzufügen @SuppressWarnings("unchecked")machte Eclipse unglücklich und behauptete, die Unterdrückung sei unnötig. Wenn Sie dieses Kontrollkästchen deaktivieren , wird Eclipse aktiviert und javacverhält sich genauso, wie ich es wollte. Das explizite Unterdrücken der Warnung im Code ist viel klarer als das Unterdrücken überall in Eclipse.
dimo414
26

Sie können eine Dienstprogrammklasse wie die folgende erstellen und damit die nicht aktivierte Warnung unterdrücken.

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

Sie können es wie folgt verwenden:

import static Objects.uncheckedCast;
...

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

Weitere Diskussionen hierzu finden Sie hier: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html

Esko Luontola
quelle
18
kein Downvoting, aber der Wrapper fügt genau nichts hinzu, als nur die Warnung zu unterdrücken.
Dustin Getz
3
+1, da diese Lösung keine wertvollen Codezeilen verschwendet.
Tino
1
@ErikE Zu viel. Viel teurere größere und höher auflösende Monitore, um all diesen verschwendeten Leitungen Platz zu bieten, ein größerer Schreibtisch, auf den all diese größeren Monitore gestellt werden können, ein größerer Raum, in den der größere Schreibtisch gestellt werden kann, und ein aufschlussreicher Chef.
Tino
1
@ErikE Bildlaufleisten, für vi? Machst du Witze?
Tino
21

Dieses Zeug ist schwer, aber hier sind meine aktuellen Gedanken:

Wenn Ihre API Object zurückgibt, können Sie nichts tun - egal was passiert, Sie werden das Objekt blind umwandeln. Sie lassen Java ClassCastExceptions auslösen, oder Sie können jedes Element selbst überprüfen und Assertions oder IllegalArgumentExceptions oder ähnliches auslösen, aber diese Laufzeitprüfungen sind alle gleichwertig. Sie müssen die ungeprüfte Umwandlung der Kompilierungszeit unterdrücken , unabhängig davon, was Sie zur Laufzeit tun.

Ich würde es einfach vorziehen, blind zu besetzen und die JVM ihre Laufzeitprüfung für mich durchführen zu lassen, da wir "wissen", was die API zurückgeben soll, und normalerweise bereit sind anzunehmen, dass die API funktioniert. Verwenden Sie Generika überall über der Besetzung, wenn Sie sie benötigen. Sie kaufen dort eigentlich nichts, da Sie immer noch den Single Blind Cast haben, aber zumindest können Sie von da an Generika verwenden, damit die JVM Ihnen hilft, Blind Casts in anderen Teilen Ihres Codes zu vermeiden.

In diesem speziellen Fall können Sie vermutlich den Aufruf von SetAttribute sehen und sehen, dass der Typ eingeht. Es ist also nicht unmoralisch, den Typ auf dem Weg nach draußen blind zu machen. Fügen Sie einen Kommentar hinzu, der auf das SetAttribute verweist, und fertig.

Dustin Getz
quelle
16

Hier ist ein verkürztes Beispiel, das die Warnung "ungeprüfte Besetzung" vermeidet, indem zwei in anderen Antworten erwähnte Strategien angewendet werden.

  1. Übergeben Sie zur Laufzeit ( Class<T> inputElementClazz) die Klasse des interessierenden Typs als Parameter . Dann können Sie verwenden:inputElementClazz.cast(anyObject);

  2. Verwenden Sie für das Typ-Casting einer Sammlung den Platzhalter? anstelle eines generischen Typs T, um zu bestätigen, dass Sie tatsächlich nicht wissen, welche Art von Objekten Sie vom Legacy-Code erwarten sollen ( Collection<?> unknownTypeCollection). Immerhin ist es das, was die Warnung "ungeprüfte Besetzung" uns sagen will: Wir können nicht sicher sein, dass wir eine bekommen Collection<T>, also ist es ehrlich, eine zu verwenden Collection<?>. Bei Bedarf kann weiterhin eine Sammlung eines bekannten Typs erstellt werden ( Collection<T> knownTypeCollection).

Der im folgenden Beispiel angeschlossene Legacy-Code hat im StructuredViewer das Attribut "input" (StructuredViewer ist ein Baum- oder Tabellen-Widget, "input" ist das dahinter stehende Datenmodell). Diese "Eingabe" kann jede Art von Java-Sammlung sein.

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();
}

Natürlich kann der obige Code Laufzeitfehler verursachen, wenn wir den Legacy-Code mit den falschen Datentypen verwenden (z. B. wenn wir anstelle einer Java-Sammlung ein Array als "Eingabe" des StructuredViewer festlegen).

Beispiel für den Aufruf der Methode:

dragFinishedStrategy.dragFinished(viewer, Product.class);
StaticNoiseLog
quelle
13

In der HTTP-Sitzungswelt können Sie die Umwandlung nicht wirklich vermeiden, da die API so geschrieben ist (nur Takes und Returns Object).

Mit ein wenig Arbeit können Sie jedoch die ungeprüfte Besetzung leicht vermeiden. Dies bedeutet, dass es sich in eine traditionelle Besetzung verwandelt, die ClassCastExceptionim Fehlerfall ein Recht gibt. Eine ungeprüfte Ausnahme kann zu einem CCEspäteren Zeitpunkt anstelle des Besetzungspunkts zu einer Ausnahme werden (aus diesem Grund handelt es sich um eine separate Warnung).

Ersetzen Sie die HashMap durch eine dedizierte Klasse:

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

Dann in diese Klasse Map<String,String>umwandeln und alles wird genau an der Stelle überprüft, an der Sie Ihren Code schreiben. Kein unerwartetes ClassCastExceptionsspäter.

Joachim Sauer
quelle
Dies ist eine sehr hilfreiche Antwort.
GPrathap
10

Wenn Sie in Android Studio die Inspektion deaktivieren möchten, können Sie Folgendes verwenden:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
Jan Moravec
quelle
2
Dies funktioniert auch in der IntelliJ IDE
neXus
8

In diesem speziellen Fall würde ich Maps nicht direkt in der HttpSession speichern, sondern eine Instanz meiner eigenen Klasse, die wiederum eine Map (ein Implementierungsdetail der Klasse) enthält. Dann können Sie sicher sein, dass die Elemente in der Karte vom richtigen Typ sind.

Wenn Sie jedoch trotzdem überprüfen möchten, ob der Inhalt der Karte vom richtigen Typ ist, können Sie einen Code wie den folgenden verwenden:

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);
    }
}
Esko Luontola
quelle
1
Genial; Ich denke, ich kann das mit meiner Antwort kombinieren, um sie zu parametrisieren und zu vermeiden, dass Warnungen insgesamt unterdrückt werden müssen!
Skiphoppy
1
+1 wahrscheinlich bestes Rezept (leicht zu verstehen und zu pflegen), um es sicher mit Laufzeitprüfungen zu machen
Tino
8

Die Dienstprogrammfunktion Objects.Unchecked in der obigen Antwort von Esko Luontola ist eine hervorragende Möglichkeit, Programmunordnung zu vermeiden.

Wenn Sie die SuppressWarnings nicht für eine gesamte Methode verwenden möchten, werden Sie von Java gezwungen, sie auf eine lokale Methode zu setzen. Wenn Sie eine Besetzung für ein Mitglied benötigen, kann dies zu folgendem Code führen:

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

Die Verwendung des Dienstprogramms ist viel sauberer und es ist immer noch offensichtlich, was Sie tun:

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

HINWEIS: Ich halte es für wichtig hinzuzufügen, dass die Warnung manchmal wirklich bedeutet, dass Sie etwas falsch machen, wie:

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

Der Compiler teilt Ihnen mit, dass diese Umwandlung zur Laufzeit NICHT überprüft wird, sodass kein Laufzeitfehler ausgelöst wird, bis Sie versuchen, auf die Daten im generischen Container zuzugreifen.

Gonen I.
quelle
5

Die Unterdrückung von Warnungen ist keine Lösung. Sie sollten kein zweistufiges Casting in einer Anweisung durchführen.

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;
}
abbas
quelle
1
Seine fünfjährige Frage? Müssen Sie so viel arbeiten? Wenn Java vom Typ gelöscht wird, sollte die zweite Hashmap zur Laufzeit mit der ersten identisch sein. Ich denke, es wäre effizienter und würde die Kopie vermeiden, wenn Sie nur die Einträge durchlaufen und überprüfen würden, ob es sich bei allen um Instanzen von Zeichenfolgen handelt. Oder, TBH, überprüfen Sie die Quelle der von Ihnen verwendeten Servlet-JAR und stellen Sie sicher, dass immer nur Zeichenfolgen eingefügt werden.
Rup
1
Bis heute sehe ich diese Warnung in Projekten. Sein Problem war nicht die Überprüfung des Typs, sondern eine Warnung, die durch ein "Einfügen" in eine nicht gesendete Karte verursacht wurde.
Abbas
2

Eine schnelle Vermutung, ob Sie Ihren Code veröffentlichen, kann sicher sagen, aber Sie haben möglicherweise etwas in der Art von getan

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

Dies wird die Warnung erzeugen, wenn Sie dies tun müssen

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

es könnte einen Blick wert sein

Generika in der Programmiersprache Java

wenn Sie nicht wissen, was zu tun ist.

Mark Davidson
quelle
1
Leider ist das nicht so einfach. Code hinzugefügt.
Skiphoppy
1
Ich bin hierher gekommen, um eine Antwort auf ein etwas anderes Problem zu finden: und du hast mir genau gesagt, was ich brauchte! Vielen Dank!
staticsan
2

Ich habe die Frage vielleicht falsch verstanden (ein Beispiel und ein paar umgebende Zeilen wären nett), aber warum verwenden Sie nicht immer eine geeignete Schnittstelle (und Java5 +)? Ich sehe keinen Grund, warum Sie jemals zu einem HashMapstatt zu einem werfen möchten Map<KeyType,ValueType>. Tatsächlich kann ich mir keinen Grund vorstellen , den Typ einer Variablen auf HashMapanstatt zu setzen Map.

Und warum ist die Quelle eine Object? Ist es ein Parametertyp einer Legacy-Sammlung? Verwenden Sie in diesem Fall Generika und geben Sie den gewünschten Typ an.

Phihag
quelle
2
Ich bin mir ziemlich sicher, dass ein Wechsel zu Map in diesem Fall nichts ändern würde, aber danke für den Programmiertipp, der die Art und Weise, wie ich einige Dinge mache, zum Besseren verändern kann. Die Quelle des Objekts ist eine API, über die ich keine Kontrolle habe (Code hinzugefügt).
Skiphoppy
2

Wenn ich eine API verwenden muss, die Generics nicht unterstützt. Ich versuche, diese Aufrufe in Wrapper-Routinen mit so wenig Zeilen wie möglich zu isolieren. Ich verwende dann die SuppressWarnings-Annotation und füge gleichzeitig die Typensicherheitsabdrücke hinzu.

Dies ist nur eine persönliche Präferenz, um die Dinge so ordentlich wie möglich zu halten.

Fortyrunner
quelle
2

Nehmen wir diese, es ist viel schneller als das Erstellen einer neuen HashMap, wenn es bereits eine ist, aber immer noch sicher, da jedes Element gegen seinen Typ geprüft wird ...

@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);
    }
jfreundo
quelle
key.isAssignableFrom(e.getKey().getClass())kann geschrieben werden alskey.isInstance(e.getKey())
user102008
1

Überprüfen Sie es einfach, bevor Sie es wirken.

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

Und für alle, die fragen, ist es durchaus üblich, Objekte zu erhalten, bei denen Sie sich nicht sicher sind, welchen Typ sie haben. Viele ältere "SOA" -Implementierungen geben verschiedene Objekte weiter, denen Sie nicht immer vertrauen sollten. (Die Schrecken!)

BEARBEITEN Der Beispielcode wurde einmal geändert, um ihn an die Aktualisierungen des Posters anzupassen. Nach einigen Kommentaren sehe ich, dass die Instanz von nicht gut mit Generika funktioniert. Das Ändern der Prüfung zur Validierung des äußeren Objekts scheint jedoch mit dem Befehlszeilen-Compiler gut zu funktionieren. Überarbeitetes Beispiel jetzt veröffentlicht.

Rick
quelle
8
Generika machen das leider unmöglich. Es ist nicht nur eine HashMap, es ist eine HashMap mit Typinformationen. Und wenn ich diese Informationen eliminiere, schiebe ich die Warnungen einfach an eine andere Stelle.
Skiphoppy
1

Fast jedes Problem in der Informatik kann durch Hinzufügen einer Indirektionsebene * oder so etwas gelöst werden.

Führen Sie also ein nicht generisches Objekt ein, das eine höhere Ebene aufweist als a Map. Ohne Kontext wird es nicht sehr überzeugend aussehen, aber trotzdem:

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();
}

* Außer zu vielen Indirektionsebenen.

Tom Hawtin - Tackline
quelle
1
Das Zitat wird dem verstorbenen Professor David Wheeler zugeschrieben. en.wikipedia.org/wiki/…
Stephen C
1

Hier ist eine Möglichkeit, wie ich damit umgehen kann, wenn ich den equals()Vorgang überschreibe .

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

Dies scheint in Java 8 zu funktionieren (sogar kompiliert mit -Xlint:unchecked)

Jim Daehn
quelle
0

Wenn Sie sicher sind, dass der von session.getAttribute () zurückgegebene Typ HashMap ist, können Sie nicht genau auf diesen Typ typisieren, sondern nur die generische HashMap überprüfen

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

Eclipse wird dann Warnungen überraschen, aber dies kann natürlich zu Laufzeitfehlern führen, die schwer zu debuggen sind. Ich verwende diesen Ansatz nicht nur in betriebskritischen Kontexten.

Lukas Normantas
quelle
0

Zwei Möglichkeiten, eine, die das Tag vollständig vermeidet, die andere mit einer frechen, aber netten Utility-Methode.
Das Problem sind vorgenerisierte Sammlungen ...
Ich glaube, die Faustregel lautet: "Objekte einzeln umwandeln " - was dies bedeutet, wenn Sie versuchen, Rohklassen in einer generisierten Welt zu verwenden, ist, dass Sie nicht wissen, was Befindet sich in dieser Karte <?,?> (und tatsächlich stellt die JVM möglicherweise sogar fest, dass es sich nicht einmal um eine Karte handelt!), ist es offensichtlich, wenn Sie darüber nachdenken, dass Sie sie nicht wirken können. Wenn Sie eine Map <String,?> Map2 hatten, gibt HashSet <String> keys = (HashSet <String>) map2.keySet () keine Warnung aus, obwohl dies ein "Glaubensakt" für den Compiler ist (weil es könnte sich als TreeSet herausstellen) ... aber es ist nur ein einziges Akt des Glaubens.

PS zu dem Einwand, dass das Iterieren wie auf meine erste Weise "langweilig" ist und "Zeit braucht", lautet die Antwort "kein Schmerz, kein Gewinn": Eine generierte Sammlung enthält garantiert Map.Entry <String, String> s und nichts sonst. Sie müssen für diese Garantie bezahlen. Bei der systematischen Verwendung von Generika erfolgt diese Zahlung in Form der Codierungskonformität und nicht in Form von Maschinenzeit!
Eine Denkrichtung könnte sagen, dass Sie die Einstellungen von Eclipse so einstellen sollten, dass solche ungeprüften Casts-Fehler statt Warnungen auftreten. In diesem Fall müssten Sie meinen ersten Weg benutzen.

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


}
Mike Nagetier
quelle
Die Tatsache, dass Sie keine Kommentarberechtigungen haben, ermöglicht es Ihnen nicht, die Antworten anderer zu bearbeiten, um Ihre Kommentare hinzuzufügen. Sie bearbeiten die Antworten anderer, um sie in Formatierung, Syntax usw. zu verbessern, ohne Ihre Meinung dazu hinzuzufügen. Wenn Sie 50 Wiederholungen erreichen, können Sie überall Kommentare abgeben. In der Zwischenzeit können Sie sicher widerstehen (oder, wenn Sie es wirklich nicht können, schreiben Sie Ihre Kommentare in vorhandene Antworten in Ihrem Beitrag). (Hinweis für andere: Ich schreibe dies, weil ich seine vorgeschlagenen Kommentare gesehen und abgelehnt habe - Änderungen an anderen Posts in den Moderationstools)
Matteo Italia
-1

Dadurch verschwinden die Warnungen ...

 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;
}
lauwm
quelle
1
Nein, tut es nicht. Tatsächlich werden dadurch zwei Warnungen erstellt, von denen die erste eine war.
Stijn de Witt
Ah, OK. Ich bin mir nicht sicher, warum ich das gedacht habe.
Luke
-3

Lösung: Deaktivieren Sie diese Warnung in Eclipse. Nicht @SuppressWarnings, sondern nur vollständig deaktivieren.

Einige der oben vorgestellten "Lösungen" sind weit von der Linie entfernt und machen Code unlesbar, um eine dumme Warnung zu unterdrücken.

Marc Riehm
quelle
9
Darf ich fragen warum? Durch globales Deaktivieren einer Warnung werden andere Stellen ausgeblendet, an denen dieses Problem tatsächlich auftritt. Das Hinzufügen von a @SuppressWarningsmacht den Code überhaupt nicht unlesbar.
MByD