692 Stimmen

Wie gehe ich mit ungeprüften Besetzungswarnungen um?

Eclipse gibt mir eine Warnung der folgenden Form:

Typ Sicherheit: Ungeprüfter Cast von Object nach HashMap

Dies stammt aus einem Aufruf an eine API, über die ich keine Kontrolle habe und die Object 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 allerdings noch keine gute Möglichkeit gefunden, dieses Problem zu beseitigen. Ich kann die einzelne betroffene Zeile in eine eigene Methode auslagern und Folgendes hinzufügen @SuppressWarnings("unchecked") zu dieser Methode, wodurch die Auswirkungen eines Codeblocks, in dem ich Warnungen ignoriere, begrenzt werden. Gibt es bessere Möglichkeiten? Ich möchte diese Warnungen in Eclipse nicht ausschalten.

Bevor ich zu dem Code kam, war es einfacher, aber es gab trotzdem Warnungen:

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

Das Problem war, dass an anderer Stelle, wenn man versuchte, den Hash zu verwenden, Warnungen angezeigt wurden:

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.

0 Stimmen

Wenn Sie HttpSession auf diese Weise verwenden, lesen Sie den Artikel von Brian Goetz zu diesem Thema: ibm.com/developerworks/library/j-jtp09238.html

0 Stimmen

Wenn ein ungeprüfter Cast unvermeidbar ist, ist es eine gute Idee, ihn mit etwas zu koppeln, das seinen Typ logisch repräsentiert (wie ein enum oder sogar Instanzen von Class<T> ), so dass Sie einen Blick darauf werfen können und savoir es ist sicher.

4 Stimmen

23voto

Dustin Getz Punkte 20462

Das ist eine schwierige Aufgabe, aber hier sind meine derzeitigen Gedanken:

Wenn Ihre API Object zurückgibt, dann gibt es nichts, was Sie tun können - egal was, Sie werden blind das Objekt Casting. Sie können Java ClassCastExceptions werfen lassen, oder Sie können jedes Element selbst überprüfen und Assertions oder IllegalArgumentExceptions oder ähnliches werfen, aber diese Laufzeit Prüfungen sind alle gleichwertig. Sie müssen die Kompilierzeit unkontrollierter Abdruck, egal was Sie zur Laufzeit tun.

Ich würde es einfach vorziehen, blind zu casten und die JVM die Laufzeitprüfung für mich durchführen zu lassen, da wir "wissen", was die API zurückgeben sollte, und in der Regel bereit sind, davon auszugehen, dass die API funktioniert. Verwenden Sie Generics überall oberhalb des Casts, wenn Sie sie brauchen. Das bringt Ihnen zwar nicht wirklich etwas, da Sie immer noch den einzelnen Blind Cast haben, aber zumindest können Sie von dort an Generics verwenden, damit die JVM Ihnen helfen kann, Blind Casts in anderen Teilen Ihres Codes zu vermeiden.

In diesem speziellen Fall können Sie vermutlich den Aufruf von SetAttribute sehen und erkennen, dass der Typ hineingeht, so dass es nicht unmoralisch ist, den Typ auf dem Weg nach draußen einfach blind an denselben zu übergeben. Fügen Sie einen Kommentar hinzu, der auf SetAttribute verweist, und schon ist es erledigt.

18voto

StaticNoiseLog Punkte 1301

Hier ist eine gekürzte Beispiel, das die Warnung vor "ungeprüfter Besetzung" vermeidet durch Anwendung von zwei Strategien, die in anderen Antworten erwähnt wurden.

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

  2. Verwenden Sie beim Typ-Casting einer Sammlung den Platzhalter ? anstelle des generischen Typs T, um zu zeigen, dass Sie tatsächlich nicht wissen, welche Art von Objekten Sie vom Legacy-Code erwarten können ( Collection<?> unknownTypeCollection ). Schließlich ist es das, was uns die Warnung "ungeprüfter Wurf" sagen will: Wir können nicht sicher sein, dass wir eine Collection<T> Daher ist es am ehrlichsten, wenn Sie eine Collection<?> . Falls unbedingt erforderlich, kann immer noch eine Sammlung eines bekannten Typs erstellt werden ( Collection<T> knownTypeCollection ).

Der Legacy-Code, der im folgenden Beispiel eingebunden ist, hat ein Attribut "input" im StructuredViewer (StructuredViewer ist ein Baum- oder Tabellen-Widget, "input" ist das Datenmodell dahinter). Diese "input" könnte jede Art von Java Collection 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 zu Laufzeitfehlern führen, wenn wir den Legacy-Code mit den falschen Datentypen verwenden (z.B. wenn wir ein Array als "Input" des StructuredViewers anstelle einer Java Collection setzen).

Beispiel für den Aufruf der Methode:

dragFinishedStrategy.dragFinished(viewer, Product.class);

13voto

Joachim Sauer Punkte 290477

In der Welt der HTTP-Sitzungen kann man den Cast nicht wirklich vermeiden, da die API so geschrieben ist (nimmt und gibt nur Object ).

Mit ein wenig Arbeit können Sie den unkontrollierten Wurf jedoch leicht vermeiden. Das bedeutet, dass es sich in einen traditionellen Wurf verwandelt und eine ClassCastException für den Fall, dass ein Fehler auftritt). Eine ungeprüfte Ausnahme könnte sich in eine CCE zu einem beliebigen späteren Zeitpunkt und nicht zum Zeitpunkt des Wurfes (deshalb eine gesonderte Warnung).

Ersetzen Sie die HashMap durch eine eigene 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 casten Sie auf diese Klasse statt auf Map<String,String> und alles wird genau an der Stelle geprüft, an der Sie Ihren Code schreiben. Nicht unerwartet ClassCastExceptions später.

0 Stimmen

Das ist eine wirklich hilfreiche Antwort.

10voto

Jan Moravec Punkte 436

Wenn Sie die Inspektion in Android Studio deaktivieren möchten, können Sie dies tun:

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

8voto

Esko Luontola Punkte 71758

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

Wenn Sie aber trotzdem überprüfen wollen, ob der Inhalt der Karte vom richtigen Typ ist, können Sie einen Code wie diesen 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);
    }
}

1 Stimmen

Großartig; ich denke, ich kann das mit meiner Antwort kombinieren, um es zu parametrisieren und die Unterdrückung von Warnungen ganz zu vermeiden!

1 Stimmen

+1 Wahrscheinlich das beste Rezept (einfach zu verstehen und zu pflegen), um es sicher mit Laufzeitprüfungen zu machen

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X