it-swarm-es.com

Crear instancia de tipo genérico en Java?

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

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

EDITAR: Resulta que Super Type Tokens podría usarse para resolver mi problema, pero requiere mucho código basado en la reflexión, como lo han indicado algunas de las respuestas a continuación.

Dejaré esto abierto por un momento para ver si a alguien se le ocurre algo radicalmente diferente a Artículo de Artima de Ian Robertson.

533
David Citron

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

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

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

310
Justin Rudd

No sé si esto ayuda, pero cuando se subclase (incluso de forma anónima) un tipo genérico, la información de tipo está disponible a través de la reflexión. p.ej.,

public abstract class Foo<E> {

  public E instance;  

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

}

Entonces, cuando haces una subclase de Foo, obtienes una instancia de Bar, por ejemplo,

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

Pero es mucho trabajo, y solo funciona para subclases. Aunque puede ser útil.

121
noah

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

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

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

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

Usted construiría esta clase como esta:

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

La sintaxis 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<BigInteger> bigIntegerContainer
    = new SomeContainer<>(() -> new BigInteger(1));
92
Daniel Pryden

Necesitará algún tipo de fábrica abstracta de un tipo u otro para pasar la pelota 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();
    }
}
87
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

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

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

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 consumirá su teléfono celular en el desayuno).

Contras:

  • No se puede pasar la clase a un constructor predeterminado (razón por la cual Foo es final). Si realmente necesita un constructor predeterminado, siempre puede agregar un método de establecimiento, pero debe recordar llamarle más tarde.
  • La 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 principal DRY porque el compilador garantizará la corrección del tipo.
  • No completamente Foo<L>proof. Para empezar, newInstance() lanzará un wobbler si la clase de argumento de tipo no tiene un constructor predeterminado. Esto se aplica a todas las soluciones conocidas aunque de todos modos.
  • Carece de la encapsulación total del enfoque STT. Sin embargo, no es un gran problema (considerando la sobrecarga de rendimiento escandaloso de STT).
20
R2D2M2

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

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

Aquí está el JavaDoc para TypeToken .

18
user177800

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

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

Desde Tutorial de Java - Restricciones en Genéricos :

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 provoca un error en tiempo de compilación:

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

Puede invocar el método de añadir de la siguiente manera:

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

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

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

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

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

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

new SomeContainer<SomeType>(SomeType.class);

Puede utilizar el método de fábrica:

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

Como en:

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.

Desafortunadamente, Java no permite lo que quieres hacer. Ver la solución oficial :

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

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

Puede invocar el método de añadir de la siguiente manera:

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

Cuando trabaja con E en tiempo de compilación, realmente no le importa el tipo genérico real "E" (ya sea que use reflexión o trabaje con una clase base de tipo genérico), por lo tanto, permita que la subclase proporcione una instancia de 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

Puedes usar:

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

Pero debe proporcionar el nombre de clase exacto, incluidos los paquetes, por ejemplo. Java.io.FileInputStream. Utilicé esto para crear un analizador de expresiones matemáticas.

3
Jaroslav Smid

Una imporovación de la respuesta de @Noah.

Razón para el cambio

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

b] Una clase de clase genérica cambia de vez en cuando para que no te sorprendan las excepciones inexplicables en el tiempo de ejecución.

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

O usa el forro

Código de una línea

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

Pensé que podía hacer eso, pero bastante decepcionado: no funciona, pero creo que todavía vale la pena compartirlo.

Tal vez alguien pueda corregir:

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

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

2

Existen varias bibliotecas que pueden resolver E para usted utilizando técnicas similares a las que se discutieron en el artículo de Robertson. Aquí hay una implementación de createContents que usa TypeTools para resolver la clase en bruto 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 fallará de otro modo, ya que el valor parametrizado real de E se habrá borrado en tiempo de ejecución si no se captura en una subclase.

0
Jonathan

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

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

Puedes lograr esto con el siguiente fragmento de código:

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

Aquí hay una implementación de createContents que usa TypeTools para resolver la clase cruda 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<String>

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

0
Jonathan

Como dijiste, realmente no puedes hacerlo debido al borrado de tipo. Puedes hacerlo utilizando la reflexión, pero requiere mucho código y mucho manejo de errores.

0
Adam Rosenfield

Espero que no sea demasiado tarde para ayudar!

Java es de seguridad de tipos, solo Objeto puede crear instancia.

En mi caso no puedo pasar parámetros al método createContents. Mi solución está utilizando se extiende en lugar de todas las siguientes respuestas.

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

Este es mi caso de ejemplo que no puedo pasar parámetros.

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

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

Usando la reflexión, cree un error de tiempo de ejecución, si extiende su clase genérica con ningún tipo de objeto. Para ampliar su tipo genérico a objeto, convierta este error en tiempo de compilación.

0
Se Song

Aquí hay una solución mejorada, basada en ParameterizedType.getActualTypeArguments, ya mencionada por @noah, @Lars Bohl, y algunas otras.

Primera pequeña mejora en la implementación. Fábrica no debe devolver la instancia, sino un tipo. Tan pronto como devuelva la instancia utilizando Class.newInstance(), reducirá el alcance de su uso. Porque solo los constructores sin argumentos pueden ser invocados así. Una mejor manera es devolver un tipo y permitir que un cliente elija, qué constructor desea invocar:

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

  }
}

Aquí hay algunos ejemplos de uso. @Lars Bohl ha mostrado solo una manera significativa de obtener genénica reificada a través de la extensión. @noah solo creando una instancia con {}. Aquí hay pruebas para demostrar ambos casos:

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: puede forzar a los clientes de TypeReference siempre use {} cuando la instancia se crea haciendo esta clase abstracta: public abstract class TypeReference<T>. No lo he hecho, solo para mostrar un caso de prueba borrado.

0
Alexandr