Als ich kürzlich mit der Extraktion von generischen Laufzeittypen für Deployit herumspielte, wurde ich von einem unerwarteten Verhalten der Reflection-API überrascht. Ein Blick in die Javadocs zeigte schnell, dass ich mich wieder einmal zu voreilig auf den "gesunden Menschenverstand" verlassen hatte. Dennoch scheint der Fall so unintuitiv zu sein, dass er eine Diskussion wert ist. In diesem Fall dreht sich das Problem um das Zusammenspiel zwischen Class.getTypeParameters und ParameterizedType. Der Kern des Codes sieht ungefähr so aus:
Schnittstelle Spionage {}
// kleine Klassenhierarchie
class Person {}
Klasse Professionell
extends Person {} class Agent extends Professional {} class Assassin extends Professional {} class Bystander extends Person {} ... Person jbond = new Agent(); System.out.println("Generic superclass type argument: " + tryGetSuperclassGenericTypeParam(jbond)); Person joepublic = new Bystander(); System.out.println("Generic superclass type argument: " + tryGetSuperclassGenericTypeParam(joepublic)); Person oddjob = new Assassin(); System.out.println("Generisches Superklassen-Argument: " + tryGetSuperclassGenericTypeParam(oddjob)); ... Type tryGetSuperclassGenericTypeParam(Object obj) { Class clazz = obj.getClass(); Class superclass = clazz.getSuperclass(); // Elvis wäre besser, aber der Klarheit halber... if (superclass.getTypeParameters().length > 0) { return ((ParameterizedType) clazz.getGenericSuperclass()).getActualTypeArguments()[0]; } else { return null; } }
Also... was passiert?
tryGetSuperclassGenericTypeParam ist der Ort, an dem die Aktion stattfindet. Es scheint ziemlich einfach zu sein: Prüfen Sie, ob die Superklasse des Objekts generisch ist (d.h. Typparameter akzeptiert) und wenn ja, wandeln Sie ihre Typdarstellung in ParameterizedType um, um den tatsächlichen Wert für den Typparameter zu extrahieren. Wenn die Superklasse nicht generisch ist, geben Sie einfach null zurück. Wenn dieser Code ausgeführt wird, führen die ersten beiden Aufrufe von tryGetSuperclassGenericTypeParam zu dem erwarteten Ergebnis:
Generisches Oberklassentyp-Argument: Schnittstelle Spying Generisches Oberklassentyp-Argument: null
Was ist mit dem dritten? Nun, angesichts der Tatsache, dass wir es versäumt haben, einen generischen Typparameter für
Ausnahme im Thread "main" java.lang.ClassCastException: java.lang.Class kann nicht in java.lang.reflect.ParameterizedType gecastet werden at tryGetSuperclassGenericTypeParam(...)
Hm?
Um herauszufinden, was hier vor sich geht, werfen wir einen Blick in die Javadoc für Class.getTypeParameters: Gibt ein Array von TypeVariable-Objekten zurück, die die Typvariablen repräsentieren, die von der durch dieses GenericDeclaration-Objekt repräsentierten generischen Deklaration deklariert wurden, und zwar in der Reihenfolge der Deklaration. Gibt ein Array der Länge 0 zurück, wenn die zugrunde liegende generische Deklaration keine Typvariablen deklariert. Mit anderen Worten: Sie erhalten Informationen auf Klassenebene über die Deklaration der Klasse Professional, die natürlich einen Typparameter hat. Wenn wir uns jedoch Class.getGenericSuperclass2 ansehen, die wir als nächstes aufrufen, stellen wir fest, dass sie: Gibt den Type zurück, der die direkte Superklasse der Entität [...] repräsentiert, die durch diese Klasse dargestellt wird. Wenn die Superklasse ein parametrisierter Typ ist, muss das zurückgegebene Type-Objekt die im Quellcode verwendeten Typparameter genau widerspiegeln. Hier sind die zurückgegebenen Informationen spezifisch für die tatsächliche Deklaration der Klasse, die Typparameter für ihre Superklasse angeben kann (oder auch nicht, wie in unserem Fall). Und genau da liegt das Problem: Professional.class.getTypeArguments schaut sich die Erklärung des Professional Klasse und entdeckt dabei ein Typargument, während Assassin.class.getGenericSuperclass das Vorkommen von Professional in der Deklaration von Assassin untersucht und keine Typparameter entdeckt. Daher gibt es eine Klasse statt eines parametrisierten Typs zurück und sprengt unseren Code.
Ergo
Um es kurz zu machen: Wenn die Superklasse eines Objekts Typargumente hat, die von Class.getTypeArguments bestimmt werden, bedeutet das nicht, dass object.getClass().getGenericSuperclass() ein ParameterizedType ist.
- Lesen Sie "Ich nahm an" ;-)
- Es ist schade, dass Class.getGenericSignature, das das "generische oder nicht generische" Verhalten von Class.getGenericSuperclass bestimmt, privat, nativ und undokumentiert ist.
Verfasst von
Andrew Phillips
Contact



