it-swarm-eu.dev

Vytvořit instanci generického typu v jazyce Java?

Je možné v jazyce Java vytvořit instanci generického typu? Přemýšlím na základě toho, co jsem viděl, že odpověď je no ( kvůli vymazání typu ), ale zajímalo by mě, jestli někdo může vidět něco, co mi chybí:

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

EDIT: Ukazuje se, že Super Type Tokeny by se dalo použít k vyřešení mého problému, ale vyžaduje to spoustu kódů založených na reflexi, jak naznačují některé z odpovědí níže.

Nechám to trochu otevřít, abych zjistil, jestli někdo přijde s něčím dramaticky jiným, než je Ian Robertson článek Artima .

533
David Citron

Máte pravdu. Nemůžete udělat new E(). Ale můžete to změnit

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

Je to bolest. Ale funguje to. Zabalení do továrního vzoru je o něco tolerovatelnější.

305
Justin Rudd

Dunno, pokud to pomůže, ale když podtřída (včetně anonymně) generického typu, informace o typu jsou k dispozici prostřednictvím reflexe. např.,

public abstract class Foo<E> {

  public E instance;  

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

}

Když tedy podtřídu Foo, dostanete instanci Bar, např.

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

Ale je to spousta práce a funguje pouze pro podtřídy. Může být šikovný.

121
noah

Budete potřebovat nějakou abstraktní továrnu jednoho druhu nebo jiného, ​​abyste mohli přenést babku na:

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

V jazyce Java 8 můžete použít funkční rozhraní Supplier function k dosažení tohoto cíle:

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

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

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

Tuto třídu byste postavili takto:

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

Syntaxe String::new na tomto řádku je odkaz konstruktoru .

Pokud váš konstruktor vezme argumenty, můžete místo toho použít výraz lambda:

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

Pokud potřebujete novou instanci typového argumentu uvnitř generické třídy, pak si konstruktéři vyžádají svou třídu ...

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

Používání:

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

Klady:

  • Mnohem jednodušší (a méně problematický) než přístup Robertsona Super Type Token (STT).
  • Mnohem efektivnější než STT přístup (který bude jíst váš mobil na snídani).

Nevýhody:

  • Nelze předat třídu do výchozího konstruktoru (což je důvod, proč je Foo konečný). Pokud opravdu potřebujete výchozí konstruktor, můžete vždy přidat metodu setteru, ale pak musíte pamatovat na její pozdější volání.
  • Robertsonova námitka ... Více barů než černá ovce (i když specifikování třídy argumentů typu ještě jednou nezabije přesně vás). A na rozdíl od Robertsonových tvrzení to neporušuje princip DRY, protože kompilátor zajistí správnost typu.
  • Ne zcela Foo<L>proof. Pro začátek ... newInstance() hodí wobler, pokud třída argumentů typu nemá výchozí konstruktor. To platí stejně pro všechna známá řešení.
  • Chybí úplné zapouzdření přístupu STT. Není to velký problém (s ohledem na pobuřující režijní výkon STT).
20
R2D2M2

Můžete to udělat teď a nevyžaduje spoustu kódu reflexe.

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

Samozřejmě, pokud potřebujete zavolat konstruktoru, který bude vyžadovat nějaký odraz, ale to je velmi dobře zdokumentováno, tento trik není!

Zde je JavaDoc pro TypeToken .

19
user177800

Přemýšlejte o funkčnějším přístupu: místo vytvoření nějakého E z ničeho (což je jednoznačně pach kódu), předejte funkci, která ví, jak ji vytvořit, tj.

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

Od Java Tutorial - Omezení na Generics :

Nelze vytvořit instance typových parametrů

Nelze vytvořit instanci parametru typu. Následující kód například způsobí chybu při kompilaci:

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

Jako řešení můžete vytvořit objekt typu parametru prostřednictvím reflexe:

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

Metodu přidávání můžete vyvolat následujícím způsobem:

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

Zde je možnost, se kterou jsem přišel, může pomoci:

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

EDIT: Alternativně můžete použít tento konstruktor (ale vyžaduje instanci E):

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

Pokud nechcete, aby se během vytváření instance dvakrát zadával název třídy, postupujte takto:

new SomeContainer<SomeType>(SomeType.class);

Můžete použít tovární metodu:

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

Jako v:

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 bohužel neumožňuje to, co chcete dělat. Podívejte se na oficiální řešení :

Nelze vytvořit instanci parametru typu. Následující kód například způsobí chybu při kompilaci:

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

Jako řešení můžete vytvořit objekt typu parametru prostřednictvím reflexe:

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

Metodu přidávání můžete vyvolat následujícím způsobem:

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

Když pracujete s E v době kompilace, nezajímá vás skutečný generický typ "E" (buď použijete reflexi nebo pracujete se základní třídou generického typu), takže podtřída poskytuje instanci třídy E. 

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

Můžeš použít:

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

Musíte však uvést přesný název třídy, včetně balíčků, např.. Java.io.FileInputStream. Použil jsem to k vytvoření syntaktického analyzátoru matematických výrazů.

3
Jaroslav Smid

Důležitá odpověď Noeho. 

Důvod pro změnu

a] Je bezpečnější, pokud se v případě změny objednávky použije více než jeden obecný typ.

b] Označení generického typu třídy se čas od času mění, takže nebudete překvapeni nevysvětlitelnými výjimkami v běhu.

Robustní kód

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

Nebo použijte jednu vložku

Jeden řádkový kód

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

Myslel jsem, že to můžu udělat, ale docela zklamaný: to nefunguje, ale myslím, že to stále stojí za to sdílet. 

Možná někdo může opravit:

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()+"]");

    }
}

Vytváří:

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)

Řádek 26 je ten s [*].

Jediným životaschopným řešením je řešení @JustinRudd

2

Existují různé knihovny, které mohou vyřešit E pro vás pomocí technik podobných tomu, co Robertsonův článek diskutoval. Zde je implementace createContents, která používá TypeTools k vyřešení raw třídy reprezentované E:

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

Předpokládá se, že getClass () vyřeší podtřídu SomeContainer a jinak se nezdaří, protože skutečná parametrizovaná hodnota E bude při běhu vymazána, pokud není zachycena v podtřídě.

0
Jonathan

Pokud máte na mysli new E() , Pak to není možné. A dodal bych, že to není vždy správné - jak víte, zda má E veřejný konstruktor no-args? Ale můžete vždy delegovat tvorbu na jinou třídu, která ví, jak vytvořit instanci - může to být Class<E> nebo tento vlastní kód

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

Toho můžete dosáhnout následujícím úryvkem:

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

Zde je implementace createContents, která používá TypeTools k vyřešení raw třídy reprezentované E:

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

Tento přístup funguje pouze tehdy, je-li SomeContainer subclassed, takže skutečná hodnota E je zachycena v definici typu:

class SomeStringContainer extends SomeContainer<String>

Jinak je hodnota E vymazána za běhu a není obnovitelná.

0
Jonathan

Jak jste řekl, nemůžete to opravdu udělat kvůli vymazání typu. Můžete to udělat pomocí odrazu, ale vyžaduje hodně kódu a spoustu chyb.

0
Adam Rosenfield

Doufám, že to není příliš pozdě na pomoc!

Java je typová bezpečnost, pouze Object je schopen vytvořit instanci.

V mém případě nemohu předat parametry createContents metodě. Moje řešení používá rozšíření namísto všech níže uvedených odpovědí.

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

Toto je můj příklad, který nemohu předat parametry.

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

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

Pomocí reflexe vytvořte chybu běhu, pokud rozšíříte obecnou třídu o žádný typ objektu. Chcete-li rozšířit obecný typ na objekt, převeďte tuto chybu na kompilaci časové chyby. 

0
Se Song

Zde je vylepšené řešení, založené na ParameterizedType.getActualTypeArguments, již zmíněné @noah, @Lars Bohl, a některé další. 

První malé zlepšení implementace. Továrna by neměla vrátit instanci, ale typ. Jakmile vrátíte instanci pomocí Class.newInstance(), snížíte rozsah použití. Protože pouze takové konstruktéry bez argumentů mohou být vyvolány takto. Lepším způsobem je vrátit typ a umožnit klientovi vybrat si, který konstruktor chce vyvolat:

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

  }
}

Zde je příklad použití. @Lars Bohl ukázala pouze signe způsob, jak získat reified geneneric přes rozšíření. @noah pouze prostřednictvím vytvoření instance s {}. Zde jsou testy, které ukazují oba případy:

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

Poznámka: vy můžete vynutit klienty TypeReference vždy používat {} když instance je vytvořena vytvořením této třídy abstraktní: public abstract class TypeReference<T>. Neudělala jsem to, jen abych ukázala vymazaný test. 

0
Alexandr