¿Crear instancia de tipo genérico en Java?

576

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

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

EDITAR: Resulta que los Super Type Tokens podrían usarse para resolver mi problema, pero requiere una gran cantidad de código basado en la reflexión, como lo indican algunas de las respuestas a continuación.

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

David Citron
fuente
2
Acabo de probar el rendimiento en un dispositivo Android. 10000 operaciones y: 8-9 ms toma una nueva SomeClass (), 9-11 ms toma Factory <SomeClass> .createInstance () y 64-71 ms toma la reflexión más corta: SomeClass z = SomeClass.class.newInstance (). Y todas las pruebas se realizaron en un solo bloque try-catch. Reflexión newInstance () arroja 4 excepciones diferentes, ¿recuerdas? Así que decidí usar el patrón de fábrica
Deepscorn
Ver también: stackoverflow.com/a/5684761/59087
Dave Jarvis
44
Con Java 8, ahora puede pasar una referencia de constructor o una lambda, lo que hace que este problema sea bastante trivial. Vea mi respuesta a continuación para más detalles.
Daniel Pryden
Creo que es una mala idea escribir ese código, son formas más elegantes y legibles de resolver el problema subyacente.
Krzysztof Cichocki
1
@DavidCitron "por un momento" , dijo ... Han pasado once años desde entonces ...
MC Emperor

Respuestas:

332

Estás en lo correcto. No se puede 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.

Justin Rudd
fuente
11
Sí, vi esa solución, pero solo funciona si ya tienes una referencia a un objeto Class del tipo que deseas instanciar.
David Citron
11
Si lo se. Sería bueno si pudieras hacer E.class, pero eso simplemente te da Object.class debido a la eliminación :)
Justin Rudd
66
Ese es el enfoque correcto para este problema. Por lo general, no es lo que deseas, pero es lo que obtienes.
Joachim Sauer
38
¿Y cómo se llama al método createContents ()?
Alexis Dufrenoy
8
Esta ya no es la única forma de hacer esto, ahora hay una mejor manera que no requiere pasar una Class<?>referencia usando Guava y TypeToken, ¡ mira esta respuesta para el código y los enlaces!
129

No sé si esto ayuda, pero cuando subclases (incluso de forma anónima) un tipo genérico, la información del 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 subclases 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.

Noé
fuente
2
Sí, esto es bueno, especialmente si la clase genérica es abstracta, puedes hacerlo en las subclases concretas :)
Pierre Henry
Este método también funciona si la clase Foono es abstracta. Pero, ¿por qué solo funciona en subclases anónimas de Foo? Supongamos que hacemos Fooconcreto (lo dejamos fuera abstract), ¿por qué new Foo<Bar>();resultará en un error, mientras new Foo<Bar>(){};que no? (Excepción: "La clase no se puede convertir a ParameterizedType")
Tim Kuipers
2
@TimKuipers El <E>en class Foo<E>no está vinculado a ningún tipo particular. Verá el excepcional comportamiento cada vez que Eno está estáticamente enlazado, como en: new Foo<Bar>(), new Foo<T>() {...}o class Fizz <E> extends Foo<E>. El primer caso no está vinculado estáticamente, se borra en el momento de la compilación. El segundo caso sustituye a otra variable de tipo (T) en lugar de Epero aún no está consolidada. Y en el último caso, debería ser obvio que Etodavía está sin consolidar.
William Price
44
Un ejemplo de vinculación estática del parámetro tipo sería class Fizz extends Foo<Bar>: en este caso, los usuarios de Fizzobtener algo que es Foo<Bar>y no puede ser otra cosa que a Foo<Bar>. Entonces, en este caso, el compilador se complace en codificar esa información en los metadatos de la clase Fizzy ponerla a disposición como ParameterizedTypecódigo de reflexión. Cuando crea una clase interna anónima como new Foo<Bar>() {...}si estuviera haciendo lo mismo, excepto que Fizzel compilador genera un nombre de clase "anónimo" que no sabrá hasta que se compila la clase externa.
William Price
44
Cabe señalar que esto no funcionará si los argumentos de tipo también son un ParameterizedType. Por ejemplo, Foo<Bar<Baz>>. Creará una instancia de la ParameterizedTypeImplcual no se puede crear explícitamente. Por lo tanto, es una buena idea verificar si getActualTypeArguments()[0]está devolviendo a ParameterizedType. Si es así, entonces desea obtener el tipo sin formato y crear una instancia de eso en su lugar.
enamorado
106

En Java 8 puede usar la Supplierinterfaz funcional 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();
  }
}

Construirías esta clase así:

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

La sintaxis String::newen 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));
Daniel Pryden
fuente
66
Bueno Evita la reflexión y tener que tratar excepciones.
Bruno Leite
2
Tan agradable. Desafortunadamente para los usuarios de Android, esto requiere un nivel de API 24 o superior.
Michael Updike
1
De acuerdo con un enfoque menos problemático y funcional, esta será una respuesta aceptada en mi opinión
Tomás
2
... y no es diferente a esta respuesta aún más antigua que muestra que el patrón técnico detrás de esto es incluso más antiguo que el soporte de Java para expresiones lambda y referencias de métodos, mientras que incluso puede usar ese código anterior con ellos una vez que haya actualizado su compilador ...
Holger
¿Sería posible poner solo SomeContainer stringContainer = new SomeContainer(String::new);?
Aaron Franke
87

Necesitará algún tipo de fábrica abstracta de un tipo u otro para pasar el dinero 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();
    }
}
Tom Hawtin - tackline
fuente
..y cómo se ve Factory.create ()?
OhadR
66
@OhadR Factory<>es una interfaz y no hay cuerpo. El punto es que necesita una capa de indirecciones para pasar el dinero a los métodos que "conocen" el código requerido para construir una instancia. Es mucho mejor hacer esto con código normal en lugar de metalingüístico Classo Constructorcomo la reflexión trae un mundo de dolor.
Tom Hawtin - tackline
2
Hoy en día se puede crear una instancia de fábrica con una expresión de referencia método como este: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());
    }

}
Lars Bohl
fuente
3
Un buen enfoque por este código puede causar ClassCastException si usa en el genérico un tipo genérico. Luego, recupera el argumento actualType Debe verificar que también es ParamterizedType y, si es así, devolver su RawType (o algo mejor que esto). Otro problema con esto es cuando ampliamos más de una vez que este código también arrojará la ClassCastExeption.
Damian Leszczyński - Vash
3
Causado por: java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl no se puede enviar a java.lang.Class
juan
@ DamianLeszczyński-Vash también fallará, por ejemploclass GenericHome<T> extends Home<T>{}
Holger el
22

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<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 comerá su teléfono celular para 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 luego debe recordar llamarlo 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 al contrario de lo que afirma Robertson, esto no viola el principio DRY de todos modos porque el compilador asegurará la corrección de tipo.
  • No del todo Foo<L>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 (considerando la sobrecarga de rendimiento escandaloso de STT).
R2D2M2
fuente
22

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á cierta reflexión, pero eso está muy bien documentado, ¡este truco no lo es!

Aquí está el JavaDoc para TypeToken .

Lukasz Stelmach
fuente
66
Esta solución funciona para un conjunto limitado de casos, al igual que la respuesta de @ noah con reflexión. Los probé todos hoy ... Y terminé pasando una instancia de la clase de parámetro a la clase parametrizada (para poder llamar a .newInstance ()). Deficiencia muy grande de "genéricos" ... nuevo Foo <Bar> (Bar.class); ... clase Foo <T> {clase final privada <T> mTFactory; Foo (Clase <T> tClass) {mTFactory = tClass; ...} T instancia = tFactory.newInstance (); }
yvolk
Esto funciona de todos los casos, incluso los métodos de fábrica estáticas que tienen parámetros genéricos
13

Piense en un enfoque más funcional: en lugar de crear un 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<E> makeone) {
     return makeone.call(); // most simple case clearly not that useful
}
Ingo
fuente
66
Técnicamente no estás pasando una función, estás pasando un objeto de función (también conocido como functor ).
Lorne Laliberte
3
O tal vez para superar el Exceptionuso de la captura en su Supplier<E>lugar.
Martin D
10

De Java Tutorial - Restricciones sobre 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 append de la siguiente manera:

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

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();
}
Mike Stone
fuente
Sí, esto funciona igual incluso sin genéricos: con los genéricos, la creación de instancias de este contenedor se vuelve un poco redundante (debe especificar qué es "E" dos veces).
David Citron
bueno, eso es lo que sucede cuando usas Java y genéricos ... no son bonitos, y hay limitaciones severas ...
Mike Stone
6

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

new SomeContainer<SomeType>(SomeType.class);

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

}
jb.
fuente
6

Desafortunadamente, Java no permite lo que quieres hacer. Vea 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 append de la siguiente manera:

List<String> ls = new ArrayList<>();
append(ls, String.class);
Neepsnikeep
fuente
¿Podrías decirme por qué votas mal cuando lo haces? No veo por qué la solución oficial es una mala solución. Gracias.
Neepsnikeep
Supongo que obtiene votos, porque su respuesta es esencialmente la misma que la de Justin Rudd: stackoverflow.com/a/75254/103412
Torsten
5

Cuando trabaja con E en tiempo de compilación, realmente no le importa el tipo genérico real "E" (ya sea que use la reflexión o trabaje con la clase base de tipo genérico), así que deje que la subclase proporcione la 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();
    }
}
Ira
fuente
4

Puedes usar:

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

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

hichris123
fuente
15
¿Y cómo se obtiene el nombre de clase exacto del tipo genérico en tiempo de ejecución?
David Citron
Tendría que guardarlo usando una instancia de esa clase. Factible, aunque difícilmente conveniente. Si su genérico tenía un miembro de tipo E (o T o lo que sea), obtener su nombre binario es justo foo.getClass().getName(). ¿De dónde viene esa instancia? Actualmente estoy pasando uno a un constructor en el proyecto en el que ahora estoy trabajando.
Mark Storer
3

¡Espero que no sea demasiado tarde para ayudar!

Java es de tipo seguro, solo Object puede crear una instancia.

En mi caso no puedo pasar parámetros al createContentsmétodo. Mi solución es usar extensiones en lugar de todas las respuestas a continuación.

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 crear error de tiempo de ejecución, si extiende su clase genérica con ningún tipo de objeto. Para extender su tipo genérico a objeto, convierta este error en error de tiempo de compilación.

Se Song
fuente
3

Usa la TypeToken<T>clase:

public class MyClass<T> {
    public T doSomething() {
        return (T) new TypeToken<T>(){}.getRawType().newInstance();
    }
}
cacheoff
fuente
Si usa guayaba en lugar de GSON, es un poco diferente:(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Jacob van Lingen
2

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

Quizás 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

Luigi R. Viggiano
fuente
2

Una mejora 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 cambie el orden.

b] Una firma de tipo genérico de clase cambia de vez en cuando para que no se sorprenda con 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 único

Código de una línea

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

lo que puedes hacer es

  1. Primero declara la variable de esa clase genérica

    2. Luego crea un constructor y crea una instancia de ese objeto

  2. Luego úsalo donde quieras usarlo

ejemplo-

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

Sudhanshu Jain
fuente
0

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

Adam Rosenfield
fuente
¿Cómo lo harías incluso usando la reflexión? El único método que veo es Class.getTypeParameters (), pero eso solo devuelve los tipos declarados, no los tipos de tiempo de ejecución.
David Citron
Estas hablando de esto? artima.com/weblogs/viewpost.jsp?thread=208860
David Citron
0

Si te refieres, 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 otra clase que sepa cómo crear una instancia, puede ser Class<E>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++;    
  }
}
Pavel Feldman
fuente
0
return   (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
Rachid
fuente
1
Esto no funciona en mi ejemplo en la pregunta original. La superclase para SomeContaineres simplemente Object. Por lo tanto, this.getClass().getGenericSuperclass()devuelve una Class(clase java.lang.Object), no a ParameterizedType. Esto ya fue señalado por la respuesta de pares stackoverflow.com/questions/75175/… también.
David Citron
1
Totalmente incorrecto: Excepción en el hilo "principal" java.lang.ClassCastException: java.lang.Class no se puede enviar a java.lang.reflect.ParameterizedType
Aubin
0

Puede lograr esto con el siguiente fragmento:

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 );
   }
}
Bogdan Veliscu
fuente
44
Totalmente incorrecto: Excepción en el hilo "principal" java.lang.ClassCastException: java.lang.Class no se puede enviar a java.lang.reflect.ParameterizedType
Aubin
0

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

Jonathan
fuente
0

Aquí hay una implementación de createContentsque usa TypeTools para resolver la clase sin procesar representada por E:

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

Este enfoque solo funciona si SomeContainerse subclasifica, por lo que el valor real de Ese 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 es recuperable.

Jonathan
fuente
-1

Puede con un cargador de clases y el nombre de la 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);
Roald
fuente
esto es peor que simplemente pasar el objeto de clase
newacct
No necesita hacer referencia explícita al cargador de clases en absoluto.
Stefan Reich
-1

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

Primera pequeña mejora en la implementación. La fábrica no debe devolver la instancia, sino un tipo. Tan pronto como devuelva la instancia utilizando Class.newInstance(), reduce el alcance de uso. Porque solo los constructores sin argumentos pueden ser invocados de esta manera. 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 forma signe de obtener la genificación reificada a través de la extensión. @noah solo mediante la creación de 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: se puede obligar a los clientes de TypeReferencesiempre uso {}cuando instancia se crea al hacer esta clase abstracta: public abstract class TypeReference<T>. No lo he hecho, solo para mostrar el caso de prueba borrado.

Alexandr
fuente