¿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 .
Respuestas:
Estás en lo correcto. No se puede hacer
new E()
. Pero puedes cambiarlo aEs un dolor. Pero funciona. Envolverlo en el patrón de fábrica lo hace un poco más tolerable.
fuente
Class<?>
referencia usando Guava y TypeToken, ¡ mira esta respuesta para el código y los enlaces!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,
Entonces, cuando subclases Foo, obtienes una instancia de Bar, por ejemplo,
Pero es mucho trabajo y solo funciona para subclases. Aunque puede ser útil.
fuente
Foo
no es abstracta. Pero, ¿por qué solo funciona en subclases anónimas de Foo? Supongamos que hacemosFoo
concreto (lo dejamos fueraabstract
), ¿por quénew Foo<Bar>();
resultará en un error, mientrasnew Foo<Bar>(){};
que no? (Excepción: "La clase no se puede convertir a ParameterizedType")<E>
enclass Foo<E>
no está vinculado a ningún tipo particular. Verá el excepcional comportamiento cada vez queE
no está estáticamente enlazado, como en:new Foo<Bar>()
,new Foo<T>() {...}
oclass 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 deE
pero aún no está consolidada. Y en el último caso, debería ser obvio queE
todavía está sin consolidar.class Fizz extends Foo<Bar>
: en este caso, los usuarios deFizz
obtener algo que esFoo<Bar>
y no puede ser otra cosa que aFoo<Bar>
. Entonces, en este caso, el compilador se complace en codificar esa información en los metadatos de la claseFizz
y ponerla a disposición comoParameterizedType
código de reflexión. Cuando crea una clase interna anónima comonew Foo<Bar>() {...}
si estuviera haciendo lo mismo, excepto queFizz
el compilador genera un nombre de clase "anónimo" que no sabrá hasta que se compila la clase externa.Foo<Bar<Baz>>
. Creará una instancia de laParameterizedTypeImpl
cual no se puede crear explícitamente. Por lo tanto, es una buena idea verificar sigetActualTypeArguments()[0]
está devolviendo aParameterizedType
. Si es así, entonces desea obtener el tipo sin formato y crear una instancia de eso en su lugar.En Java 8 puede usar la
Supplier
interfaz funcional para lograr esto con bastante facilidad:Construirías esta clase así:
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:
fuente
SomeContainer stringContainer = new SomeContainer(String::new);
?Necesitará algún tipo de fábrica abstracta de un tipo u otro para pasar el dinero a:
fuente
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üísticoClass
oConstructor
como la reflexión trae un mundo de dolor.SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
fuente
class GenericHome<T> extends Home<T>{}
Si necesita una nueva instancia de un argumento de tipo dentro de una clase genérica, haga que sus constructores exijan su clase ...
Uso:
Pros:
Contras:
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.fuente
Puede hacer esto ahora y no requiere un montón de código de reflexión.
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 .
fuente
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
fuente
Exception
uso de la captura en suSupplier<E>
lugar.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:
Como solución alternativa, puede crear un objeto de un parámetro de tipo a través de la reflexión:
Puede invocar el método append de la siguiente manera:
fuente
Aquí hay una opción que se me ocurrió, puede ayudar:
EDITAR: Alternativamente, puede usar este constructor (pero requiere una instancia de E):
fuente
Si no desea escribir el nombre de la clase dos veces durante la creación de instancias, como en:
Puedes usar el método de fábrica:
Como en:
fuente
Desafortunadamente, Java no permite lo que quieres hacer. Vea la solución oficial :
fuente
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.
fuente
Puedes usar:
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.fuente
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.¡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
createContents
método. Mi solución es usar extensiones en lugar de todas las respuestas a continuación.Este es mi caso de ejemplo que no puedo pasar parámetros.
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.
fuente
Usa la
TypeToken<T>
clase:fuente
(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
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:
Produce:
La línea 26 es la que tiene el
[*]
.La única solución viable es la de @JustinRudd
fuente
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
O usa el forro único
Código de una línea
fuente
lo que puedes hacer es
Primero declara la variable de esa clase genérica
2. Luego crea un constructor y crea una instancia de ese objeto
Luego úsalo donde quieras usarlo
ejemplo-
1
2
3)
fuente
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.
fuente
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 serClass<E>
su código personalizado como estefuente
fuente
SomeContainer
es simplementeObject
. Por lo tanto,this.getClass().getGenericSuperclass()
devuelve unaClass
(clase java.lang.Object), no aParameterizedType
. Esto ya fue señalado por la respuesta de pares stackoverflow.com/questions/75175/… también.Puede lograr esto con el siguiente fragmento:
fuente
Existen varias bibliotecas que pueden resolver
E
por usted utilizando técnicas similares a las que discutió el artículo de Robertson. Aquí hay una implementación decreateContents
que usa TypeTools para resolver la clase sin procesar representada por E: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.
fuente
Aquí hay una implementación de
createContents
que usa TypeTools para resolver la clase sin procesar representada porE
:Este enfoque solo funciona si
SomeContainer
se subclasifica, por lo que el valor real deE
se captura en una definición de tipo:De lo contrario, el valor de E se borra en tiempo de ejecución y no es recuperable.
fuente
Puede con un cargador de clases y el nombre de la clase, eventualmente algunos parámetros.
fuente
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: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:Nota: se puede obligar a los clientes de
TypeReference
siempre 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.fuente