it-swarm.it

Creare un'istanza di tipo generico in Java?

È possibile creare un'istanza di un tipo generico in Java? Sto pensando in base a ciò che ho visto che la risposta è no (dovuto a type erasure), ma sarei interessato se qualcuno potesse vedere qualcosa che mi manca:

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

EDIT: Risulta che Super Type Tokens potrebbe essere usato per risolvere il mio problema, ma richiede un sacco di codice basato sulla riflessione, come indicato da alcune delle risposte sottostanti.

Lascerò aperto per un po 'per vedere se qualcuno presenta qualcosa di drammaticamente diverso da quello di Ian Robertson Artima Articolo .

525
David Citron

Hai ragione. Non puoi fare new E(). Ma puoi cambiarlo

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

È un dolore. Ma funziona. Avvolgendolo nel modello di fabbrica lo rende un po 'più tollerabile.

305
Justin Rudd

Non so se questo aiuta, ma quando si sottoclasse (includendo anonimo) un tipo generico, le informazioni sul tipo sono disponibili tramite riflessione. per esempio.,

public abstract class Foo<E> {

  public E instance;  

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

}

Quindi, quando sottoclassi Foo, ottieni un'istanza di Bar, ad es.

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

Ma è un sacco di lavoro, e funziona solo per sottoclassi. Può essere utile però.

121
noah

Avrai bisogno di una sorta di fabbrica astratta di un tipo o dell'altro per passare il tempo a:

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 è possibile utilizzare l'interfaccia Supplier functional per ottenere questo risultato abbastanza facilmente:

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

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

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

Costruiresti questa classe in questo modo:

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

La sintassi String::new su quella riga è un riferimento costruttore .

Se il tuo costruttore accetta argomenti puoi usare invece un'espressione 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

Se hai bisogno di una nuova istanza di un argomento di tipo all'interno di una classe generica, fai in modo che i costruttori richiedano la sua classe ...

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

Uso:

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

Professionisti:

  • Molto più semplice (e meno problematico) dell'approccio Super Type Token (STT) di Robertson.
  • Molto più efficiente dell'approccio STT (che mangerà il tuo cellulare per la colazione).

Contro:

  • Impossibile passare Class a un costruttore predefinito (che è il motivo per cui Foo è definitivo). Se hai davvero bisogno di un costruttore predefinito puoi sempre aggiungere un metodo setter, ma poi devi ricordarti di chiamarla più tardi.
  • L'obiezione di Robertson ... Più barre di una pecora nera (anche se specificando la classe argomento tipo un'altra volta non ti ucciderà esattamente). E contrariamente alle affermazioni di Robertson, ciò non viola il principio DRY perché il compilatore garantirà la correttezza del tipo.
  • Non completamente Foo<L>proof. Per i principianti ... newInstance() lancerà un wobbler se la classe dell'argomento type non ha un costruttore predefinito. Ciò vale comunque per tutte le soluzioni conosciute.
  • Manca l'incapsulamento totale dell'approccio STT. Non è un grosso problema però (considerando il sovraccarico di prestazioni scandaloso di STT).
20
R2D2M2

Puoi farlo ora e non richiede un mucchio di codice di riflessione.

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

Naturalmente se hai bisogno di chiamare il costruttore che richiederà qualche riflessione, ma questo è molto ben documentato, questo trucco non lo è!

Ecco il JavaDoc per TypeToken .

19
user177800

Pensa ad un approccio più funzionale: invece di creare un E dal nulla (che è chiaramente un odore di codice), passa una funzione che sa come crearne uno, ad es.

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

Da Esercitazione Java - Limitazioni su Generics :

Impossibile creare istanze di parametri tipo

Non è possibile creare un'istanza di un parametro di tipo. Ad esempio, il seguente codice causa un errore in fase di compilazione:

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

Come soluzione alternativa, puoi creare un oggetto di un parametro di tipo tramite reflection:

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

È possibile richiamare il metodo append come segue:

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

Ecco un'opzione che ho trovato, può aiutare:

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: In alternativa puoi usare questo costruttore (ma richiede un'istanza di E):

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

Se non vuoi digitare il nome della classe due volte durante l'istanza come in:

new SomeContainer<SomeType>(SomeType.class);

Puoi usare il metodo di fabbrica:

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

Come 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 sfortunatamente non consente quello che vuoi fare. Vedi la soluzione ufficiale :

Non è possibile creare un'istanza di un parametro di tipo. Ad esempio, il seguente codice causa un errore in fase di compilazione:

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

Come soluzione alternativa, puoi creare un oggetto di un parametro di tipo tramite reflection:

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

È possibile richiamare il metodo append come segue:

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

Quando lavori con E al momento della compilazione non ti interessa il vero tipo generico "E" (o usi la riflessione o lavori con la classe base del tipo generico) quindi lascia che la sottoclasse fornisca l'istanza di 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

Puoi usare:

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

Ma è necessario fornire il nome esatto della classe, compresi i pacchetti, ad es. Java.io.FileInputStream. L'ho usato per creare un parser di espressioni matematiche.

3
Jaroslav Smid

Un impornimento della risposta di @ Noah. 

Ragione per cambiare

a] È più sicuro se vengono utilizzati più di 1 tipo generico in caso di modifica dell'ordine.

b] Una firma di tipo generico di classe cambia di volta in volta in modo tale da non essere sorpresi da eccezioni inspiegabili nel runtime.

Codice robusto

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

Oppure usa l'unica fodera

One Line Code

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

Pensavo di poterlo fare, ma piuttosto deluso: non funziona, ma penso che valga ancora la pena condividerlo. 

Forse qualcuno può correggere:

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

    }
}

Produce:

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)

La riga 26 è quella con [*].

L'unica soluzione praticabile è quella di @JustinRudd

2

Esistono varie librerie che possono risolvere E per te usando tecniche simili a quelle trattate dall'articolo di Robertson. Ecco una implementazione di createContents che utilizza TypeTools per risolvere la classe raw rappresentata da E:

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

Ciò presuppone che getClass () risolva in una sottoclasse di SomeContainer e fallirà altrimenti poiché il valore parametrizzato effettivo di E sarà stato cancellato in fase di esecuzione se non è stato acquisito in una sottoclasse.

0
Jonathan

Se vuoi dire new E() Allora è impossibile. E aggiungo che non è sempre corretto - come fai a sapere se E ha un costruttore no-arg pubblico? Ma puoi sempre delegare la creazione ad un'altra classe che sa come creare un'istanza - può essere Class<E> o il tuo codice personalizzato come questo

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

Puoi ottenerlo con il seguente frammento:

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

Ecco un'implementazione di createContents che utilizza TypeTools per risolvere la classe raw rappresentata da E:

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

Questo approccio funziona solo se SomeContainer è sottoclasse in modo tale che il valore effettivo di E venga catturato in una definizione di tipo:

class SomeStringContainer extends SomeContainer<String>

Altrimenti il ​​valore di E viene cancellato in fase di runtime e non è recuperabile.

0
Jonathan

Come hai detto, non puoi farlo davvero a causa della cancellazione dei caratteri. Puoi farlo utilizzando Riflessione, ma richiede molto codice e molta gestione degli errori.

0
Adam Rosenfield

Spero che non sia troppo tardi per aiutare !!!

Java è type-safety, solo Object è in grado di creare un'istanza.

Nel mio caso non posso passare i parametri al metodo createContents. La mia soluzione sta usando estende invece di tutte le risposte a soffietto.

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

Questo è il mio esempio che non posso passare i parametri.

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

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

L'uso di reflection crea un errore di run time, se si estende la classe generica con nessun tipo di oggetto. Per estendere il tipo generico a oggetto convertire questo errore in errore di compilazione del tempo. 

0
Se Song

Ecco una soluzione migliorata, basata su ParameterizedType.getActualTypeArguments, già menzionata da @noah, @Lars Bohl e alcuni altri. 

Primo piccolo miglioramento nell'implementazione. Factory non dovrebbe restituire istanza, ma un tipo. Non appena restituisci l'istanza usando Class.newInstance() riduci un ambito di utilizzo. Perché solo costruttori senza argomenti possono essere invocati in questo modo. Un modo migliore è quello di restituire un tipo e consentire a un client di scegliere, quale costruttore desidera richiamare:

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

  }
}

Ecco alcuni esempi di utilizzo. @Lars Bohl ha mostrato solo un modo signale per ottenere il genenerico reificato tramite l'estensione. @noah solo creando un'istanza con {}. Ecco i test per dimostrare entrambi i casi:

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: è possibile forzare i client di TypeReference a utilizzare sempre {} quando viene creata un'istanza rendendo questa classe astratta: public abstract class TypeReference<T>. Non l'ho fatto, solo per mostrare il caso di test cancellato. 

0
Alexandr