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.

378voto

Justin Rudd Punkte 5146

Sie haben Recht. Sie können nicht tun new E() . Aber Sie können es ändern in

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

Es ist eine Qual. Aber es funktioniert. Das Umwickeln mit dem Fabrikmuster macht es ein wenig erträglicher.

15 Stimmen

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

11 Stimmen

Ja, ich weiß. Es wäre schön, wenn Sie E.class tun könnte, aber das gibt Ihnen einfach Object.class wegen der Löschung :)

6 Stimmen

Das ist der richtige Ansatz für dieses Problem. Normalerweise ist es nicht das, was man sich wünscht, aber es ist das, was man bekommt.

155voto

Daniel Pryden Punkte 56882

In Java 8 können Sie die Supplier funktionale Schnittstelle, um dies recht 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 wie folgt konstruieren:

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

Die Syntax String::new in dieser Zeile ist ein Bauherrenreferenz .

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

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

7 Stimmen

Der war gut. So wird vermieden, dass man nachdenken und Ausnahmen behandeln muss.

2 Stimmen

Sehr schön. Leider erfordert dies für Android-Nutzer API-Level 24 oder höher.

3 Stimmen

Und es ist nicht anders als diese noch ältere Antwort die zeigen, dass das technische Muster dahinter sogar älter ist als Javas Unterstützung für Lambda-Ausdrücke und Methodenreferenzen, während Sie sogar diesen älteren Code mit ihnen verwenden können, sobald Sie Ihren Compiler aktualisiert haben

147voto

noah Punkte 20711

Ich weiß nicht, ob dies hilft, aber wenn Sie (auch anonym) einen generischen Typ subclass, die Typinformationen ist über Reflexion 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 unterklassifizieren, erhalten Sie eine Instanz von Bar, z. B.,

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

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

2 Stimmen

Ja, das ist schön, vor allem, wenn die generische Klasse abstrakt ist, können Sie dies in den konkreten Unterklassen tun :)

0 Stimmen

Diese Methode funktioniert auch, wenn die Klasse Foo ist nicht abstrakt. Aber warum funktioniert es nur bei anonymen Unterklassen von Foo? Nehmen wir an, wir machen Foo Beton (wir lassen weg abstract ), warum wird new Foo<Bar>(); führen zu einem Fehler, während new Foo<Bar>(){}; nicht? (Ausnahme: "Klasse kann nicht in ParameterizedType gecastet werden")

3 Stimmen

@TimKuipers Die <E> en class Foo<E> ist nicht an einen bestimmten Typ gebunden. Sie werden das außergewöhnliche Verhalten sehen, wenn E ist nicht statisch gebunden, wie in: new Foo<Bar>() , new Foo<T>() {...} , oder class Fizz <E> extends Foo<E> . Der erste Fall ist nicht statisch gebunden, er ist Gelöscht zur Kompilierzeit. Im zweiten Fall wird eine andere Typvariable (T) anstelle von E ist aber noch ungebunden. Und im letzten Fall sollte es offensichtlich sein, dass E ist noch ungebunden.

90voto

Tom Hawtin - tackline Punkte 142461

Sie brauchen eine Art abstrakte Fabrik, an die Sie die Verantwortung 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();
    }
}

0 Stimmen

Und wie sieht Factory.create() aus?

7 Stimmen

@OhadR Factory<> ist eine Schnittstelle und hat daher keinen Körper. Der Punkt ist, dass man eine Schicht von Indirektionen braucht, um die Verantwortung an Methoden weiterzugeben, die den erforderlichen Code zur Erstellung einer Instanz "kennen". Es ist viel besser, dies mit normalem Code zu tun, als mit metalinguistischen Class ou Constructor da die Reflexion eine ganze Welt des Schmerzes mit sich bringt.

2 Stimmen

Heutzutage können Sie eine Fabrikinstanz mit einem Methodenreferenzausdruck wie diesem erstellen: SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);

27voto

Lars Bohl Punkte 991
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());
    }

}

4 Stimmen

Guter Ansatz durch diesen Code kann ClassCastException verursachen, wenn Sie in der generischen einen generischen Typ verwenden. Dann sollten Sie das Argument actualType zurückgeben. Sie sollten prüfen, ob es auch ParamterizedType ist und wenn ja, seinen RawType zurückgeben (oder etwas Besseres als das). Ein weiteres Problem ist, wenn wir erweitern mehr als einmal dieser Code wird auch die ClassCastExeption werfen.

3 Stimmen

Verursacht durch: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl kann nicht in java.lang.Class gecastet werden

0 Stimmen

@DamianLeszczynski-Vash wird auch scheitern mit z.B. class GenericHome<T> extends Home<T>{}

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