663 Stimmen

Instanz eines generischen Typs in Java erstellen?

Ist es möglich, eine Instanz eines generischen Typs in Java zu erstellen? Basierend auf dem, was ich gesehen habe, denke ich, dass die Antwort lautet no ( aufgrund von Schriftlöschung ), aber es würde mich interessieren, ob jemand etwas sieht, das ich übersehe:

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

EDIT: Es hat sich herausgestellt, dass Supertyp-Marken könnte verwendet werden, um mein Problem zu lösen, aber es erfordert eine Menge von Reflexion-basierten Code, wie einige der Antworten unten angegeben haben.

Ich werde das Thema noch eine Weile offen lassen, um zu sehen, ob jemand mit etwas kommt, das sich von Ian Robertsons Artima Artikel .

3 Stimmen

Habe gerade die Leistung auf einem Android-Gerät getestet. 10000 Operationen und: 8-9 ms dauert new SomeClass(), 9-11 ms dauert Factory<SomeClass>.createInstance() und 64-71 ms dauert shortest reflection: SomeClass z = SomeClass.class.newInstance(). Und alle Tests waren in einem einzigen try-catch-Block. Reflection newInstance() wirft 4 verschiedene Ausnahmen, erinnern Sie sich? Also habe ich beschlossen, das Factory-Muster zu verwenden

0 Stimmen

4 Stimmen

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

25voto

Sie können dies jetzt tun, ohne einen Haufen Reflexionscode zu benötigen.

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()) {};
    }
}

Wenn Sie den Konstruktor aufrufen müssen, ist natürlich etwas Reflexion erforderlich, aber das ist sehr gut dokumentiert, dieser Trick ist es nicht!

Hier ist die JavaDoc für TypeToken .

6 Stimmen

Diese Lösung funktioniert nur in einer begrenzten Anzahl von Fällen, genau wie @noahs Antwort mit der Reflexion. Ich habe sie heute alle ausprobiert... Und ich endete bei der Übergabe einer Instanz der Parameterklasse an die parametrisierte Klasse (um .newInstance() aufrufen zu können). Sehr großes Manko der "Generics"... new Foo<Bar>(Bar.class); ... class Foo<T> { private final Class<T> mTFactory; Foo(Class<T> tClass) { mTFactory = tClass; ... } T instance = tFactory.newInstance(); }

0 Stimmen

Dies funktioniert in allen Fällen, auch bei statischen Fabrikmethoden, die generische Parameter annehmen

3 Stimmen

public class Q26289147 ? Warum?

25voto

R2D2M2 Punkte 261

Wenn Sie eine neue Instanz eines Typarguments innerhalb einer generischen Klasse benötigen, dann lassen Sie Ihre Konstruktoren dessen Klasse verlangen...

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();
        ...
    }
}

Verwendung:

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

Vorteile:

  • Viel einfacher (und weniger problematisch) als Robertsons Super Type Token (STT) Ansatz.
  • Das ist viel effizienter als der STT-Ansatz (der Ihr Handy zum Frühstück verspeist).

Nachteile:

  • Class kann nicht an einen Standardkonstruktor übergeben werden (deshalb ist Foo final). Wenn Sie wirklich einen Standardkonstruktor benötigen, können Sie immer eine Setter-Methode hinzufügen, aber dann müssen Sie daran denken, ihr später einen Aufruf zu geben.
  • Robertson's Einwand... Mehr Balken als ein schwarzes Schaf (obwohl die Angabe des Typarguments Klasse ein weiteres Mal nicht gerade tödlich ist). Und im Gegensatz zu Robertsons Behauptungen verstößt dies ohnehin nicht gegen das DRY-Prinzip, da der Compiler die Typkorrektheit sicherstellt.
  • Nicht ganz Foo<L> Beweis. Für den Anfang... newInstance() wird einen Wobbler auslösen, wenn die Klasse des Typarguments keinen Standardkonstruktor hat. Dies gilt allerdings für alle bekannten Lösungen.
  • Es fehlt die vollständige Kapselung des STT-Ansatzes. Das ist aber nicht weiter schlimm (wenn man den unverschämten Leistungs-Overhead von STT bedenkt).

20voto

Sergiy Sokolenko Punkte 5707

から Java-Tutorial - Beschränkungen für Generika :

Instanzen vom Typ Parameter können nicht erstellt werden

Sie können keine Instanz eines Typparameters erstellen. Der folgende Code verursacht beispielsweise einen Kompilierungsfehler:

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

Als Abhilfe können Sie ein Objekt eines Typparameters durch Reflexion erstellen:

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

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

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

2 Stimmen

cls.newInstance() wurde abgeschrieben zu Gunsten von cls.getDeclaredConstructor().newInstance() .

0 Stimmen

@antikbd danke für den Hinweis! Ich habe das Beispiel entsprechend aktualisiert.

15voto

Ingo Punkte 35534

Denken Sie über einen funktionelleren Ansatz nach: Anstatt ein E aus dem Nichts zu erzeugen (was eindeutig ein Codegeruch ist), übergeben Sie eine Funktion, die weiß, wie man ein E erzeugt, d. h.

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

6 Stimmen

Technisch gesehen übergeben Sie keine Funktion, sondern eine Funktionsobjekt (auch bekannt als Funktor ).

3 Stimmen

Oder vielleicht, um das Fangen zu überwinden Exception verwenden. Supplier<E> stattdessen.

10voto

Ira Punkte 686

Wenn Sie mit E zur Kompilierzeit arbeiten, ist Ihnen der tatsächliche generische Typ "E" eigentlich egal (entweder verwenden Sie Reflection oder arbeiten mit einer Basisklasse vom generischen Typ), so dass die Unterklasse eine Instanz von E bereitstellen kann.

abstract class SomeContainer<E>
{
    abstract protected E createContents();
    public void doWork(){
        E obj = createContents();
        // Do the work with E 
     }
}

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

1 Stimmen

Mir gefällt dieser Ansatz, da er gut lesbar ist und keine Zauberei mit sich bringt. Der Nachteil ist, dass Sie createContents in jeder abgeleiteten Klasse implementieren müssen. Auch wenn man es nicht braucht. Der andere Ansatz ist, createContents nicht abstrakt zu machen, sondern mit einer leeren Implementierung (return null/throws) ... in diesem Fall kann man es nur bei Bedarf implementieren.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X