it-swarm-eu.dev

Instanz eines generischen Typs in Java erstellen?

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 ist (aufgrund von Typlöschung), aber es würde mich interessieren, wenn jemand etwas sieht, was mir fehlt:

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

BEARBEITEN: Es stellt sich heraus, dass Super Type Tokens verwendet werden könnte, um mein Problem zu lösen, aber es erfordert eine Menge reflexionsbasierten Codes, wie einige der folgenden Antworten gezeigt haben.

Ich lasse das für eine Weile offen, um zu sehen, ob jemand etwas anderes als Ian Robertsons Artima-Artikel herausfindet.

525
David Citron

Du hast Recht. Sie können new E() nicht tun. Sie können es aber ändern

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

Es ist ätzend. Aber es funktioniert. Das Einwickeln in das Fabrikmuster macht es ein wenig erträglicher.

305
Justin Rudd

Keine Ahnung, ob dies hilft, aber wenn Sie einen generischen Typ (einschließlich anonym) in eine Unterklasse 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 die Unterklasse Foo wählen, 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.

121
noah

Sie brauchen eine Art abstrakte Fabrik der einen oder anderen Art, um den Dollar weiterzugeben:

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

In Java 8 können Sie die Funktion Supplier verwenden, um dies ganz leicht 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::new in 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));
86
Daniel Pryden
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());
    }

}
23
Lars Bohl

Wenn Sie eine neue Instanz eines Typarguments in einer generischen Klasse benötigen, fordern Sie Ihre Konstruktoren dazu auf, ihre Klasse zu fordern.

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);

Pros:

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

Nachteile:

  • Klasse kann nicht an einen Standardkonstruktor übergeben werden (weshalb Foo final ist). Wenn Sie wirklich einen Standardkonstruktor benötigen, können Sie immer eine Setter-Methode hinzufügen. Später müssen Sie jedoch daran denken, sie anzurufen.
  • Robertson's Einwand ... Mehr Barren als ein schwarzes Schaf (obwohl Sie die Typargumentklasse noch einmal angeben müssen, wird Sie das nicht wirklich töten). Entgegen den Behauptungen von Robertson verstößt dies ohnehin nicht gegen den DRY - Prinzip, da der Compiler die Richtigkeit des Typs sicherstellen wird.
  • Nicht vollständig Foo<L>proof. Für den Anfang ... wirft newInstance() einen Wobbler, wenn die Typargumentklasse keinen Standardkonstruktor hat. Dies gilt jedoch für alle bekannten Lösungen.
  • Es fehlt die Gesamtkapselung des STT-Ansatzes. Dies ist jedoch keine große Sache (in Anbetracht des außergewöhnlichen Leistungsaufwandes von STT).
20
R2D2M2

Sie können dies jetzt tun, und es ist kein Haufen 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()) {};
    }
}

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

Hier ist das JavaDoc für TypeToken .

19
user177800

Stellen Sie sich einen funktionaleren Ansatz vor: Anstatt etwas E aus dem Nichts zu erzeugen (was eindeutig ein Codegeruch ist), übergeben Sie eine Funktion, die weiß, wie man ein solches erstellt, d. H.

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

Aus Java-Tutorial - Einschränkungen für Generics :

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

Als Problemumgehung können Sie ein Objekt mit einem Typparameter durch Reflektion 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);
8

Hier ist eine Option, die ich mir ausgedacht habe, es 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();
}
6
Mike Stone

Wenn Sie den Klassennamen während der Instantiierung 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
jb.

Java lässt leider nicht zu, was Sie tun möchten. Siehe den offiziellen Workaround :

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

Als Problemumgehung können Sie ein Objekt mit einem Typparameter durch Reflektion 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);
5
Neepsnikeep

Wenn Sie zur Kompilierzeit mit E arbeiten, kümmert es Sie nicht wirklich um den generischen Typ "E" (entweder verwenden Sie Reflection oder arbeiten mit der Basisklasse des generischen Typs). 

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
Ira

Verwenden Sie die Klasse TypeToken<T> :

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

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 damit einen Parser für mathematische Ausdrücke erstellt.

3
Jaroslav Smid

Eine Verbesserung von @ Noahs Antwort. 

Änderungsgrund

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

b] Eine generische Typensignatur der Klasse ändert sich von Zeit zu Zeit, so dass 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 einen Liner

Einleitungscode

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

Ich dachte, ich könnte das, aber ziemlich enttäuscht: Es funktioniert nicht, aber ich denke, es lohnt sich trotzdem, 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 dem [*].

Die einzig gangbare Lösung ist die von @JustinRudd

2

Es gibt verschiedene Bibliotheken, die E für Sie auflösen können, indem Sie Techniken verwenden, die denen des Robertson-Artikels ähneln. Hier ist eine Implementierung von createContents, die TypeTools verwendet, um die durch E dargestellte rohe Klasse 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 parametrierte Wert von E zur Laufzeit gelöscht wird, wenn er nicht in einer Unterklasse erfasst wird.

0
Jonathan

Wenn Sie "new E().__" meinen, ist dies unmöglich. Und ich würde hinzufügen, dass es nicht immer richtig ist - woher wissen Sie, ob E über einen öffentlichen No-Args-Konstruktor verfügt?. __ Sie können die Erstellung jedoch jederzeit an andere Klassen delegieren, die wissen, wie eine Instanz erstellt wird - Class<E> oder Ihr benutzerdefinierter Code wie dieser

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

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}
0
Pavel Feldman
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
0
Rachid

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 );
   }
}
0
bogdan

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

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

Dieser Ansatz funktioniert nur, wenn SomeContainer eine Unterklasse enthält, sodass der tatsächliche Wert von E in einer Typdefinition erfasst wird:

class SomeStringContainer extends SomeContainer<String>

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

0
Jonathan

was Sie tun können, ist -

  1. Deklarieren Sie zuerst die Variable dieser generischen Klasse

    2. Erstellen Sie dann einen Konstruktor und instanziieren Sie das Objekt

  2. Verwenden Sie es dann überall dort, wo 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
Sudhanshu Jain

Wie Sie gesagt haben, können Sie es wegen der Löschung von Typen nicht wirklich tun. Sie können dies mit Reflection tun, aber es erfordert viel Code und eine Menge Fehlerbehandlung.

0
Adam Rosenfield

Ich hoffe, es ist nicht zu spät um zu helfen !!!

Java ist typensicher, nur Object kann eine Instanz erstellen.

In meinem Fall kann ich keine Parameter an die createContents-Methode übergeben. Meine Lösung verwendet Erweiterungen statt aller untenstehenden Antworten.

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

Dies ist mein Beispiel, den ich nicht übergeben kann.

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

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

Verwenden Sie Reflection-Laufzeitfehler, wenn Sie Ihre generische Klasse ohne Objekttyp erweitern. Um Ihren generischen Typ zu erweitern, konvertieren Sie diesen Fehler in einen Kompilierzeitfehler. 

0
Se Song

Hier ist eine verbesserte Lösung, basierend auf ParameterizedType.getActualTypeArguments, die bereits von @noah, @Lars Bohl und einigen anderen erwähnt wurde. 

Erste kleine Verbesserung in der Implementierung. Factory sollte keine Instanz, sondern einen Typ zurückgeben. Sobald Sie mit Class.newInstance() eine Instanz zurückgeben, reduzieren Sie den Nutzungsumfang. Weil nur Konstruktoren ohne Argumente so aufgerufen werden können. Besser ist es, einen Typ zurückzugeben und einem Client die Auswahl zu erlauben, welchen Konstruktor 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 ist ein Anwendungsbeispiel. @Lars Bohl hat nur einen wichtigen Weg gezeigt, um durch die Erweiterung genitalisiert zu werden. @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 von TypeReference zwingen, immer {} zu verwenden, wenn die Instanz erstellt wird, indem Sie diese Klasse abstrakt machen: public abstract class TypeReference<T>. Ich habe es nicht getan, nur um gelöschte Testfälle anzuzeigen. 

0
Alexandr