Creare un’istanza di tipo generico in Java?

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

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

EDIT: Si è scoperto che i token Super Type potrebbero essere usati per risolvere il mio problema, ma richiede un sacco di codice basato sulla riflessione, come indicato da alcune delle risposte che seguono.

Lascerò aperto per un po ‘per vedere se qualcuno presenta qualcosa di drammaticamente diverso da Artima Article di Ian Robertson.

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

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

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

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

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

Quindi, quando si sottoclass Foo, si ottiene un’istanza di Bar es.

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

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

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

 interface Factory { E create(); } class SomeContainer { private final Factory factory; SomeContainer(Factory factory) { this.factory = factory; } E createContents() { return factory.create(); } } 

In Java 8 è ansible utilizzare l’interfaccia funzionale Supplier per ottenere questo risultato abbastanza facilmente:

 class SomeContainer { private Supplier supplier; SomeContainer(Supplier supplier) { this.supplier = supplier; } E createContents() { return supplier.get(); } } 

Costruiresti questa class in questo modo:

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

La syntax String::new su quella linea è un riferimento costruttore .

Se il costruttore accetta argomenti, puoi utilizzare invece un’espressione lambda:

 SomeContainer bigIntegerContainer = new SomeContainer<>(() -> new BigInteger(1)); 
 package org.foo.com; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; /** * Basically the same answer as noah's. */ public class Home { @SuppressWarnings ("unchecked") public Class getTypeParameterClass() { Type type = getClass().getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType) type; return (Class) paramType.getActualTypeArguments()[0]; } private static class StringHome extends Home { } private static class StringBuilderHome extends Home { } private static class StringBufferHome extends Home { } /** * 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()); } } 

Se hai bisogno di una nuova istanza di un argomento di tipo all’interno di una class generica, fai in modo che i costruttori richiedano la sua class …

 public final class Foo { private Class typeArgumentClass; public Foo(Class typeArgumentClass) { this.typeArgumentClass = typeArgumentClass; } public void doSomethingThatRequiresNewT() throws Exception { T myNewT = typeArgumentClass.newInstance(); ... } } 

Uso:

 Foo barFoo = new Foo(Bar.class); Foo etcFoo = new Foo(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:

  • Imansible 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 class argomento tipo un’altra volta non ti ucciderà esattamente). E contrariamente alle affermazioni di Robertson, ciò non viola comunque il DRY principal perché il compilatore garantirà la correttezza del tipo.
  • Prova non completamente Foo . Per i principianti … newInstance() lancia un wobbler se la class argomento tipo non ha un costruttore predefinito. Questo 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).

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 smpc = new StrawManParameterizedClass() {}; final String string = (String) smpc.type.getRawType().newInstance(); System.out.format("string = \"%s\"",string); } static abstract class StrawManParameterizedClass { final TypeToken type = new TypeToken(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 .

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 una, cioè

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

Da Java Tutorial – Limitazioni su Generics :

Imansible creare istanze di parametri di tipo

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

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

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

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

È ansible richiamare il metodo append come segue:

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

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

 public static class Container { private Class clazz; public Container(Class 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) instance.getClass(); } 

Se non vuoi digitare il nome della class due volte durante l’istanza come in:

 new SomeContainer(SomeType.class); 

Puoi usare il metodo di fabbrica:

  SomeContainer createContainer(Class class); 

Come in:

 public class Container { public static  Container create(Class c) { return new Container(c); } Class c; public Container(Class c) { super(); this.c = c; } public E createInstance() throws InstantiationException, IllegalAccessException { return c.newInstance(); } } 

Quando lavori con E al momento della compilazione non ti interessa l’effettivo tipo generico “E” (o usi la riflessione o lavori con la class base del tipo generico), quindi lascia che la sottoclass fornisca l’istanza di E.

 Abstract class SomeContainer { abstract protected E createContents(); public doWork(){ E obj = createContents(); // Do the work with E } } **BlackContainer extends** SomeContainer{ Black createContents() { return new Black(); } } 

Puoi usare:

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

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

Java sfortunatamente non permette quello che vuoi fare. Vedi http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#createObjects

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

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

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

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

È ansible richiamare il metodo append come segue:

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

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 createContents(); } public class Main { @SuppressWarnings("unchecked") public static  SomeContainer createSomeContainer() { return (SomeContainer) 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 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 linea 26 è quella con [*] .

L’unica soluzione praticabile è quella di @JustinRudd

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 class cambia di volta in volta in modo da non essere sorpresi da eccezioni non spiegate nel runtime.

Codice robusto

 public abstract class Clazz

{ 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) type).newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } } }

Oppure usa l’unica fodera

One Line Code

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

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

Se intendi la new E() allora è imansible. E aggiungo che non è sempre corretto – come fai a sapere se E ha un costruttore pubblico no-args? Ma puoi sempre debind la creazione ad un’altra class che sa come creare un’istanza – può essere Class o il tuo codice personalizzato come questo

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

Puoi ottenerlo con il seguente frammento:

 import java.lang.reflect.ParameterizedType; public class SomeContainer { E createContents() throws InstantiationException, IllegalAccessException { ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass(); @SuppressWarnings("unchecked") Class clazz = (Class) 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 ); } } 

Esistono varie librerie che possono risolvere E per te usando tecniche simili a quelle dell’articolo discusso da Robertson. Ecco una implementazione di createContents che utilizza TypeTools per risolvere la class raw rappresentata da E:

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

Ciò presuppone che getClass () risolva in una sottoclass 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 sottoclass.

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

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

Questo approccio funziona solo se SomeContainer è sottoclassato, quindi il valore effettivo di E viene acquisito in una definizione di tipo:

 class SomeStringContainer extends SomeContainer 

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

È ansible con un classloader e il nome della class, 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);