Die Liste l
enthält das Element vom Typ Box
. In Fall 1 erhalte ich das erste Element als Box<Integer>
und im zweiten Fall wird das zweite Element in der Liste als Box<String>
erhalten. Die ClassCastException wird im ersten Fall nicht ausgelöst.
Als ich versucht habe zu debuggen, sind der element's
Typ in b1
und b2
jeweils String
und Integer
.
Hat es etwas mit dem Löschen von Typen zu tun?
Um genau zu sein, ist das Problem PrintStream#println
.
Überprüfen wir den kompilierten Code mit javap -c Test.class
:
Wie Sie sehen können, löschte der Compiler die Typen und unterließ auch eine Umwandlung für Integer
, da dies hier nicht notwendig war. Der Compiler hat die verwendete überladene Methode bereits mit verknüpft PrintStream#(Object)
.
Dies geschieht aufgrund der JLS-Regel §5.3 :
Methodenaufrufkonvertierung wird auf jeden Argumentwert in einem Methoden- oder Konstruktoraufruf angewendet (§8.8.7.1 , §15.9 , §15.12 ): Der Typ des Argumentausdrucks muss in den Typ des entsprechenden Parameters konvertiert werden.
Methodenaufrufkontexte ermöglichen die Verwendung eines der folgenden Elemente:
- eine Identitätskonvertierung ( §5.1.1 )
- eine erweiterte primitive Konvertierung ( §5.1.2 )
- eine Erweiterung der Referenzkonvertierung ( §5.1.5 )
- eine Box-Konvertierung ( §5.1.7 ) optional gefolgt von einer erweiterten Referenzkonvertierung
- eine Unboxing-Konvertierung ( §5.1.8 ) optional gefolgt von einer Erweiterung der Primitivumwandlung.
Die dritte Regel ist die Umwandlung von einem Subtyp in einen Supertyp:
Eine erweiternde Referenz Konvertierung existiert von jedem Referenztyp S zu jedem Referenztyp T, vorausgesetzt, S ist ein Subtyp (§4.10 ) von T.
Und es wird vor der Prüfung gemacht, ob der Typ entpackt werden kann (der fünfte Check: "eine Unboxing-Konvertierung" ). Der Compiler prüft also, ob Integer
ein Untertyp von Object
ist und daher #println(Object)
aufrufen muss (Ihre IDE sagt Ihnen dasselbe, wenn Sie die aufgerufene überladene Version überprüfen).
Die zweite Version andererseits:
%Vor% hat einen checkcast
, um den abgerufenen Typ von Box#getElement
zu überprüfen, ist wirklich ein String
. Dies ist notwendig, weil Sie dem Compiler gesagt haben, dass es ein String
ist (aufgrund des generischen Typs Box<String> b2 = l.get(1);
) und es die Methode PrintStream#(String)
. Diese Überprüfung schlägt mit der erwähnten ClassCastException
fehl, weil Integer
nicht in String
umgewandelt werden kann.
Ok, das Problem hier ist, dass b2 falsch als Box<String>
markiert ist, wenn es tatsächlich Box<Integer>
ist (der Typ von box2) - also wird b2.getElement()
als String eingegeben, obwohl Es enthält tatsächlich eine Ganzzahl. Der Compiler versucht, die überladene println-Methode aufzurufen, die einen String anstelle der Methode verwendet, die ein Object akzeptiert, und so erhalten Sie eine ClassCastException. Die Objektversion von println führt eine explizite Konvertierung ihres Arguments in einen String durch (über einen Aufruf von toString ()), aber die String-Version der Methode tut das nicht.
Das zugrunde liegende Problem besteht darin, rohe Typen zu verwenden, anstatt den Typparameter für die Liste l
vollständig anzugeben - es hätte List<Box<?>>
sein müssen. Dann hätten Sie b1
und b2
als Box gehabt und die richtige Überladung von System.out.println wäre gewählt worden.
Zur Kompilierzeit kennt der Compiler den Typ und verknüpft den Aufruf von System.out.println(..)
mit der Methode mit dem richtigen Parametertyp. Im ersten Fall löst der Compiler den Aufruf von println(Object)
auf. Da b1.getElement()
eine Object
, String
eine Object
zurückgibt, ist der Methodenaufruf korrekt und löst keine Ausnahme aus. Im zweiten Fall löst der Compiler den Aufruf von println(String)
wegen Box<String>
auf, aber b2.getElement()
gibt Integer
zurück. Dies kann nicht in String umgewandelt werden und ein ClassCastException
wird ausgelöst.
Die Methode println ist nicht für Integer-Parameter definiert, daher ruft Ihr Code println (Objektobjekt) auf, das object.toString () aufrufen wird, um die Zeichenfolge zu erhalten, die gedruckt werden soll. Es gibt keine Typüberprüfung, weil alles ein Objekt ist.
Im zweiten Fall möchte Ihr Code println (String someString) aufrufen und wird deshalb überprüfen, ob es sich bei sayString wirklich um einen String handelt, und weil dies nicht der Fall ist, wird eine Ausnahme ausgelöst.
Tags und Links java generics type-erasure generic-collections