Использование дженериков Java с перечислениями

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

Исходный Пост:

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

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

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 as:

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

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

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

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

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

5 ответов


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


это не связано с генериками. Оператор Switch для перечисления в 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);
    }
}

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

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

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

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


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

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.ленг.Перечисление, не число. T стирается для перечисления во время компиляции, поэтому вы не можете включить его.

EDIT:

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


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

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