Sonntag, 2. Dezember 2012

Runtime Generic Type Resolution for Java

Bekanntlich stehen in Java die meisten generische Typinformationen zur Laufzeit nicht zur Verfügung. Insbesondere können sie nicht mittels Reflection ausgelesen werden. Doch stimmt diese Aussage unter allen Umständen?

Das Verfahren, bei dem die Typinformationen gelöscht werden, heisst Type Erasure. Damit soll das sogenannte "Generic Legacy Problem" gelöst werden.

Aus Gründen der Rückwärtskompatibilität mit altem Code, hat man sich damals entschieden, beim Kompiliervorgang die Generics weitgehend zu löschen oder in andere Konstrukte zu transformieren. Im Detail löscht der Kompiler die Typparameter, ersetzt die Typrariablen durch ihren Grenztyp, fügt Typecasts hinzu, und erweitert Klassen um sogenannte Brückenmethoden, damit das Overriding auch weiterhin funktioniert.

Folglich kann zur Laufzeit nicht herausgefunden werden, welchen Typ die unten stehende Liste "myList" hat:

public class Scribble { 

    public static void main(String[] args) {
        List<String> myList = new ArrayList<String>();

        Class elementType = getElementTypeOf(myList);
        System.out.println(elementType);
    }

    /**
    * Returns the generic element type of the given list.
    */
    public static Class getElementTypeOf(List list) {
        // This is not possible in Java !?!
    }
}

Für das obige Beispiel ist das tatsächlich unmöglich. Allerdings ist das nicht die ganze Wahrheit. Wenn wir den Code etwas abwandeln, dann lässt sich die Typinformation auslesen:

public class Scribble { 

    static List<String> myList = new ArrayList<String>();

    public static void main(String[] args) {
        Class elementType = getElementTypeOf("myList");
        System.out.println(elementType);
    }

    /**
    * Returns the generic element type of the field with the given name
    */
    public static Class getElementTypeOf(String fieldname) {
        return new GenericType(Scribble.class)
                   .getFieldType( fieldname)
                   .getTypeParameter(List.class.getTypeParameters()[0])
                   .asClass();
    }
}

Das liegt daran, dass die Variable "myList" nun als Member der Klasse deklariert ist, d.h. mit vollständigen Metainformationen im Classfile abgelegt wird.

Die Klasse "GenericType" kapselt die Suche nach dem Wert des Typparameters, welche mit Hilfe der etwas umständlichen Reflection API realisiert ist. Sie ist Teil des gleichnamigen Github-Projekt GenericType.

Keine Kommentare:

Kommentar veröffentlichen