Verwenden von Java Generics mit Enums

Update: Vielen Dank für alle, die mitgeholfen haben - die Antwort darauf lag in dem, was ich in meinem komplexeren Code nicht bemerkte und was ich nicht über die Java5-Kovarianten-Rückgabetypen wusste.

Original Post:

Ich habe heute Morgen mit etwas herumgespielt. Obwohl ich weiß, dass ich dieses ganze Problem anders angehen könnte, bin ich besessen davon, herauszufinden, warum es nicht so funktioniert, wie ich es erwartet hatte. Nachdem ich einige Zeit damit verbracht habe, darüber herumzulesen, finde ich, dass ich nicht näher am Verständnis bin, also biete ich es als eine Frage an, um zu sehen, ob ich nur dumm bin oder ob es wirklich etwas gibt, das ich hier nicht verstehe.

Ich habe eine benutzerdefinierte Ereignishierarchie wie folgt erstellt:

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;
    }
}

Mit einer konkreten Implementierung wie:

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

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

Und dann erstelle ich ein Ereignis wie folgt:

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

Wo mein fireEvent definiert ist als:

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;
         }
    }
}

Also dachte ich, dass dies ziemlich einfach wäre, aber es stellt sich heraus, dass der Aufruf zu event.getId() dazu führt, dass der Compiler mir sagt, dass ich nicht auf Enums wechseln kann. , nur konvertierbare Int-Werte oder Enumeratkonstanten.

Es ist möglich, die folgende Methode zu MyEvent hinzuzufügen:

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

Sobald ich dies tue, funktioniert alles genau so, wie ich es erwartet habe. Ich bin nicht nur daran interessiert, eine Problemumgehung dafür zu finden (weil ich offensichtlich eine habe), ich interessiere mich für jede Einsicht, die Leute haben könnten, warum dies nicht funktioniert, wie ich es erwartet hatte, um direkt von der Fledermaus.

Antwort auf "Verwenden von Java Generics mit Enums " 5 von antworten

Dies bezieht sich nicht auf Generika. Die Switch-Anweisung für enum in java kann nur Werte dieser bestimmten Enumerat verwenden, daher ist es verboten , tatsächlich den Enumerumnamen anzugeben. Dies sollte funktionieren:

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

Update : Ok, hier ist ein tatsächlicher Code (den ich ein wenig ändern musste, um ihn ohne Abhängigkeiten kompilieren zu lassen), den ich kopiert /eingefügt, kompiliert und ausgeführt habe - keine Fehler:

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;
         }
    }
}

Dies kompiliert und läuft unter Java 1.5 einfach in Ordnung. Was vermisse ich hier?

Kurz gesagt, da T in eine Enum-Klasse und nicht in eine Enum-Konstante ausgegart ist, so dass die kompilierte Anweisung so aussieht, als würden Sie das Ergebnis der getID-Enum einschalten, wie in dieser Signatur:

Enum getId();

Wenn Sie es mit einem bestimmten Typ überschreiben, ändern Sie den Rückgabewert und können ihn einschalten.

EDIT: Der Widerstand machte mich neugierig, also habe ich einen Code aufgepeitscht:

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

Das Ergebnis ist java.lang.Enum, nicht Num. T wird zur Kompilierungszeit in Enum ausgegeut, so dass man es nicht einschalten kann.

EDIT:

Ich habe die Codebeispiele getestet, mit denen jeder arbeitet, und sie arbeiten auch für mich, also obwohl ich vermute, dass dies dem Problem im komplexeren realen Code zugrunde liegt, wird das Sample, wie es gepostet wurde, tatsächlich kompiliert und läuft gut.

Yishai hat Recht, und die magische Phrase ist covariant return types" which is new as of Java 5.0 -- you can't switch on Enum, but you can switch on your Type class which extends Enum. The methods in AbstractEvent that are inherited by MyEvent are subject to type erasure. By overriding it, you're redirecting the result of getId() towards your Type class in a way that Java can handle at run-time. towards your Type class in a way that Java can handle at run-time. , was ab Java 5.0 neu ist -- Sie können Enum nicht einschalten, aber Sie können Ihre Type-Klasse einschalten, die Enum erweitert. Die Methoden in AbstractEvent, die von MyEvent geerbt werden, unterliegen der Typlöschung. Durch Überschreiben leiten Sie das Ergebnis von getId() towards your Type class in a way that Java can handle at run-time. auf eine Weise um, die Java zur Laufzeit verarbeiten kann.

Ich habe dies gerade ausprobiert (Kopierten Sie Ihren Code) und ich kann keinen Compilerfehler duplizieren.

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;
    }
}

und

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

    public enum Type
    {
        SELECTED, SELECTION_CLEARED
    };

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


}

und

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

funktioniert für mich.

Komplettes Ausgeschnitten-und-Einfügen-Testprogramm:

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;
        }
    }
}

Verwenden Sie einen eigenartigen Compiler?