2009-07-23 23 views
17

Actualización: Gracias a todos los que ayudaron, la respuesta a esto radica en lo que no estaba notando en mi código más complejo y lo que no sabía sobre los tipos de retorno covariante Java5 .Uso de Java Generics con Enums

Post original:

he estado jugando con algo esta mañana. Si bien sé que I podría abordar este problema de manera diferente, estoy obsesionado con averiguar por qué no funciona de la manera que esperaba. Después de pasar un tiempo leyendo sobre esto, descubro que no estoy más cerca de la comprensión, así que lo ofrezco como una pregunta para ver si solo estoy siendo estúpido o si realmente hay algo que no entiendo que sucede aquí. .

He creado una jerarquía de eventos personalizados, así:

public abstract class AbstractEvent<S, T extends Enum<T>> 
{ 
    private S src; 
    private T id; 

    public AbstractEvent(S src, T id) 
    { 
     this.src = src; 
     this.id = id; 
    } 

    public S getSource() 
    { 
     return src; 
    } 

    public T getId() 
    { 
     return id; 
    } 
} 

Con una implementación concreta de este modo:

public class MyEvent 
extends AbstractEvent<String, MyEvent.Type> 
{ 
    public enum Type { SELECTED, SELECTION_CLEARED }; 

    public MyEvent(String src, Type t) 
    { 
     super(src, t); 
    } 
} 

Y entonces crear un evento de este modo:

fireEvent(new MyEvent("MyClass.myMethod", MyEvent.Type.SELECTED)); 

Donde mi fireEvent se define como:

protected void fireEvent(MyEvent event) 
{ 
    for(EventListener l : getListeners()) 
    { 
     switch(event.getId()) 
     { 
      case SELECTED: 
       l.selected(event); 
       break; 
      case SELECTION_CLEARED: 
       l.unselect(event); 
       break; 
     } 
    } 
} 

Así que pensé que esto sería bastante sencillo, pero resulta que la llamada a event.getId() hace que el compilador me diga que no puedo activar Enums, solo valores int convertibles o constantes enum.

Es posible añadir el siguiente método para MyEvent:

public Type getId() 
{ 
    return super.getId(); 
} 

Una vez que haga esto, todo funciona exactamente como se esperaba que. No estoy solo interesado en encontrar una solución para esto (porque obviamente tengo uno), estoy interesado en cualquier idea que las personas puedan tener de por qué esto no funciona, como esperaba de inmediato.

+0

Chris, ¿puedes mostrar la declaración de clase de clase que contiene 'fireEvent'? – notnoop

+0

El problema fue que mi AbstractEvent es mucho más complicado que lo que publiqué aquí. ¡Yo * debería * haber creado la implementación del juguete y haberlo probado ** antes ** de publicar! ¡Gracias a todos los que ayudaron! La respuesta fue que mi método getId() era un 'tipo de retorno covariante' - tuve otra implementación enterrada en la clase que se definió como Enum tipo de devolución. Cuando abandoné eso, la instrucción switch comenzó a compilar. –

+0

-0 para al menos estar limpio ... –

Respuesta

10

Yishai tiene razón, y la frase mágica es "covariant return types" que es nueva a partir de Java 5.0 - no puede encender Enum, pero puede activar su clase Type que se extiende a Enum. Los métodos en AbstractEvent heredados por MyEvent están sujetos a borrado de tipo. Al anularlo, está redireccionando el resultado de getId() hacia su clase de Tipo de una manera que Java puede manejar en tiempo de ejecución.

+0

Esto es genial - ¡Extrañé esta característica de Java5 enteramente gracias por el puntero! –

+0

Después de volver atrás y analizar el código, no me había dado cuenta de que había definido un segundo método getId() en mi clase abstracta (obviamente era más complejo que el ejemplo que había publicado aquí). Fue este segundo getId() el que devolvió un Enum que recibía la llamada de la instrucción switch y me fastidiaba. –

+0

El enlace parece estar muerto. – FDinoff

8

Esto no está relacionado con los genéricos. La instrucción Switch para enum en java solo puede usar los valores de esa enumeración particular, por lo tanto, está prohibido para especificar realmente el nombre enum. Esto debería funcionar:

switch(event.getId()) { 
    case SELECTED: 
     l.selected(event); 
     break; 
    case SELECTION_CLEARED: 
     l.unselect(event); 
     break; 
} 

actualización: Ok, aquí hay un código real (que tuve que cambiar un poco para que se compile sin dependencias) que he copiar/pegar, compilado y corrí - no hay errores:

AbstractEvent.java

public abstract class AbstractEvent<S, T extends Enum<T>> { 
    private S src; 
    private T id; 

    public AbstractEvent(S src, T id) { 
     this.src = src; 
     this.id = id; 
    } 

    public S getSource() { 
     return src; 
    } 

    public T getId() { 
     return id; 
    } 
} 

MyEvent.java

public class MyEvent extends AbstractEvent<String, MyEvent.Type> { 
    public enum Type { SELECTED, SELECTION_CLEARED }; 

    public MyEvent(String src, Type t) { 
     super(src, t); 
    } 
} 

Test.java

public class Test { 
    public static void main(String[] args) { 
     fireEvent(new MyEvent("MyClass.myMethod", MyEvent.Type.SELECTED)); 
    } 

    private static void fireEvent(MyEvent event) { 
     switch(event.getId()) { 
      case SELECTED: 
       System.out.println("SELECTED"); 
       break; 
      case SELECTION_CLEARED: 
       System.out.println("UNSELECTED"); 
       break; 
     } 
    } 
} 

Esto compila y se ejecuta con Java 1.5 muy bien. ¿Que me estoy perdiendo aqui?

+0

No, me temo que eso no funciona. Si ese fuera el caso, entonces se estaría quejando sobre mi caso: lines y no my event.getId() en una declaración de cambio. Además, si anulo getId() en mi clase concreta y solo devuelvo getId() de super, todo funciona. –

+0

Me disculpo: escribí mal el ejemplo. Por supuesto, tiene razón acerca de la calificación de las etiquetas de los casos, sin embargo, el problema real que me deja perplejo es por qué event.getId() no es un parámetro válido que se puede cambiar. –

+0

Sin duda me funciona con tu código, sin anulaciones. Ahora si 'fireEvent()' se declaró con 'AbstractEvent' como tipo de parámetro, eso hubiera sido una historia diferente. – ChssPly76

2

En muy corto, ya que T se borra a una clase de enumeración, no una enumeración constante, por lo que la sentencia compilada parece que está cambiando en el resultado de getID siendo Enum, como en esta firma:

Enum getId(); 

Cuando lo reemplaza con un tipo específico, está cambiando el valor de retorno y puede activarlo.

EDIT: La resistencia me causó curiosidad, así que nos prepararon rápidamente algo de código:

public enum Num { 
    ONE, 
    TWO 
} 
public abstract class Abstract<T extends Enum<T>> { 
    public abstract T getId(); 
} 

public abstract class Real extends Abstract<Num> { 

} 

public static void main(String[] args) throws Exception { 
    Method m = Real.class.getMethod("getId"); 
    System.out.println(m.getReturnType().getName()); 
} 

El resultado es java.lang.Enum, no Num. T se borra a Enum en el momento de la compilación, por lo que no puede encenderlo.

EDIT:

Probé los ejemplos de código todo el mundo está trabajando, y trabajan para mí, así que aunque sospecho que esto subyace la cuestión en el código real más compleja, la muestra tal como fue anunciado en realidad compila y funciona bien.

+0

Esto solo importaría si tenemos una referencia en bruto a 'AbstractEvent'. – notnoop

+0

Eso era lo que estaba pensando, que el auto boxeo no funciona por alguna razón, pero ¿POR QUÉ es la pregunta importante? –

+0

No, fireEvent solo funciona con la clase concreta MyEvent. Nadie está lidiando con AbstractEvent, es solo una conveniencia para mí. –

1

Acabo de probar esto (copiar y pegar el código) y no puedo duplicar un error del compilador.

public abstract class AbstractEvent<S, T extends Enum<T>> 
{ 
    private S src; 
    private T id; 

    public AbstractEvent(S src, T id) 
    { 
     this.src = src; 
     this.id = id; 
    } 

    public S getSource() 
    { 
     return src; 
    } 

    public T getId() 
    { 
     return id; 
    } 
} 

y

public class MyEvent extends AbstractEvent<String, MyEvent.Type> 
{ 

    public enum Type 
    { 
     SELECTED, SELECTION_CLEARED 
    }; 

    public MyEvent(String src, Type t) 
    { 
     super(src, t); 
    } 


} 

y

public class TestMain 
{ 
    protected void fireEvent(MyEvent event) 
    { 
     switch (event.getId()) 
     { 
      case SELECTED: 
      break; 
      case SELECTION_CLEARED: 
      break; 
     } 
    } 
} 
2

funciona para mí.

completo programa de pruebas de corte y pegar:

enum MyEnum { 
    A, B 
} 
class Abstract<E extends Enum<E>> { 
    private final E e; 
    public Abstract(E e) { 
     this.e = e; 
    } 
    public E get() { 
     return e; 
    } 
} 
class Derived extends Abstract<MyEnum> { 
    public Derived() { 
     super(MyEnum.A); 
    } 
    public static int sw(Derived derived) { 
     switch (derived.get()) { 
      case A: return 1; 
      default: return 342; 
     } 
    } 
} 

¿Está utilizando algún compilador peculiar?

+0

Estoy usando un compilador Java 6 (r14) a través de Eclipse –