2008-12-10 6 views
13

Ist es möglich, einen generischen Typ in Java reflektiv zu instanziieren? Unter Verwendung der beschriebenen Technik here erhalte ich einen Fehler, weil Klassen-Token nicht generisch sein können. Nehmen Sie das Beispiel unten. Ich möchte eine Unterklasse von Creator instanziieren, die Creator implementiert. Der tatsächliche Klassenname wird als Befehlszeilenargument übergeben. Die Idee ist, eine Implementierung von Creator zur Laufzeit angeben zu können. Gibt es einen anderen Weg, um das zu erreichen, was ich hier versuche?Kann ich einen generischen Typ in Java reflektiv instantiieren?

Edit: Ich mag Marcus 'Ansatz als die einfachste und pragmatisch, ohne die ganze Generika-Sache zu umgehen. Ich kann es in meiner Situation verwenden, weil ich angeben kann, dass die übergebene Klasse eine Unterklasse von StringCreator sein muss. Aber Ericson wies darauf hin, dass die generische Information immer noch auf der Typ-Ebene vorhanden ist, nur nicht auf der Laufzeit-Ebene, so dass es immer noch möglich ist, reflektiv zu untersuchen, ob eine gegebene Klasse den korrekten generischen Typ implementiert.

Antwort

7

Die generischen Informationen gehen zur Laufzeit verloren. Es gibt kein Laufzeitäquivalent eines Creators < String> .class. Sie könnten eine Art zwischen Schöpfer und StringCreator schaffen, die den generischen Typ fixiert:

public interface Creator<T> { 
     T create(); 
} 
public interface StringCreator extends Creator<String> { } 
public class StringCreatorImpl implements StringCreator { 
     public String create() { return new String(); } 
} 
public class FancyStringCreator implements StringCreator { 
     public String create() { return new StringBuffer().toString(); } 
} 
public static void main(String[] args) throws Exception { 
     Class<?> someClass = Class.forName(args[0]); 
     Class<? extends StringCreator> creatorClass = someClass.asSubclass(StringCreator.class); 
     Constructor<? extends StringCreator> creatorCtor = creatorClass.getConstructor((Class<?>[]) null); 
     Creator<String> creator = creatorCtor.newInstance((Object[]) null); 
} 

Aber natürlich Sie ein wenig Flexibilität zu verlieren, weil Sie nicht die folgenden Schöpfer Klasse verwenden:

public class AnotherCreator implements Creator<String> { 
    public String create() { return ""; } 
} 
+0

Nicht alle generischen Informationen gehen zur Laufzeit verloren; Es bleibt genug übrig, um nach den notwendigen Typinformationen zu suchen, auch ohne eine andere Schnittstelle zu erstellen. Es ist nur so, dass nicht genug Informationen übrig sind, damit der Compiler die Korrektheit ableiten kann. – erickson

+0

Sie sind genau dort. Was ich (in einer komplizierten Weise) gesagt habe, ist, dass es kein Klassenobjekt gibt, das den Schöpfer im Unterschied zum Schöpfer darstellt. Aber Ihr unten aufgeführter Code scheint ein vernünftiger Weg zu sein, das Problem zu lösen. – Markus

3

Sie brauchen diese Zeile nicht. Sie brauchen den Konstruktor auch nicht, da Sie nur den Standardkonstruktor verwenden. Nur die Klasse instanziiert direkt:

public static void main(String[] args) throws Exception { 
     Class<?> someClass = Class.forName(args[0]); 
     Creator<String> creator = (Creator<String>) someClass.newInstance(); 
} 

Wenn Sie darauf bestehen, werden Sie nur in der Lage sein, auf halbem Weg dorthin zu gelangen:

public static void main(String[] args) throws Exception { 
    Class<?> someClass = Class.forName(args[0]); 
    Class<? extends Creator> creatorClass = someClass.asSubclass(Creator.class); 
    Constructor<? extends Creator> creatorCtor = creatorClass.getConstructor((Class<?>[]) null); 
    Creator<String> creator = (Creator<String>) creatorCtor.newInstance((Object[]) null); 
} 
+0

claz.newInstance() und ctor.newInstance() verhalten sich nicht genau gleich, wenn ein Fehler auftritt.Die newInstance-Methode der Klasse ist älter und widerspricht anderen reflektiven Methodenaufrufen. – erickson

-1

Nicht ganz sicher, warum Sie Generika hier verwenden. Die Instanziierung des Objekts mittels Reflektion würde eine allgemeine Verwendung nahelegen, aber vermutlich werden Sie an einem bestimmten Punkt create aufrufen und das Ergebnis einer String zuweisen, anderenfalls sollten Sie die Generika verwenden, um den Rückgabetyp zu steuern.

Aber wenn Sie die folgende Implementierung von Creator schrieb:

public class IntegerCreator implements Creator<Integer> 
{ 
    public Integer create() 
    { 
    ... 
    } 
} 

Und es passiert in als Argument Sie eine Classcast bekommen würde, wenn create aufrufen und das Ergebnis zuweisen.

4

Dies wird tun, was Sie versuchen zu tun, während Typ Sicherheit bieten. Es gibt keine Möglichkeit, eine ungeprüfte Warnung zu vermeiden, aber die hier vorgenommene Typprüfung rechtfertigt ihre Unterdrückung.

public static void main(String[] args) 
    throws Exception 
    { 
    Class<? extends Creator<String>> clz = load(argv[0], String.class); 
    Constructor<? extends Creator<String>> ctor = clz.getConstructor(); 
    Creator<String> creator = ctor.newInstance(); 
    System.out.println(creator.create()); 
    } 

    public static <T> Class<? extends Creator<T>> load(String fqcn, Class<T> type) 
    throws ClassNotFoundException 
    { 
    Class<?> any = Class.forName(fqcn); 
    for (Class<?> clz = any; clz != null; clz = clz.getSuperclass()) { 
     for (Object ifc : clz.getGenericInterfaces()) { 
     if (ifc instanceof ParameterizedType) { 
      ParameterizedType pType = (ParameterizedType) ifc; 
      if (Creator.class.equals(pType.getRawType())) { 
      if (!pType.getActualTypeArguments()[0].equals(type)) 
       throw new ClassCastException("Class implements " + pType); 
      /* We've done the necessary checks to show that this is safe. */ 
      @SuppressWarnings("unchecked") 
      Class<? extends Creator<T>> creator = (Class<? extends Creator<T>>) any; 
      return creator; 
      } 
     } 
     } 
    } 
    throw new ClassCastException(fqcn + " does not implement Creator<String>"); 
    } 

Die Haupteinschränkung Sie haften müssen, ist, dass eine Klasse in der Hierarchie der Typparameter angeben müssen. Zum Beispiel class MyCreator implements Creator<String>. Sie können es nicht mit class GenericCreator<T> implements Creator<T> verwenden.

Es behandelt derzeit nicht den gültigen Fall, wo Sie eine neue Schnittstelle erstellen interface StringCreatorIfc extends Creator<String>, und eine Klasse implementieren, dass. Es könnte verbessert werden, um das zu tun, aber ich werde das als eine Übung für die geneigten verlassen.