Подтвердить что ты не робот

Использование Java Generics с Enums

Обновление: Спасибо всем, кто помог - ответ на этот вопрос лежал в том, что я не замечал в своем более сложном коде, и о том, что я не знал о ковариантных возвращаемых типах Java5,

Оригинальное сообщение:

Сегодня утром я что-то общался. Хотя я знаю, что я мог бы решить эту проблему по-разному, я нахожу себя одержимым, выясняя, почему он не работает так, как я ожидал. Проведя некоторое время, читая об этом, я нахожу, что я не ближе к пониманию, поэтому я предлагаю его в качестве вопроса, чтобы увидеть, насколько я просто глуп, или если я действительно не понимаю, что происходит здесь.

Я создал пользовательскую иерархию событий следующим образом:

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

С конкретной реализацией так:

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

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

И затем я создаю такое событие:

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

Где мой fireEvent определяется как:

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

Итак, я думал, что это будет довольно просто, но оказывается, что вызов event.getId() приводит к тому, что компилятор говорит мне, что я не могу включить Enums, только конвертируемые значения int или константы enum.

В MyEvent можно добавить следующий метод:

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

Как только я это сделаю, все работает точно так, как я ожидал. Я не просто заинтересован в поиске обходного пути для этого (потому что у меня, очевидно, есть), я интересуюсь любым прозрением, которое люди могут иметь, чтобы ПОЧЕМУ это не работает, поскольку я ожидал, что он спустится с места в карьер.

4b9b3361

Ответ 1

Ишай прав, а волшебная фраза " ковариантные типы возвращаемого значения", которая является новой по сравнению с Java 5.0 - вы не можете переключать на Enum, но вы можете включить класс Type, который расширяет Enum. Методы в AbstractEvent, которые наследуются MyEvent, подвержены стиранию типа. Переопределяя это, вы перенаправляете результат getId() к вашему типу класса таким образом, что Java может обрабатывать во время выполнения.

Ответ 2

Это не связано с дженериками. Оператор switch для enum в java может использовать только значения этого конкретного перечисления, поэтому ему запрещено указывать имя перечисления. Это должно работать:

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

Обновить: Хорошо, вот реальный код (который мне пришлось немного изменить, чтобы его компилировать без каких-либо зависимостей), который я копировал/вставлял, компилировал и запускал - никаких ошибок:

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

Это компилируется и работает под Java 1.5 просто отлично. Что мне здесь не хватает?

Ответ 3

Очень коротко, потому что T стирается до класса Enum, а не константы Enum, поэтому скомпилированный оператор выглядит так, как будто вы включаете результат getID, являющийся Enum, как в этой сигнатуре:

Enum getId();

Когда вы переопределяете его определенным типом, вы меняете возвращаемое значение и можете его включить.

EDIT: Сопротивление вызвало у меня любопытство, поэтому я взломал код:

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

В результате получается java.lang.Enum, а не Num. T стирается до Enum во время компиляции, поэтому вы не можете включить его.

EDIT:

Я тестировал образцы кода, с которыми все работают, и они работают для меня, поэтому, хотя я подозреваю, что это лежит в основе проблемы в более сложном реальном коде, образец, который был отправлен, действительно компилируется и работает нормально.

Ответ 4

Работает для меня.

Полная тестовая программа вырезания и вставки:

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

Вы используете какой-то особенный компилятор?

Ответ 5

Я просто попробовал это (скопировал ваш код), и я не могу дублировать ошибку компилятора.

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

и

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

    public enum Type
    {
        SELECTED, SELECTION_CLEARED
    };

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


}

и

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