Creare un'istanza di tipo generico in Java?


576

È possibile creare un'istanza di un tipo generico in Java? Sto pensando in base a quello che ho visto che la risposta è no(a causa della cancellazione del tipo ), ma sarei interessato se qualcuno potesse vedere qualcosa che mi manca:

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

EDIT: Si scopre che i token Super Type potrebbero essere utilizzati per risolvere il mio problema, ma richiede molto codice basato sulla riflessione, come indicato da alcune delle risposte di seguito.

Lascerò questo per un po 'per vedere se qualcuno esce con qualcosa di drammaticamente diverso dall'articolo di Artima di Ian Robertson .


2
Prestazioni appena testate su dispositivo Android. 10000 operazioni e: 8-9 ms prende il nuovo SomeClass (), 9-11 ms prende Factory <SomeClass> .createInstance () e 64-71 ms prende il riflesso più breve: SomeClass z = SomeClass.class.newInstance (). E tutti i test erano in un unico blocco try-catch. Reflection newInstance () genera 4 diverse eccezioni, ricordi? Così ho deciso di utilizzare il modello di fabbrica
Deepscorn,


4
Con Java 8, ora puoi passare un riferimento al costruttore o un lambda che rende questo problema abbastanza banale da aggirare. Vedi la mia risposta qui sotto per i dettagli.
Daniel Pryden,

Penso che sia una cattiva idea scrivere questo codice, ci sono modi più eleganti e leggibili per risolvere il problema sottostante.
Krzysztof Cichocki,

1
@DavidCitron "per un po '" ha detto ... Sono passati undici anni da allora ...
MC Emperor

Risposte:


332

Hai ragione. Non si può fare new E(). Ma puoi cambiarlo in

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.


11
Sì, ho visto quella soluzione, ma funziona solo se hai già un riferimento a un oggetto Class del tipo che desideri creare un'istanza.
David Citron,

11
Si lo so. Sarebbe bello se tu potessi fare E.class ma questo ti dà semplicemente Object.class a causa della cancellazione :)
Justin Rudd,

6
Questo è l'approccio corretto per questo problema. Di solito non è quello che desideri, ma è quello che ottieni.
Joachim Sauer,

38
E come si chiama il metodo createContents ()?
Alexis Dufrenoy, l'

8
Questo non è più l'unico modo per farlo, ora c'è un modo migliore che non richiede di passare un Class<?>riferimento usando Guava e TypeToken, vedi questa risposta per il codice e i collegamenti!

129

Non so se questo aiuta, ma quando si esegue la sottoclasse (anche in forma anonima) di un tipo generico, le informazioni sul tipo sono disponibili tramite reflection. 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 esegui la sottoclasse di 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 le sottoclassi. Può essere utile però.


2
Sì, è bello soprattutto se la classe generica è astratta, puoi farlo nelle sottoclassi concrete :)
Pierre Henry

Questo metodo funziona anche se la classe Foonon è astratta. Ma perché funziona solo su sottoclassi anonime di Foo? Supponiamo di rendere Fooconcreto (tralasciamo abstract), perché si new Foo<Bar>();tradurrà in un errore, mentre new Foo<Bar>(){};no? (Eccezione: "La classe non può essere trasmessa a ParameterizedType")
Tim Kuipers il

2
Il @TimKuipers <E>in class Foo<E>non è legato ad alcun tipo particolare. Si vedrà il comportamento eccezionale ogni volta che Enon è staticamente legato, come in: new Foo<Bar>(), new Foo<T>() {...}, o class Fizz <E> extends Foo<E>. Il primo caso non è associato staticamente, viene cancellato in fase di compilazione. Il secondo caso sostituisce un'altra variabile di tipo (T) al posto di Ema non è ancora associata. E nell'ultimo caso dovrebbe essere ovvio che Eè ancora illimitato.
William Price,

4
Un esempio di associazione statica del parametro type sarebbe class Fizz extends Foo<Bar>- in questo caso, gli utenti di Fizzottenere qualcosa che è a Foo<Bar>e non può essere altro che a Foo<Bar>. Quindi, in questo caso, il compilatore è felice di codificare tali informazioni nei metadati della classe Fizze renderle disponibili come ParameterizedTypecodice di riflessione. Quando crei una classe interna anonima come new Foo<Bar>() {...}se stesse facendo la stessa cosa, tranne invece che Fizzil compilatore genera un nome di classe "anonimo" che non conoscerai fino a quando non verrà compilata la classe esterna.
William Price,

4
Va notato che questo non funzionerà se gli argomenti di tipo sono anche ParameterizedType. Ad esempio Foo<Bar<Baz>>,. Creerai un'istanza di ParameterizedTypeImplcui non puoi creare esplicitamente. Pertanto, è una buona idea verificare se getActualTypeArguments()[0]sta restituendo a ParameterizedType. Se lo è, allora vuoi ottenere il tipo grezzo e crearne un'istanza.
schiaccia

106

In Java 8 è possibile utilizzare l' Supplierinterfaccia funzionale per ottenere ciò 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::newsu quella linea è un riferimento del costruttore .

Se il tuo costruttore accetta argomenti, puoi invece usare un'espressione lambda:

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

6
Buona Evita la riflessione e il trattamento delle eccezioni.
Bruno Leite,

2
Così carino. Sfortunatamente per gli utenti Android, questo richiede un livello API 24 o superiore.
Michael Updike,

1
Secondo un approccio meno complicato e funzionale, questa sarà una risposta accettata secondo me
Tomas

2
... e non è diverso da questa risposta ancora più antica che mostra che il modello tecnico dietro è ancora più vecchio del supporto di Java per espressioni lambda e riferimenti a metodi mentre puoi persino usare quel codice più vecchio con loro una volta aggiornato il tuo compilatore ...
Holger

Sarebbe possibile mettere giusto SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke il

87

Avrai bisogno di una sorta di fabbrica astratta di un tipo o di un altro per passare il dollaro 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();
    }
}

..e come appare Factory.create ()?
OhadR,

6
@OhadR Factory<>è un'interfaccia e quindi non c'è nessun corpo. Il punto è che è necessario uno strato di indirette per passare il dollaro a metodi che "conoscono" il codice richiesto per costruire un'istanza. È molto meglio farlo con un codice normale piuttosto che metalinguistico Classo Constructorpoiché la riflessione porta un intero mondo di dolore.
Tom Hawtin - tackline

2
Oggi è possibile creare un'istanza di fabbrica con un'espressione di riferimento del metodo come questa:SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
Lii

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

}

3
Un buon approccio con questo codice può causare ClassCastException se si utilizza un tipo generico generico. Quindi riprendi l'argomento actualType Dovresti verificare che sia anche ParamterizedType e, in tal caso, restituire il suo RawType (o qualcosa di meglio di questo). Un altro problema con questo è quando estendiamo più di una volta che anche questo codice genererà ClassCastExeption.
Damian Leszczyński - Vash,

3
Causato da: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl non può essere trasmesso a java.lang.Class
juan

@ DamianLeszczyński-Vash fallirà anche, ad esempioclass GenericHome<T> extends Home<T>{}
Holger

22

Se hai bisogno di una nuova istanza di un argomento di tipo all'interno di una classe generica, fai richiedere ai tuoi costruttori 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 di Robertson Super Type Token (STT).
  • Molto più efficiente dell'approccio STT (che mangerà il tuo cellulare a colazione).

Contro:

  • Impossibile passare la classe a un costruttore predefinito (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 in seguito.
  • L'obiezione di Robertson ... Più barre che una pecora nera (sebbene specificare una volta la classe dell'argomento tipo non ti ucciderà esattamente). E contrariamente a quanto affermato da Robertson, ciò non viola comunque il principio DRY poiché il compilatore garantirà la correttezza del tipo.
  • Non del tutto Foo<L>prova. Per cominciare ... newInstance()lancerà un wobbler se la classe argomento tipo non ha un costruttore predefinito. Questo vale comunque per tutte le soluzioni conosciute.
  • Manca l'incapsulamento totale dell'approccio STT. Tuttavia, non è un grosso problema (considerando l'eccessivo sovraccarico prestazionale di STT).

22

Puoi farlo ora e non richiede un sacco 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()) {};
    }
}

Ovviamente se devi chiamare il costruttore che richiederà qualche riflessione, ma questo è ben documentato, questo trucco non lo è!

Ecco JavaDoc per TypeToken .


6
Questa soluzione funziona per un numero limitato di casi, proprio come la risposta di @ noah con la riflessione. Li ho provati tutti oggi ... E ho finito per passare un'istanza della classe di parametri alla classe con parametri (per poter chiamare .newInstance ()). Grande carenza di "generici" ... nuovo Foo <Bar> (Bar.class); ... classe Foo <T> {classe finale privata <T> mTFactory; Foo (Class <T> tClass) {mTFactory = tClass; ...} T instance = tFactory.newInstance (); }
yvolk

funziona in tutti i casi, anche metodi di fabbrica statici che accettano parametri generici

13

Pensa a un approccio più funzionale: invece di creare una E dal nulla (che è chiaramente un odore di codice), passa una funzione che sappia crearne una, ovvero

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

6
Tecnicamente non sei di passaggio di una funzione, si sta passando un oggetto funzione (anche conosciuto come un functor ).
Lorne Laliberte,

3
O forse per superare l' Exceptionuso della cattura Supplier<E>invece.
Martin D,

10

Da Java Tutorial - Restrizioni su Generics :

Impossibile creare istanze di parametri di tipo

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

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

Come soluzione alternativa, è possibile 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);

6

Ecco un'opzione che mi è venuta in mente, potrebbe 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();
}

Sì, funziona allo stesso modo anche senza generici - con i generici l'istanza di questo contenitore diventa un po 'ridondante (devi specificare quale "E" è due volte).
David Citron,

bene, è quello che succede quando usi Java e i generici ... non sono belli, e ci sono gravi limiti ...
Mike Stone,

6

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

new SomeContainer<SomeType>(SomeType.class);

È possibile utilizzare 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

Java purtroppo non consente ciò che si desidera fare. Vedi la soluzione ufficiale :

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

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

Come soluzione alternativa, è possibile 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);

Potresti per favore dirmi perché stai effettuando il downvoting? Non vedo perché la soluzione ufficiale sia una cattiva soluzione. Grazie.
Neepsnikeep,

Immagino che abbassi i voti, perché la tua risposta è sostanzialmente la stessa di Justin Rudd: stackoverflow.com/a/75254/103412
Torsten

5

Quando lavori con E in fase di compilazione non ti interessa davvero il tipo generico effettivo "E" (o usi la riflessione o lavori con una classe base di 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

Puoi usare:

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

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


15
E come si ottiene il nome esatto della classe del tipo generico in fase di esecuzione?
David Citron,

Dovresti salvarlo usando un'istanza di quella classe. Fattibile, anche se difficilmente conveniente. Se il tuo generico aveva un membro di tipo E (o T o altro), ottenere il suo nome binario è giusto foo.getClass().getName(). Da dove proviene quell'istanza? Attualmente sto passando uno a un costruttore nel progetto a cui sto lavorando.
Mark Storer,

3

Spero che non sia troppo tardi per aiutare !!!

Java è di tipo sicurezza, solo Object è in grado di creare istanze.

Nel mio caso non riesco a passare i parametri al createContentsmetodo. La mia soluzione sta usando extends invece di tutte le risposte qui sotto.

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

Questo è il mio caso di esempio a cui non riesco a passare i parametri.

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

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

Usando reflection crea un errore di runtime, se estendi la tua classe generica con nessun tipo di oggetto. Per estendere il tipo generico all'oggetto, convertire questo errore in errore di compilazione.


3

Usa la TypeToken<T>classe:

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

Se usi Guava invece di GSON, è un po 'diverso:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen,

2

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

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 il [*].

L'unica soluzione praticabile è quella di @JustinRudd


2

Un imporovement della risposta di @ Noah.

Ragione per cambiare

a] È più sicuro se viene utilizzato 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 da non essere sorpreso 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 la fodera

Codice di una riga

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

2

quello che puoi fare è -

  1. Dichiara innanzitutto la variabile di quella classe generica

    2. Quindi crearne uno e crearne un'istanza

  2. Quindi usalo dove vuoi usarlo

esempio-

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



0

Se intendi, new E() allora è impossibile. E aggiungerei che non è sempre corretto - come fai a sapere se E ha un costruttore pubblico no-args? 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 in questo modo

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

class IntegerFactory implements Factory<Integer>{    
  private static int i = 0; 
  Integer create() {        
    return i++;    
  }
}

0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();

1
Questo non funziona nel mio esempio nella domanda originale. La superclasse per SomeContainerè semplicemente Object. Pertanto, this.getClass().getGenericSuperclass()restituisce a Class(classe java.lang.Object), non a ParameterizedType. Questo è stato già sottolineato dalla risposta dei pari stackoverflow.com/questions/75175/… .
David Citron,

1
Totalmente sbagliato: eccezione nel thread "main" java.lang.ClassCastException: java.lang.Class non può essere trasmesso a java.lang.reflect.ParameterizedType
Aubin

0

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

4
Totalmente sbagliato: eccezione nel thread "main" java.lang.ClassCastException: java.lang.Class non può essere trasmesso a java.lang.reflect.ParameterizedType
Aubin

0

Esistono varie librerie che possono risolvere Eper te usando tecniche simili a quelle di cui parlava l'articolo di Robertson. Ecco una spiegazione di createContentsciò 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 () si risolva in una sottoclasse di SomeContainer e fallirà altrimenti poiché il valore parametrizzato effettivo di E sarà cancellato in fase di esecuzione se non viene acquisito in una sottoclasse.


0

Ecco un'implementazione createContentsche 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è sottoclassato, pertanto il valore effettivo di Eviene acquisito in una definizione di tipo:

class SomeStringContainer extends SomeContainer<String>

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


-1

È possibile con un classloader e il nome della classe, eventualmente alcuni parametri.

final ClassLoader classLoader = ...
final Class<?> aClass = classLoader.loadClass("java.lang.Integer");
final Constructor<?> constructor = aClass.getConstructor(int.class);
final Object o = constructor.newInstance(123);
System.out.println("o = " + o);

questo è peggio del semplice passaggio dell'oggetto di classe
newacct

Non è necessario fare esplicitamente riferimento al programma di caricamento classi.
Stefan Reich,

-1

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

Primo piccolo miglioramento nell'attuazione. Factory non deve restituire un'istanza, ma un tipo. Non appena si restituisce l'istanza in uso, Class.newInstance()si riduce l'ambito di utilizzo. Perché solo i costruttori senza argomenti possono essere invocati in questo modo. Un modo migliore è restituire un tipo e consentire a un client di scegliere quale costruttore desidera invocare:

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 un esempio di utilizzo. @Lars Bohl ha mostrato solo un modo signe per ottenere una genetica reificata attraverso l'estensione. @noah solo tramite la creazione di 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 clienti di TypeReferencesempre uso {}quando istanza viene creato da rendere questa classe astratta: public abstract class TypeReference<T>. Non l'ho fatto, solo per mostrare il caso di prova cancellato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.