2013-05-17 5 views
13

Geben Sie den folgenden Code:Generics Oddity - Ich kann einen Long-Wert in eine Map <String, String> einfügen und es kompiliert und nicht ausfällt zur Laufzeit

public static void main(String[] args) { 
     HashMap<String, String> hashMap = new HashMap<>(); 
     HashMap<String, Object> dataMap = new HashMap<>(); 
     dataMap.put("longvalue", 5L); 

     class TestMethodHolder { 
      <T> T getValue(Map<String, Object> dataMap, String value) { 
       return (T)dataMap.get(value); 
      } 
     } 

     hashMap.put("test", new TestMethodHolder().<String>getValue(dataMap, "longvalue")); 
     String value = hashMap.get("test"); // ClassCastException occurs HERE 
     System.out.println(value); 
    } 

Mir ist es nicht verwunderlich, dass diese Code kompiliert, aber eher, dass die ClassCastException in der get-Zeile auftritt, im Gegensatz zur Put-Zeile darüber, obwohl ich eine fundierte Schätzung habe, was passieren könnte. Da generische Typen zur Laufzeit gelöscht werden, tritt die Umwandlung in getValue() zur Laufzeit nie auf und ist tatsächlich eine Umwandlung in Object. Wenn die Methode wie folgt implementiert würde, würde die Laufzeitumwandlung stattfinden und sie würde (wie erwartet) in der Put-Zeile fehlschlagen. Kann das jemand bestätigen?

class TestMethodHolder { 
     String getValue(Map<String, Object> dataMap, String value) { 
      return (String)dataMap.get(value); 
     } 
    } 

Ist dies ein bekannter Fehler oder Merkwürdigkeit der Verwendung von Generika? Ist es dann eine schlechte Übung, beim Aufruf von Methoden die Notation <> zu verwenden?

Edit: Ich verwende das Standard-Oracle JDK 1.7_03. Eine weitere implizierte Frage von oben: Ist die Umwandlung in der ursprünglichen getValue STILL zur Laufzeit, aber die Besetzung ist eigentlich auf Object - oder ist der Compiler schlau genug, um diese Besetzung aus der Laufzeit überhaupt nicht zu entfernen? Dies könnte den Unterschied erklären, in dem die ClassCastException auftritt, die Benutzer bemerken, wenn sie ausgeführt wird.

+0

Es kompiliert und ausgeführt werden, da Typ Löschung es eine 'Karte machen ' zur Laufzeit. Natürlich erhalten Sie eine 'ClassCastException', weil Sie' Long' als 'String' behandeln und das ist falsch. –

+0

@LuiggiMendoza Ich denke, OP weiß das - siehe seinen Abschnitt "Gebildete Vermutung". –

+0

Sie haben eine unkontrollierte Umwandlung von Objekt zu T in 'return (T) dataMap.get (value);'. Sie überschreiben den Compiler hier. – devconsole

Antwort

2

Der Compiler hängt vom Typ Sicherheit ab, um Annahmen zu treffen und Transformationen/Optimierungen durchzuführen. Leider kann der Typ Sicherheit durch unkontrollierte Besetzung untergraben werden. Wenn Ihr Programm eine falsche unkontrollierte Umwandlung enthält, ist es unklar, was der Compiler tun soll. Im Idealfall sollte es eine Laufzeitprüfung genau an der Stelle der ungeprüften Umsetzung machen, in Ihrem Beispiel, wenn Object auf T geworfen wird. Aber das ist unmöglich wegen der Löschung, die nicht genau Teil des Typsystems ist.

Überall sonst in Ihrem Beispiel, Typen sind Sound, so Compiler kann davon ausgehen, dass getValue() wirklich eine String zurückgibt, ist es nicht notwendig, zu überprüfen. Aber es ist auch legal, die Überprüfung durchzuführen, wie es der Eclipse-Compiler macht (wahrscheinlich, weil er den Rückgabewert einer lokalen String lokalen Temp-Variable zuweist).

Also die schlechte Nachricht ist, wenn Ihr Programm falsche unkontrollierte Besetzung enthält, ist sein Verhalten undefined .... Also stellen Sie sicher, dass alle Ihre unchecked Casts korrekt sind, durch strenge Begründung.

Eine gute Übung besteht darin, alle unkontrollierten Umwandlungen zu überprüfen, damit Sie die ungeprüfte Warnung rechtmäßig unterdrücken können. Zum Beispiel

 <T> T getValue(Map<String, Object> dataMap, String value, Class<T> type) 
     { 
      Object value = dataMap.get(value); 
      if(value!=null && !type.isInstance(value)) // check! 
       throw new ClassCastException(); 

      @SuppressWarning("unchecked") 
      T t = (T)value; // this is safe, because we've just checked 
      return t; 
     } 

meine Antwort auf eine ähnliche Frage Siehe: Lazy class cast in Java?

+0

Ich denke das beantwortet am klarsten meine Frage - danke! – GreenieMeanie

+0

In echtem Code können Sie jedoch eine etwas kürzere Lösung verwenden: 'Objekt value = dataMap.get (name); Rückgabetyp.cast (Wert); '. – biziclop

12

Linie

return (T)dataMap.get(value); 

erzeugt ein ungeprüfter Guss Warnung und pro-Spezifikation, das Vorhandensein einer solchen Warnung macht den Code typ unsicher. Das ClassCastException tritt das erste Mal auf, wenn Sie versuchen, das type-unsafe Ergebnis einer Variablen des falschen Typen zuzuweisen, da das der kompilierte Code das erste Mal eine Typenprüfung ist.

Beachten Sie, dass der Eclipse-Compiler mehr Typprüfungen einfügt, als von der JLS vorgeschrieben. Wenn Sie also innerhalb von Eclipse kompilieren, schlägt der Aufruf hashMap.put mit CCE fehl. Der Compiler weiß, dass dieser Aufruf zwei String Argumente haben muss und somit in der Lage ist, die Typprüfungen vor dem eigentlichen Methodenaufruf einzufügen.

Genau wie Sie vermuten, wenn Sie das generische T mit dem spezifischen String ersetzen, dann tritt die Typüberprüfung an diesem Punkt — auf und schlägt fehl.

+0

Entschuldigung, falsch gelesen OP. Dachte, sie deuteten an, dass der "CCE" in der von Ihnen zitierten Linie (bei der Besetzung zu "T") auftrat, im Gegensatz zu der Tatsache, dass es eine unkontrollierte Besetzung wäre, was tatsächlich überraschend wäre. –

+0

@MarkPeters Ich werde es verbessern, um beide Verhaltensweisen abzudecken. –

+0

@Marko Topolnik +1 Schöne Erklärung über den Eclipse-Compiler. –

0

Diese Informationen werden während der Kompilierung gelöscht (siehe Neal Gafter's "Reified Generics for Java").

Auf einer praktischen Anmerkung, können Sie Ihre Sammlungen mit den Collections Utility-Methoden schützen:

Class<String> type = String.class; 
Map<String, String> hashMap = new HashMap<>(); 
Map<String, String> map = Collections.checkedMap(hashMap, type, type); 

Map rawType = map; // pre-Java 1.5 code knows nothing about generics 
rawType.put(1, 2); // throws ClassCastException at runtime 
Verwandte Themen