setborder - Crear instancia de tipo genérico en Java?
textarea java propiedades (23)
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.
¿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 tipos ), 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 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 al Artículo Artima de Ian Robertson.
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();
}
Como dijiste, realmente no puedes hacerlo debido al borrado de tipo. Puede hacerlo utilizando la reflexión, pero requiere mucho código y mucho manejo de errores.
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), 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();
}
}
Desafortunadamente, Java no permite lo que quieres hacer. Consulte 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 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);
Desde el 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);
En Java 8 puede usar la interfaz funcional del 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();
}
}
Construirías esta clase así:
SomeContainer<String> stringContainer = new SomeContainer<>(String::new);
La sintaxis String::new
en esa línea es una referencia del constructor .
Si su constructor toma argumentos, puede usar una expresión lambda en su lugar:
SomeContainer<BigInteger> bigIntegerContainer
= new SomeContainer<>(() -> new BigInteger(1));
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.
Existen varias bibliotecas que pueden resolver E
por ti 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, de lo contrario, fallará, ya que el valor parametrizado real de E se habrá borrado en tiempo de ejecución si no se captura en una subclase.
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();
}
}
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.
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.
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
}
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);
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 está muy bien documentado, ¡este truco no lo es!
Aquí está el JavaDoc para TypeToken .
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 );
}
}
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.
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 principio DRY porque el compilador garantizará la corrección del tipo.
- No es del todo prueba de
Foo<L>
. 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).
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();
}
}
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++;
}
}
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
Un código de línea
model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
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());
}
}
return (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();