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.

0voto

Braian Coronel Punkte 19992

Beachten Sie, dass ein generischer Typ in Kotlin ohne einen Standardkonstruktor auskommen kann.

 implementation("org.objenesis","objenesis", "3.2")

    val fooType = Foo::class.java
    var instance: T = try {
        fooType.newInstance()
    } catch (e: InstantiationException) {
//            Use Objenesis because the fooType class has not a default constructor
        val objenesis: Objenesis = ObjenesisStd()
        objenesis.newInstance(fooType)
    }

0voto

Adam Rosenfield Punkte 373807

Wie Sie schon sagten, ist das wegen der Schriftlöschung nicht wirklich möglich. Sie können es irgendwie mit Reflexion tun, aber es erfordert eine Menge Code und eine Menge von Fehlerbehandlung.

0 Stimmen

Wie würde man das mit der Reflexion überhaupt machen? Die einzige Methode, die ich sehe, ist Class.getTypeParameters(), aber das gibt nur die deklarierten Typen zurück, nicht die Laufzeittypen.

0 Stimmen

0voto

michal.jakubeczy Punkte 6138

Ich habe mich von Iras Lösung inspirieren lassen und sie leicht abgewandelt.

abstract class SomeContainer<E>
{
    protected E createContents() {
        throw new NotImplementedException();
    }

    public void doWork(){
        E obj = createContents();
        // Do the work with E 
     }
}

class BlackContainer extends SomeContainer<Black>{
    // this method is optional to implement in case you need it
    protected Black createContents() {
        return new Black();
    }
}

Für den Fall, dass Sie E Instanz können Sie implementieren createContents Methode in Ihrer abgeleiteten Klasse (oder lassen Sie sie nicht implementiert, falls Sie sie nicht benötigen.

0voto

Alexandr Punkte 8796

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

Erste kleine Verbesserung bei der Umsetzung. Factory sollte keine Instanz zurückgeben, sondern einen Typ. Sobald Sie eine Instanz zurückgeben, indem Sie Class.newInstance() reduzieren Sie einen Nutzungsumfang. Denn nur Konstruktoren ohne Argumente können auf diese Weise aufgerufen werden. Ein besserer Weg ist es, einen Typ zurückzugeben und dem Kunden die Wahl zu lassen, 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 Beispiel für die Verwendung. @Lars Bohl hat nur einen einzigen Weg gezeigt, wie man verifizierte Genetiken über eine Erweiterung erhält. @noah nur über das Erzeugen einer Instanz mit {} . Hier sind Tests, die beide Fälle 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);
  }
}

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

0voto

Jonathan Punkte 5518

Es gibt verschiedene Bibliotheken, die folgende Probleme lösen können E für Sie mit ähnlichen Techniken wie in dem Artikel von Robertson. Hier ist eine Implementierung von createContents die die TypeTools um die durch E repräsentierte rohe Klasse aufzulösen:

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

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

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