Instanz eines generischen Typs in Java erstellen?


576

Ist es möglich, eine Instanz eines generischen Typs in Java zu erstellen? Ich denke basierend auf dem, was ich gesehen habe, dass die Antwort no( aufgrund der Typlöschung ) ist, aber ich wäre interessiert, wenn jemand etwas sehen kann, das mir fehlt:

class SomeContainer<E>
{
    E createContents()
    {
        return what???
    }
}

BEARBEITEN: Es stellt sich heraus, dass Super Type Tokens verwendet werden könnten, um mein Problem zu beheben, aber es erfordert viel reflexionsbasierten Code, wie einige der folgenden Antworten gezeigt haben.

Ich werde dies für eine Weile offen lassen, um zu sehen, ob sich jemand etwas dramatisch anderes einfallen lässt als Ian Robertsons Artima-Artikel .


2
Gerade die Leistung auf einem Android-Gerät getestet. 10000 Operationen und: 8-9 ms nehmen neue SomeClass (), 9-11 ms nehmen Factory <SomeClass> .createInstance () und 64-71 ms nehmen kürzeste Reflexion: SomeClass z = SomeClass.class.newInstance (). Und alle Tests waren in einem einzigen Try-Catch-Block. Reflection newInstance () löst 4 verschiedene Ausnahmen aus, erinnerst du dich? Also entschied ich mich für Factory Pattern
Deepscorn


4
Mit Java 8 können Sie jetzt eine Konstruktorreferenz oder ein Lambda übergeben, wodurch dieses Problem ziemlich einfach zu umgehen ist. Siehe meine Antwort unten für Details.
Daniel Pryden

Ich denke, das ist eine schlechte Idee, solchen Code zu schreiben. Das sind elegantere und lesbarere Möglichkeiten, das darunter liegende Problem zu lösen.
Krzysztof Cichocki

1
@ DavidCitron "für eine Weile" , sagte er ... Seitdem sind elf Jahre vergangen ...
MC Emperor

Antworten:


332

Du hast Recht. Das kannst du nicht machen new E(). Aber Sie können es ändern

private static class SomeContainer<E> {
    E createContents(Class<E> clazz) {
        return clazz.newInstance();
    }
}

Es ist nervig. Aber es funktioniert. Das Einwickeln in das Werksmuster macht es etwas erträglicher.


11
Ja, ich habe diese Lösung gesehen, aber sie funktioniert nur, wenn Sie bereits einen Verweis auf ein Class-Objekt des Typs haben, den Sie instanziieren möchten.
David Citron

11
Ja ich weiß. Es wäre schön, wenn Sie E.class machen könnten, aber das gibt Ihnen einfach Object.class wegen Löschung :)
Justin Rudd

6
Das ist der richtige Ansatz für dieses Problem. Es ist normalerweise nicht das, was Sie wünschen, aber es ist das, was Sie bekommen.
Joachim Sauer

38
Und wie ruft man die Methode createContents () auf?
Alexis Dufrenoy

8
Dies ist nicht mehr der einzige Weg, dies zu tun. Es gibt jetzt einen besseren Weg, bei dem keine Class<?>Referenz mit Guava und TypeToken übergeben werden muss. Code und Links finden Sie in dieser Antwort!

129

Keine Ahnung, ob dies hilft, aber wenn Sie einen generischen Typ in eine Unterklasse (einschließlich anonym) unterteilen, sind die Typinformationen über Reflection verfügbar. z.B,

public abstract class Foo<E> {

  public E instance;  

  public Foo() throws Exception {
    instance = ((Class)((ParameterizedType)this.getClass().
       getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
    ...
  }

}

Wenn Sie also Foo unterordnen, erhalten Sie eine Instanz von Bar, z.

// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );

Aber es ist viel Arbeit und funktioniert nur für Unterklassen. Kann aber praktisch sein.


2
Ja, das ist besonders schön, wenn die generische Klasse abstrakt ist. Sie können dies in den konkreten Unterklassen tun :)
Pierre Henry

Diese Methode funktioniert auch, wenn die Klasse Foonicht abstrakt ist. Aber warum funktioniert es nur in anonymen Unterklassen von Foo? Angenommen, wir machen Fookonkret (wir lassen es weg abstract), warum wird new Foo<Bar>();dies zu einem Fehler führen, während new Foo<Bar>(){};dies nicht der Fall ist? (Ausnahme: "Klasse kann nicht in ParameterizedType umgewandelt werden")
Tim Kuipers

2
@ TimKuipers Das <E>In class Foo<E>ist nicht an einen bestimmten Typ gebunden. Sie das außergewöhnliche Verhalten sehen , wenn Enicht ist statisch gebunden, wie in: new Foo<Bar>(), new Foo<T>() {...}oder class Fizz <E> extends Foo<E>. Der erste Fall ist nicht statisch gebunden, sondern wird beim Kompilieren gelöscht . Der zweite Fall ersetzt eine andere Typvariable (T) anstelle von E, ist aber noch nicht gebunden. Und im letzten Fall sollte es offensichtlich sein, dass Enoch ungebunden ist.
William Price

4
Ein Beispiel für die statische Bindung des Typparameters wäre class Fizz extends Foo<Bar>- in diesem Fall erhalten Benutzer von Fizzetwas, das a ist Foo<Bar>und nichts anderes als a sein kann Foo<Bar>. In diesem Fall kodiert der Compiler diese Informationen gerne in die Klassenmetadaten Fizzund stellt sie als ParameterizedTypeReflection-Code zur Verfügung. Wenn Sie eine anonyme innere Klasse erstellen, als würde new Foo<Bar>() {...}sie dasselbe tun, außer dass Fizzder Compiler stattdessen einen "anonymen" Klassennamen generiert, den Sie erst kennen, wenn die äußere Klasse kompiliert ist.
William Price

4
Es sollte beachtet werden, dass dies nicht funktioniert, wenn die Typargumente auch ein ParameterizedType sind. Zum Beispiel Foo<Bar<Baz>>. Sie erstellen eine Instanz, ParameterizedTypeImpldie nicht explizit erstellt werden kann. Daher ist es eine gute Idee zu überprüfen, ob a zurückgegeben getActualTypeArguments()[0]wird ParameterizedType. Wenn dies der Fall ist, möchten Sie den Rohtyp abrufen und stattdessen eine Instanz davon erstellen.
Crush

106

In Java 8 können Sie die SupplierFunktionsschnittstelle verwenden, um dies ziemlich einfach zu erreichen:

class SomeContainer<E> {
  private Supplier<E> supplier;

  SomeContainer(Supplier<E> supplier) {
    this.supplier = supplier;
  }

  E createContents() {
    return supplier.get();
  }
}

Sie würden diese Klasse folgendermaßen konstruieren:

SomeContainer<String> stringContainer = new SomeContainer<>(String::new);

Die Syntax String::newin dieser Zeile ist eine Konstruktorreferenz .

Wenn Ihr Konstruktor Argumente akzeptiert, können Sie stattdessen einen Lambda-Ausdruck verwenden:

SomeContainer<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));

6
Gut. Es vermeidet Reflexion und muss Ausnahmen behandeln.
Bruno Leite

2
So nett. Leider erfordert dies für Android-Benutzer API-Level 24 oder höher.
Michael Updike

1
Nach weniger Aufwand und funktionalem Ansatz soll dies meiner Meinung nach als Antwort akzeptiert werden
Tomas

2
… Und es unterscheidet sich nicht von dieser noch älteren Antwort, die zeigt, dass das technische Muster dahinter noch älter ist als Javas Unterstützung für Lambda-Ausdrücke und Methodenreferenzen, während Sie diesen älteren Code sogar verwenden können, wenn Sie Ihren Compiler aktualisiert haben…
Holger

Wäre es möglich, nur zu setzen SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke

87

Sie benötigen eine abstrakte Fabrik der einen oder anderen Art, an die Sie das Geld weitergeben können:

interface Factory<E> {
    E create();
}

class SomeContainer<E> {
    private final Factory<E> factory;
    SomeContainer(Factory<E> factory) {
        this.factory = factory;
    }
    E createContents() {
        return factory.create();
    }
}

..und wie Factory.create () aussieht?
OhadR

6
@OhadR Factory<>ist eine Schnittstelle und es gibt keinen Körper. Der Punkt ist, dass Sie eine Schicht von Indirektionen benötigen, um das Geld an Methoden zu übergeben, die den erforderlichen Code zum Erstellen einer Instanz "kennen". Es ist viel besser, dies mit normalem Code zu tun, als mit Metalinguistik Classoder Constructorweil Reflexion eine ganze Welt voller Verletzungen mit sich bringt.
Tom Hawtin - Tackline

2
Heute können Sie eine Factory-Instanz mit einem Methodenreferenzausdruck wie dem folgenden erstellen:SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Lii

24
package org.foo.com;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Basically the same answer as noah's.
 */
public class Home<E>
{

    @SuppressWarnings ("unchecked")
    public Class<E> getTypeParameterClass()
    {
        Type type = getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        return (Class<E>) paramType.getActualTypeArguments()[0];
    }

    private static class StringHome extends Home<String>
    {
    }

    private static class StringBuilderHome extends Home<StringBuilder>
    {
    }

    private static class StringBufferHome extends Home<StringBuffer>
    {
    }   

    /**
     * This prints "String", "StringBuilder" and "StringBuffer"
     */
    public static void main(String[] args) throws InstantiationException, IllegalAccessException
    {
        Object object0 = new StringHome().getTypeParameterClass().newInstance();
        Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
        Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
        System.out.println(object0.getClass().getSimpleName());
        System.out.println(object1.getClass().getSimpleName());
        System.out.println(object2.getClass().getSimpleName());
    }

}

3
Ein guter Ansatz dieses Codes kann zu ClassCastException führen, wenn Sie im generischen einen generischen Typ verwenden. Dann rufen Sie das Argument actualType ab. Sie sollten überprüfen, ob es auch ParamterizedType ist, und in diesem Fall seinen RawType (oder etwas Besseres) zurückgeben. Ein weiteres Problem dabei ist, wenn wir mehr als einmal erweitern, sobald dieser Code auch die ClassCastExeption auslöst.
Damian Leszczyński - Vash

3
Verursacht durch: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl kann nicht auf java.lang.Class gegossen werden
juan

@ DamianLeszczyński-Vash wird auch scheitern mit zBclass GenericHome<T> extends Home<T>{}
Holger

22

Wenn Sie eine neue Instanz eines Typarguments in einer generischen Klasse benötigen, lassen Sie Ihre Konstruktoren ihre Klasse anfordern ...

public final class Foo<T> {

    private Class<T> typeArgumentClass;

    public Foo(Class<T> typeArgumentClass) {

        this.typeArgumentClass = typeArgumentClass;
    }

    public void doSomethingThatRequiresNewT() throws Exception {

        T myNewT = typeArgumentClass.newInstance();
        ...
    }
}

Verwendungszweck:

Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);

Vorteile:

  • Viel einfacher (und weniger problematisch) als der Super Type Token (STT) -Ansatz von Robertson.
  • Viel effizienter als der STT-Ansatz (der Ihr Handy zum Frühstück frisst).

Nachteile:

  • Klasse kann nicht an einen Standardkonstruktor übergeben werden (weshalb Foo endgültig ist). Wenn Sie wirklich einen Standardkonstruktor benötigen, können Sie jederzeit eine Setter-Methode hinzufügen, aber Sie müssen daran denken, sie später anzurufen.
  • Robertsons Einwand ... Mehr Balken als ein schwarzes Schaf (obwohl eine erneute Angabe der Typargumentklasse Sie nicht genau tötet). Und entgegen den Behauptungen von Robertson verstößt dies ohnehin nicht gegen das DRY-Prinzip, da der Compiler die Typkorrektheit sicherstellt.
  • Nicht ganz der Foo<L>Beweis. Für Starter...newInstance() wird ein Wobbler ausgelöst, wenn die Typargumentklasse keinen Standardkonstruktor hat. Dies gilt jedoch ohnehin für alle bekannten Lösungen.
  • Fehlt die Gesamtverkapselung des STT-Ansatzes. Keine große Sache (angesichts des unverschämten Leistungsaufwands von STT).

22

Sie können dies jetzt tun und es ist kein Reflexionscode erforderlich.

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

Natürlich, wenn Sie den Konstruktor aufrufen müssen, der einige Überlegungen erfordert, aber das ist sehr gut dokumentiert, ist dieser Trick nicht!

Hier ist das JavaDoc für TypeToken .


6
Diese Lösung funktioniert für eine begrenzte Anzahl von Fällen, genau wie die Antwort von @ noah mit Reflexion. Ich habe sie heute alle ausprobiert ... Und am Ende habe ich eine Instanz der Parameterklasse an die parametrisierte Klasse übergeben (um .newInstance () aufrufen zu können). Sehr großer Mangel an "Generika" ... neue Foo <Bar> (Bar.class); ... Klasse Foo <T> {private letzte Klasse <T> mTFactory; Foo (Klasse <T> tClass) {mTFactory = tClass; ...} T instance = tFactory.newInstance (); }
yvolk

Dies funktioniert in allen Fällen, auch statische Factory-Methoden, die generische Parameter verwenden

13

Denken Sie an einen funktionaleren Ansatz: Anstatt ein E aus dem Nichts zu erstellen (was eindeutig ein Codegeruch ist), übergeben Sie eine Funktion, die weiß, wie man eines erstellt, d. H.

E createContents(Callable<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}

6
Technisch vorbei Sie keine Funktion, sind Sie ein vorübergehendes Funktionsobjekt (auch bekannt als ein functor ).
Lorne Laliberte

3
Oder vielleicht zu überwinden fängt ExceptionNutzung Supplier<E>statt.
Martin D

10

Aus dem Java-Tutorial - Einschränkungen für Generika :

Instanzen von Typparametern können nicht erstellt werden

Sie können keine Instanz eines Typparameters erstellen. Der folgende Code verursacht beispielsweise einen Fehler bei der Kompilierung:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

Um dieses Problem zu umgehen, können Sie durch Reflektion ein Objekt eines Typparameters erstellen:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

Sie können die Append-Methode wie folgt aufrufen:

List<String> ls = new ArrayList<>();
append(ls, String.class);

6

Hier ist eine Option, die ich mir ausgedacht habe. Sie kann helfen:

public static class Container<E> {
    private Class<E> clazz;

    public Container(Class<E> clazz) {
        this.clazz = clazz;
    }

    public E createContents() throws Exception {
        return clazz.newInstance();
    }
}

BEARBEITEN: Alternativ können Sie diesen Konstruktor verwenden (er erfordert jedoch eine Instanz von E):

@SuppressWarnings("unchecked")
public Container(E instance) {
    this.clazz = (Class<E>) instance.getClass();
}

Ja, dies funktioniert auch ohne Generika genauso - mit Generika wird die Instanziierung dieses Containers etwas redundant (Sie müssen zweimal angeben, was "E" ist).
David Citron

Nun, das passiert, wenn Sie Java und Generika verwenden ... sie sind nicht hübsch und es gibt schwerwiegende Einschränkungen ...
Mike Stone

6

Wenn Sie den Klassennamen während der Instanziierung nicht zweimal eingeben möchten, wie in:

new SomeContainer<SomeType>(SomeType.class);

Sie können die Factory-Methode verwenden:

<E> SomeContainer<E> createContainer(Class<E> class); 

Wie in:

public class Container<E> {

    public static <E> Container<E> create(Class<E> c) {
        return new Container<E>(c);
    }

    Class<E> c;

    public Container(Class<E> c) {
        super();
        this.c = c;
    }

    public E createInstance()
            throws InstantiationException,
            IllegalAccessException {
        return c.newInstance();
    }

}

6

Java erlaubt leider nicht, was Sie tun möchten. Siehe die offizielle Problemumgehung :

Sie können keine Instanz eines Typparameters erstellen. Der folgende Code verursacht beispielsweise einen Fehler bei der Kompilierung:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

Um dieses Problem zu umgehen, können Sie durch Reflektion ein Objekt eines Typparameters erstellen:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

Sie können die Append-Methode wie folgt aufrufen:

List<String> ls = new ArrayList<>();
append(ls, String.class);

Könnten Sie mir bitte sagen, warum Sie dabei abstimmen? Ich verstehe nicht, warum die offizielle Problemumgehung eine schlechte Lösung ist. Vielen Dank.
Neepsnikeep

Ich denke, Sie erhalten Abstimmungen, weil Ihre Antwort im Wesentlichen die gleiche ist wie die von Justin Rudd: stackoverflow.com/a/75254/103412
Torsten

5

Wenn Sie zur Kompilierungszeit mit E arbeiten, ist Ihnen der tatsächliche generische Typ "E" nicht wirklich wichtig (entweder verwenden Sie Reflection oder arbeiten mit einer Basisklasse vom generischen Typ). Lassen Sie also die Unterklasse die Instanz von E bereitstellen.

Abstract class SomeContainer<E>
{

    abstract protected  E createContents();
    public doWork(){
        E obj = createContents();
        // Do the work with E 

     }
}


BlackContainer extends SomeContainer<Black>{
    Black createContents() {
        return new  Black();
    }
}

4

Sie können verwenden:

Class.forName(String).getConstructor(arguments types).newInstance(arguments)

Sie müssen jedoch den genauen Klassennamen angeben, einschließlich der Pakete, z. java.io.FileInputStream. Ich habe dies verwendet, um einen Parser für mathematische Ausdrücke zu erstellen.


15
Und wie erhält man zur Laufzeit den genauen Klassennamen des generischen Typs?
David Citron

Sie müssten es mit einer Instanz dieser Klasse speichern. Machbar, aber kaum bequem. Wenn Ihr Generikum ein Mitglied vom Typ E (oder T oder was auch immer) hatte, ist es gerecht, seinen Binärnamen zu erhalten foo.getClass().getName(). Woher kommt DIESE Instanz? Ich übergebe derzeit einen an einen Konstruktor in dem Projekt, an dem ich gerade arbeite.
Mark Storer

3

Hoffe das ist nicht zu spät um zu helfen !!!

Java ist Typensicherheit, nur Object kann eine Instanz erstellen.

In meinem Fall kann ich keine Parameter an die createContentsMethode übergeben. Meine Lösung besteht darin, anstelle aller folgenden Antworten Erweiterungen zu verwenden.

private static class SomeContainer<E extends Object> {
    E e;
    E createContents() throws Exception{
        return (E) e.getClass().getDeclaredConstructor().newInstance();
    }
}

Dies ist mein Beispielfall, bei dem ich keine Parameter übergeben kann.

public class SomeContainer<E extends Object> {
    E object;

    void resetObject throws Exception{
        object = (E) object.getClass().getDeclaredConstructor().newInstance();
    }
}

Wenn Sie Reflection verwenden, wird ein Laufzeitfehler erstellt, wenn Sie Ihre generische Klasse ohne Objekttyp erweitern. Um Ihren generischen Typ auf Objekt zu erweitern, konvertieren Sie diesen Fehler in einen Kompilierungszeitfehler.


3

Verwenden Sie die TypeToken<T>Klasse:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}

Wenn Sie Guave anstelle von GSON verwenden, ist es etwas anders:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen

2

Ich dachte, ich könnte das tun, war aber ziemlich enttäuscht: Es funktioniert nicht, aber ich denke, es lohnt sich immer noch, es zu teilen.

Vielleicht kann jemand korrigieren:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface SomeContainer<E> {
    E createContents();
}

public class Main {

    @SuppressWarnings("unchecked")
    public static <E> SomeContainer<E> createSomeContainer() {
        return (SomeContainer<E>) Proxy.newProxyInstance(Main.class.getClassLoader(),
                new Class[]{ SomeContainer.class }, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Class<?> returnType = method.getReturnType();
                return returnType.newInstance();
            }
        });
    }

    public static void main(String[] args) {
        SomeContainer<String> container = createSomeContainer();

    [*] System.out.println("String created: [" +container.createContents()+"]");

    }
}

Es produziert:

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
    at Main.main(Main.java:26)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

Zeile 26 ist die mit der [*].

Die einzig praktikable Lösung ist die von @JustinRudd


2

Eine Verbesserung der Antwort von @ Noah.

Änderungsgrund

a] Ist sicherer, wenn mehr als 1 generischer Typ verwendet wird, falls Sie die Reihenfolge geändert haben.

b] Die Signatur eines generischen Klassentyps ändert sich von Zeit zu Zeit, sodass Sie nicht von unerklärlichen Ausnahmen in der Laufzeit überrascht werden.

Robuster Code

public abstract class Clazz<P extends Params, M extends Model> {

    protected M model;

    protected void createModel() {
    Type[] typeArguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
    for (Type type : typeArguments) {
        if ((type instanceof Class) && (Model.class.isAssignableFrom((Class) type))) {
            try {
                model = ((Class<M>) type).newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Oder verwenden Sie den Einzeiler

Einzeiliger Code

model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();

2

was Sie tun können ist -

  1. Deklarieren Sie zuerst die Variable dieser generischen Klasse

    2. Machen Sie dann einen Konstruktor daraus und instanziieren Sie das Objekt

  2. Verwenden Sie es dann, wo immer Sie es verwenden möchten

Beispiel-

1

private Class<E> entity;

2

public xyzservice(Class<E> entity) {
        this.entity = entity;
    }



public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
        return entity.newInstance();
    }

3.

E e = getEntity (Entität);


0

Wie Sie sagten, können Sie dies aufgrund der Typlöschung nicht wirklich tun. Sie können dies mithilfe von Reflection tun, es erfordert jedoch viel Code und viel Fehlerbehandlung.


Wie würden Sie es überhaupt mit Reflexion machen? Die einzige Methode, die ich sehe, ist Class.getTypeParameters (), die jedoch nur die deklarierten Typen zurückgibt, nicht die Laufzeittypen.
David Citron


0

Wenn du meinst, new E() dann ist es unmöglich. Und ich würde hinzufügen, dass es nicht immer richtig ist - woher weißt du, ob E einen öffentlichen Konstruktor ohne Argumente hat? Sie können die Erstellung jedoch jederzeit an eine andere Klasse delegieren, die weiß, wie eine Instanz erstellt wird - dies kann auch Class<E>Ihr benutzerdefinierter Code sein

interface Factory<E>{
    E create();
}    

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

1
Dies funktioniert in meinem Beispiel in der ursprünglichen Frage nicht. Die Oberklasse für SomeContainerist einfach Object. Gibt daher this.getClass().getGenericSuperclass()a Class(Klasse java.lang.Object) zurück, nicht a ParameterizedType. Dies wurde auch bereits von Peer-Antwort stackoverflow.com/questions/75175/… hervorgehoben .
David Citron

1
Völlig falsch: Ausnahme im Thread "main" java.lang.ClassCastException: java.lang.Class kann nicht in java.lang.reflect.ParameterizedType umgewandelt werden
Aubin

0

Sie können dies mit dem folgenden Snippet erreichen:

import java.lang.reflect.ParameterizedType;

public class SomeContainer<E> {
   E createContents() throws InstantiationException, IllegalAccessException {
      ParameterizedType genericSuperclass = (ParameterizedType)
         getClass().getGenericSuperclass();
      @SuppressWarnings("unchecked")
      Class<E> clazz = (Class<E>)
         genericSuperclass.getActualTypeArguments()[0];
      return clazz.newInstance();
   }
   public static void main( String[] args ) throws Throwable {
      SomeContainer< Long > scl = new SomeContainer<>();
      Long l = scl.createContents();
      System.out.println( l );
   }
}

4
Völlig falsch: Ausnahme im Thread "main" java.lang.ClassCastException: java.lang.Class kann nicht in java.lang.reflect.ParameterizedType umgewandelt werden
Aubin

0

Es gibt verschiedene Bibliotheken, die Emit ähnlichen Techniken wie im Robertson-Artikel für Sie aufgelöst werden können. Hier ist eine Implementierung createContents, die TypeTools verwendet , um die durch E dargestellte Rohklasse aufzulösen:

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

Dies setzt voraus, dass getClass () in eine Unterklasse von SomeContainer aufgelöst wird und andernfalls fehlschlägt, da der tatsächlich parametrisierte Wert von E zur Laufzeit gelöscht wurde, wenn er nicht in einer Unterklasse erfasst wurde.


0

Hier ist eine Implementierung createContents, die TypeTools verwendet , um die durch Efolgende Klasse dargestellte Rohklasse aufzulösen :

E createContents() throws Exception {
  return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}

Dieser Ansatz funktioniert nur, wenn er SomeContainerin Unterklassen unterteilt ist, sodass der tatsächliche Wert von Ein einer Typdefinition erfasst wird:

class SomeStringContainer extends SomeContainer<String>

Andernfalls wird der Wert von E zur Laufzeit gelöscht und kann nicht wiederhergestellt werden.


-1

Sie können mit einem Klassenladeprogramm und dem Klassennamen eventuell einige Parameter festlegen.

final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);

Dies ist schlimmer als nur das Klassenobjekt zu übergeben
Newacct

Sie müssen den Klassenlader überhaupt nicht explizit referenzieren.
Stefan Reich

-1

Hier ist eine verbesserte Lösung, basierend auf ParameterizedType.getActualTypeArguments @noah, @Lars Bohl und einigen anderen .

Erste kleine Verbesserung in der Implementierung. Factory sollte keine Instanz zurückgeben, sondern einen Typ. Sobald Sie die Instanz mit zurückgeben Class.newInstance(), reduzieren Sie den Nutzungsumfang. Weil nur Konstruktoren ohne Argumente so aufgerufen werden können. Eine bessere Möglichkeit besteht darin, einen Typ zurückzugeben und einem Client die Auswahl des Konstruktors zu ermöglichen, den er aufrufen möchte:

public class TypeReference<T> {
  public Class<T> type(){
    try {
      ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
      if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
        throw new IllegalStateException("Could not define type");
      }
      if (pt.getActualTypeArguments().length != 1){
        throw new IllegalStateException("More than one type has been found");
      }
      Type type = pt.getActualTypeArguments()[0];
      String typeAsString = type.getTypeName();
      return (Class<T>) Class.forName(typeAsString);

    } catch (Exception e){
      throw new IllegalStateException("Could not identify type", e);
    }

  }
}

Hier sind Anwendungsbeispiele. @Lars Bohl hat nur einen Weg aufgezeigt, wie man durch Erweiterung reifizierter Genener wird. @noah nur durch Erstellen einer Instanz mit {}. Hier sind Tests, um beide Fälle zu demonstrieren:

import java.lang.reflect.Constructor;

public class TypeReferenceTest {

  private static final String NAME = "Peter";

  private static class Person{
    final String name;

    Person(String name) {
      this.name = name;
    }
  }

  @Test
  public void erased() {
    TypeReference<Person> p = new TypeReference<>();
    Assert.assertNotNull(p);
    try {
      p.type();
      Assert.fail();
    } catch (Exception e){
      Assert.assertEquals("Could not identify type", e.getMessage());
    }
  }

  @Test
  public void reified() throws Exception {
    TypeReference<Person> p = new TypeReference<Person>(){};
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }

  static class TypeReferencePerson extends TypeReference<Person>{}

  @Test
  public void reifiedExtenension() throws Exception {
    TypeReference<Person> p = new TypeReferencePerson();
    Assert.assertNotNull(p);
    Assert.assertEquals(Person.class.getName(), p.type().getName());
    Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
    Assert.assertNotNull(ctor);
    Person person = (Person) ctor.newInstance(NAME);
    Assert.assertEquals(NAME, person.name);
  }
}

Hinweis: Sie können die Clients zwingen, TypeReferenceimmer zu verwenden, {}wenn eine Instanz erstellt wird, indem Sie diese Klasse abstrakt machen : public abstract class TypeReference<T>. Ich habe es nicht getan, nur um einen gelöschten Testfall zu zeigen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.