Crear instancia de tipo genérico en Java?

¿Es posible crear una instancia de un tipo genérico en Java? Estoy pensando, basado en lo que he visto, que la respuesta es no ( debido a la borradura de tipo ), pero me interesaría que alguien pudiera ver algo que me faltaba:

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

EDITAR: Resulta que los Super Type Tokens se pueden usar para resolver mi problema, pero requiere una gran cantidad de código basado en la reflexión, como han indicado algunas de las respuestas a continuación.

Dejaré esto abierto por un tiempo para ver si alguien presenta algo dramáticamente diferente al Artículo Artima de Ian Robertson.

Estás en lo correcto. No puedes hacer una new E() . Pero puedes cambiarlo a

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

Es un dolor. Pero funciona. Envolverlo en el patrón de fábrica lo hace un poco más tolerable.

No sé si esto ayuda, pero cuando subclases (incluido anónimamente) un tipo genérico, la información de tipo está disponible a través de la reflexión. p.ej,

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

Entonces, cuando subclases a Foo, obtienes una instancia de Bar, por ejemplo,

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

Pero es mucho trabajo, y solo funciona para subclases. Puede ser útil sin embargo.

Necesitarás algún tipo de fábrica abstracta de un tipo u otro para pasar el dinero a:

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

En Java 8 puede usar la interfaz funcional del Supplier para lograr esto con bastante facilidad:

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

Construirías esta clase así:

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

La syntax String::new en esa línea es una referencia de constructor .

Si su constructor toma argumentos, puede usar una expresión lambda en su lugar:

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

Si necesita una nueva instancia de un argumento de tipo dentro de una clase genérica, haga que sus constructores exijan su clase …

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

Pros:

  • Mucho más simple (y menos problemático) que el enfoque de Super Type Token (STT) de Robertson.
  • Mucho más eficiente que el enfoque STT (que se comerá su teléfono celular para el desayuno).

Contras:

  • No se puede pasar Class a un constructor predeterminado (razón por la cual Foo es final). Si realmente necesita un constructor predeterminado, siempre puede agregar un método setter, pero luego debe recordar llamarla más tarde.
  • Objeción de Robertson … Más barras que una oveja negra (aunque especificar la clase de argumento de tipo una vez más no te matará exactamente). Y, contrariamente a las afirmaciones de Robertson, esto no viola el principio DRY de todos modos porque el comstackdor asegurará la corrección del tipo.
  • No del todo Foo prueba. Para empezar … newInstance() arrojará un wobbler si la clase de argumento de tipo no tiene un constructor predeterminado. Sin embargo, esto se aplica a todas las soluciones conocidas.
  • Carece de la encapsulación total del enfoque STT. Sin embargo, no es un gran problema (teniendo en cuenta la sobrecarga de rendimiento escandaloso de STT).

Puede hacer esto ahora y no requiere un montón de código de reflexión.

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

Por supuesto, si necesita llamar al constructor que requerirá un poco de reflexión, pero eso está muy bien documentado, ¡este truco no lo es!

Aquí está el JavaDoc para TypeToken .

Piense en un enfoque más funcional: en lugar de crear algo de E de la nada (que es claramente un olor a código), pase una función que sepa cómo crear uno, es decir,

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

De Java Tutorial – Restricciones sobre generics :

No se pueden crear instancias de parámetros de tipo

No puede crear una instancia de un parámetro de tipo. Por ejemplo, el siguiente código causa un error en tiempo de comstackción:

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

Como solución alternativa, puede crear un objeto de un parámetro de tipo a través de la reflexión:

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

Puede invocar el método de agregar de la siguiente manera:

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

Aquí hay una opción que se me ocurrió, puede ayudar:

 public static class Container { private Class clazz; public Container(Class clazz) { this.clazz = clazz; } public E createContents() throws Exception { return clazz.newInstance(); } } 

EDITAR: Alternativamente puede usar este constructor (pero requiere una instancia de E):

 @SuppressWarnings("unchecked") public Container(E instance) { this.clazz = (Class) instance.getClass(); } 

Si no desea escribir el nombre de clase dos veces durante la creación de instancias como en:

 new SomeContainer(SomeType.class); 

Puedes usar el método de fábrica:

  SomeContainer createContainer(Class class); 

Como en:

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

Cuando trabajas con E en tiempo de comstackción, realmente no te importa el tipo genérico real “E” (ya sea que utilices la reflexión o trabajes con la clase base de tipo genérico), así que deja que la subclase proporcione una instancia de 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(); } } 

Puedes usar:

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

Pero debe proporcionar el nombre de clase exacto, incluidos los paquetes, p. Ej. java.io.FileInputStream . Usé esto para crear un analizador de expresiones matemáticas.

Desafortunadamente, Java no permite lo que quiere hacer. Ver http://docs.oracle.com/javase/tutorial/java/generics/restrictions.html#createObjects

No puede crear una instancia de un parámetro de tipo. Por ejemplo, el siguiente código causa un error en tiempo de comstackción:

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

Como solución alternativa, puede crear un objeto de un parámetro de tipo a través de la reflexión:

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

Puede invocar el método de agregar de la siguiente manera:

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

Pensé que podría hacerlo, pero bastante decepcionado: no funciona, pero creo que todavía vale la pena compartirlo.

Tal vez alguien puede corregir:

 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 línea 26 es la que tiene el [*] .

La única solución viable es la de @JustinRudd

Una imporración de la respuesta de @ Noah.

Razón para el cambio

a] Es más seguro si se utiliza más de 1 tipo genérico en caso de que haya cambiado el pedido.

b] Una firma de tipo genérico de clase cambia periódicamente para que no te sorprendan las excepciones inexplicadas en el tiempo de ejecución.

Código 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); } } } }

O usa el delineador

Código de una línea

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

Como dijiste, no puedes hacerlo debido a la borradura de tipo. Puede hacerlo utilizando la reflexión, pero requiere mucho código y mucho manejo de errores.

Si te refieres a una new E() entonces es imposible. Y agregaría que no siempre es correcto, ¿cómo se puede saber si E tiene un constructor público no args? Pero siempre puedes delegar la creación a alguna otra clase que sepa cómo crear una instancia: puede ser Class o tu código personalizado como este

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

Puede lograr esto con el siguiente fragmento:

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

Hay varias bibliotecas que pueden resolver E por usted usando técnicas similares a las que se discutió en el artículo de Robertson. Aquí hay una implementación de createContents que usa TypeTools para resolver la clase raw representada por E:

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

Esto supone que getClass () se resuelve en una subclase de SomeContainer y, de lo contrario, fallará dado que el valor parametrizado real de E se habrá borrado en el tiempo de ejecución si no se captura en una subclase.

Aquí hay una implementación de createContents que usa TypeTools para resolver la clase raw representada por E :

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

Este enfoque solo funciona si SomeContainer está subclasificado por lo que el valor real de E se captura en una definición de tipo:

 class SomeStringContainer extends SomeContainer 

De lo contrario, el valor de E se borra en el tiempo de ejecución y no es recuperable.

Puedes con un cargador de clases y el nombre de clase, eventualmente algunos parámetros.

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