2010-12-23 10 views
7

Ich habe eine parametrisierte Schnittstelle, die auf viele verschiedene Arten implementiert ist. Zur Laufzeit muss ich bei einem beliebigen Objekt, das diese Schnittstelle implementiert, herausfinden, was die eigentlichen Typparameter für die Schnittstelle sind.Wie erhalten Sie die Argumente des tatsächlichen Typs zu einer indirekt implementierten generischen Schnittstelle?

Hier ist ein Ausschnitt, das Problem zu veranschaulichen, und ein halbwegs Versuch, es zu lösen (also on ideone.com):

import java.util.*; 
import java.lang.reflect.*; 

interface Awesome<X> { } 
class Base<E> implements Awesome<Set<E>> { } 
class Child extends Base<List<Integer>> { } 

class AwesomeExample {  
    public static void main(String[] args) { 
     Awesome<Set<List<Integer>>> x = new Child(); 

     System.out.println(
      ((ParameterizedType) 
       Child.class.getGenericSuperclass() 
      ).getActualTypeArguments()[0] 
     ); 
     // prints "java.util.List<java.lang.Integer>" 

     System.out.println(
      ((ParameterizedType) 
       Base.class.getGenericInterfaces()[0] 
      ).getActualTypeArguments()[0] 
     ); 
     // prints "java.util.Set<E>"   

     investigate(x); 
     // we want this to print "Set<List<Integer>>" 
    } 

    static void investigate(Awesome<?> somethingAwesome) { 
     // how to do this? 
    } 
} 

Es sieht aus wie genug gibt es generische Typinformationen zur Laufzeit, dass ableiten:

  • Child extends Base<List<Integer>>
  • Base<E> implements Awesome<Set<E>>

Und deshalb können wir alle Bits und Stücke zusammen, dass zu dem Schluss:

  • Child implements Awesome<Set<List<Integer>>>

So sieht es aus wie das Problem lösbar ist, aber es ist nicht so einfach, denn wir haben würden mit einer beliebigen Klassen-/Schnittstellenhierarchie zu arbeiten. Ist das der einzige Weg, dies zu tun? Gibt es einen einfacheren Weg? Hat jemand schon eine Bibliothek dafür geschrieben?

+0

Mir ist keine Bibliothek bekannt, die dies implementiert. Ich habe es selbst gemacht - und es war nicht sehr angenehm. Ich könnte zumindest eine Super-Typ-Token-Implementierung finden: http://code.google.com/p/google-gson/source/browse/trunk/src/main/java/com/google/gson/reflect/TypeToken.java ? r = 60 & spec = svn89 –

Antwort

-1

Die kurze Antwort ist NEIN. Ich stimme zu, dass es schade ist ... :( Der Grund ist, dass Java Typ Parameter in der Compile-Phase. Sie sind nicht in Byte-Code. Sie werden nur vom Compiler verwendet.

Um Ihr Problem zu lösen muss hinzufügen, noch einen weiteren „normalen“ Parameter des Typs Klasse und übergeben es an den Konstruktor, wenn Sie eine Instanz von Basis schaffen:

class Base<E> implements Awesome<Set<E>> { 
    private E type; 
    public Base(E type) { 
     this.type = type; 
    } 
} 
+2

Die Typinformation befindet sich im Byte-Code. Du bist also nicht korrekt. Sie können die Typinformationen an dieser Position abrufen. Google für Super-Typ-Token, um ein Beispiel zu erhalten. –

+1

@Thomas - Statische Informationen werden gespeichert, dynamische Informationen nicht. – OrangeDog

4

Edit: Vielleicht möchten Sie einfach schauen mit: http://code.google.com/p/gentyref/

Wenn Sie garantieren können, dass alle Implementierungen von Awesome<?> keine Typargumente enthalten, wird folgende Co de sollten Sie [1] loszulegen: wird

static void investigate(Object o) { 
    final Class<?> c = o.getClass(); 
    System.out.println("\n" + c.getName() + " implements: "); 
    investigate(c, (Type[])null); 
} 

static void investigate(Type t, Type...typeArgs) { 
    if(t == null) return; 

    if(t instanceof Class<?>) { 
     investigate((Class<?>)t, typeArgs); 
    } else if(t instanceof ParameterizedType) { 
     investigate((ParameterizedType)t, typeArgs); 
    } 
} 

static void investigate(Class<?> c, Type...typeArgs) { 
    investigate(c.getGenericSuperclass(), typeArgs); 

    for(Type i : c.getGenericInterfaces()) { 
     investigate(i, typeArgs); 
    } 
} 

static void investigate(ParameterizedType p, Type...typeArgs) { 
    final Class<?> c = (Class<?>)p.getRawType(); 
    final StringBuilder b = new StringBuilder(c.getName()); 
    b.append('<'); 
    Type[] localArgs = p.getActualTypeArguments(); 
    if(typeArgs != null && typeArgs.length > 0) { 
     int i = 0, nextTypeArg = 0; 
     for(Type local : localArgs) { 
      if(local instanceof ParameterizedType) { 
       ParameterizedType localP = (ParameterizedType) local; 
       b.append(localP.getRawType()).append('<'); 
       b.append(typeArgs[nextTypeArg++]); 
       b.append('>'); 
      } else if(local instanceof TypeVariable) { 
       // reify local type arg to instantiated one. 
       localArgs[nextTypeArg] = typeArgs[nextTypeArg]; 
       b.append(localArgs[nextTypeArg]); 
       nextTypeArg++; 
      } else { 
       b.append(local.toString()); 
      } 
      b.append(", "); 
      i++; 
     } 
     if(typeArgs.length > 0) { 
      b.delete(b.length() - 2, b.length()); 
     } 
     b.append('>'); 
    } else { 
     String args = Arrays.toString(localArgs); 
     b.append(args.substring(1, args.length()-1)).append('>'); 
    } 
    System.out.println(b); 
    investigate(c, localArgs); 
} 

Wenn jedoch Instanzierungen Awesome<?> oder Base<E> wird gemacht, dass Typ-Informationen werden aufgrund Löschung verloren. Dies kann umgangen werden, als Konvention, mit so etwas wie folgt aus:

Awesome<?> awesome = new Base<Double>() {}; 

Beachten Sie die {}, das schafft eine neue anonyme Klasse, die implementiert (oder hier erstreckt) Base<E>. Diese Klasse hat ihre Typparameter zur Reflektion verfügbar.

Wenn Sie Angst haben, diese Konvention Durchsetzung wird ein Thema sein, können Sie die Konstrukteure verstecken & nur Fabrikmethoden ausgesetzt:

class Base<E> implements Awesome<Set<E>> { 

    public static Base<Number> newNumberInstance() { 
     return new Base<Number>() {}; 
    } 

    protected Base() {} 
} 

Wie der obige Code vollständig nicht getestet, können Sie tun Das. Der Punkt hier ist, dass Sie die tatsächlichen Typparameter finden können, die Ihren Anforderungen streng genug sind. Ob das für Ihre Situation zutrifft oder nicht, entscheiden Sie selbst.

[1] Es werden alle Schnittstellen ausgegeben, die eine Klasse & nicht nur die Typparameter Awesome implementiert. Dies kann geändert werden, aber ich dachte, ich würde für allgemeinere gehen & lassen Sie sich die Besonderheiten erarbeiten. Zum Beispiel möchten Sie diese testen, um zu sehen, was ich meine:

investigate(new ArrayList<Integer>()); 
investigate(new ArrayList<String>() {}); // new anonymous ArrayList class 
investigate(""); 
investigate(new Awesome<Comparable<?>>() {}); // new anonymous implementation of Awesome 
0

Nach Vorschlag von @ oconnor0, hier ist, wie es mit gentyref zu tun:

static Type investigate(Awesome<?> somethingAwesome) { 
    return GenericTypeReflector.getTypeParameter(somethingAwesome.getClass(), Awesome.class.getTypeParameters()[0]); 
} 

Bei somethingAwesome.getClass() könnte auch generisch, könnte es nützlich sein, es zuerst durch GenericTypeReflector.addWildcardParameters zu übergeben.

Feder hat auch eine GenericTypeResolver.resolveTypeArgument(Class,Class), die das gleiche Ergebnis erzielen kann.

Verwandte Themen