2012-09-22 20 views
12

tengo una clase que representa un par de dos valores del mismo tipo (tipo que puede ser cualquiera de un conjunto específico de tipos):Limitaciones de Java Generics o uso incorrecto?

public class Pair<E extends AClass>{ 
     private E var1; 
     private E var2; 
    } 

Esta clase se utiliza por un marco, por lo que necesita un no constructor -argumento en el que tengo de ejecutar el 2 variables (Q1, var2):

public class Pair<E extends AClass>{ 
     private E var1; 
     private E var2; 

     public Pair(){ 
      var1 = invoke constructor of type E; 
      var2 = invoke constructor of type E 
     } 
    } 

obviamente, hay una serie de problemas aquí:

  1. en Para instanciar las variables, de alguna manera debería saber su tipo exacto e invocar el constructor de ese tipo específico; en el mejor de los casos esto significa tener una muy grande si else en el constructor, algo así como:

    public Pair(){ 
         if(var1 instanceof SpecificType1){ 
          var1 = new SpecificType1(); 
          var2 = new SpecificType2(); 
         } 
        } 
    
  2. Incluso si hago que el anterior, voy a tener algunos problemas debido a var1 se declara de tipo E y yo obtendrá un error de desajuste de tipo al intentar crear una instancia de SpecficType1 y asignar el objeto resultante a var1/var2. Con el fin de hacer que funcione, tengo que echar a E:

    var1 = (E)new SpecificType1(); 
    

Pero esto destruye el tipo de tiempo de compilación como yo estoy tratando de lanzar un tipo específico de un tipo genérico.

¿Es esta una limitación de los genéricos en java o es este escenario malo para usar genéricos?

+1

Si tiene que usar 'instanceOf' dentro de un contenedor genérico, entonces tiene un problema de diseño. –

+0

Puedes probar [Class.forName()] (http://docs.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#forName%28java.lang.String%29) –

+0

Notificación al asker (ya que las ediciones no notifican al asker): Agregué un ejemplo de código simple sobre cómo usar el patrón de diseño de fábrica en su caso – amit

Respuesta

6

Para crear una instancia de las variables, de alguna manera debería saber su tipo exacto e invocar el constructor de ese tipo específico; en el mejor de los casos, esto significa tener una declaración bastante grande si no más en el constructor, algo así como:

Te encontrarás con problemas antes de eso.

if(var1 instanceof SpecificType1){ 
     var1 = new SpecificType1(); 
     var2 = new SpecificType2(); 
    } 

var1 es null en este punto, por lo que es var1 instanceof Tfalse para todos T.


Una limitación de genéricos de Java es que los parámetros de tipo genérico son erased por lo que no hay manera de que se puede reflexionar sobre el parámetro de tipo de un constructor sin argumentos.

La persona que llama tiene que proporcionar algún contexto para indicarle cómo inicializar var1 y var2, y la forma típica de proporcionar ese contexto es mediante argumentos de constructor.


Su mejor opción es probablemente para dejar var1 y var2 Comience null y luego retrasar la inicialización hasta el momento en que se puede obtener el contexto que necesita.

Quizás

void init(Class<E> type) { 
    if (type.isAssignableFrom(ConcreteType1.class)) { 
    var1 = type.cast(new ConcreteType1(...)); 
    var2 = type.cast(new ConcreteType1(...)); 
    } else { /* other branches */ } 
} 

Esto no es perfecto debido a que aún no se puede distinguir de E extends List<String>E extends List<Number> pero puede ser lo suficientemente bueno para su caso, y el método .cast le dará un molde de tipo seguro de E.


Alternativamente, guayaba, Guice y bibliotecas relacionadas proporcionan cosas como la interfaz Supplier<E> que puede ser útil en un método init.

+0

Sí, esta también fue mi intuición. Entonces, ¡las limitaciones son aún más serias! – Razvan

+0

Acepto la edición también. Tengo otro constructor que toma 2 variables E como parámetros, pero el framework quiere un constructor no-arg – Razvan

+0

Se suponía que el framework llamaría a init y ciertamente no lo haría. – Razvan

2

No se puede crear una instancia de un tipo genérico: ¿qué pasará si, por ejemplo, el tipo genérico es SomeAbstractClass? ¿Qué será instanciado? (Esta no es la razón, es simplemente la intuición)

Sin embargo, puede utilizar java reflection API a una instancia del objeto - pero se necesita la clase de objeto específica para ello.

Una alternativa más elegante es usar abstract factory design pattern, y pasar un objeto de fábrica a su pareja, y usarlo para construir el objeto necesario.


Ejemplo de código:

public class Pair<S> { 
    public final S var1; 
    public final S var2; 
    public Pair(Factory<S> builder) { 
     var1 = builder.build(); 
     var2 = builder.build(); 

    } 
} 

public interface Factory<S> { 
    public S build(); 
} 

public class IntegerBuilder implements Factory<Integer> { 
    private int element = 5; 
    public Integer build() { 
     return new Integer(element++); 
    } 
} 
+0

Algun código de muestra siempre es bienvenido. – OldCurmudgeon

+0

@ OldCurmudgeon: Acabo de hacerlo, ¿es comprensible? – amit

+0

Excelente. Y ahora para el método 'Clase ', solo para completarlo. – OldCurmudgeon

1

Si fuese un marco para crear instancias de ella, lo haría como un tipo de crudo, algo equivalente a new Pair() sin parámetros de tipo.

supongo que tienes para crear simples clases de una sola línea como:

class SpecificType1Pair extends Pair<SpecificType1> {} 

y pasarlos al marco en su lugar. Puede obtener el parámetro de tipo real como getClass().getGenericSuperclass()).getActualTypeArguments()[0]. Su par de clases se vería así:

public abstract class Pair<E extends AClass> { 
    private E var1; 
    private E var2; 

    public Pair() { 
     ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass(); 
     @SuppressWarnings("unchecked") 
     Class<E> clazz = (Class<E>) superclass.getActualTypeArguments()[0]; 
     try { 
      var1 = clazz.newInstance(); 
      var2 = clazz.newInstance(); 
     } catch (InstantiationException e) { 
      handle(e); 
     } catch (IllegalAccessException e) { 
      handle(e); 
     } 
    } 
} 
+1

Un '@SuppressWarnings (" unchecked ")' podría estar en orden en la línea 'Class clazz = (Class ) ...;'. –