Вопрос по java, generics, switch-statement, enums – Использование Java Generics с Enums

18

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

Original Post:

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

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

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

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

Крис, вы можете показать объявление класса, содержащееfireEvent? notnoop
-0 по крайней мере, чтобы очиститься ... Tom Hawtin - tackline
Проблема была в том, что мой AbstractEvent намного сложнее, чем я здесь разместил яshould создали игрушку и протестировали ееbefore размещение! Спасибо всем, кто выручил! Тогда ответ состоял в том, что мой метод getId () был «ковариантным типом возврата»; - В классе была похоронена другая реализация, которая была определена как Enum & lt; T & gt; тип возврата. Когда я отказался от этого, оператор switch начал компилироваться. Chris Boran

Ваш Ответ

5   ответов
11

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

Это здорово - я пропустил эту особенность Java5 полностью спасибо за указатель! Chris Boran
Вернувшись назад и прочесав код, я не заметил, что я определил второй метод getId () в моем абстрактном классе (очевидно, он был более сложным, чем пример, который я разместил здесь). Именно этот второй getId () возвратил Enum & lt; T & gt; был вызван заявлением переключателя и портит меня! Chris Boran
Ссылка кажется мертвой.
1

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

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

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

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

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

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 просто отлично. Что мне здесь не хватает?

Нет, fireEvent () использует конкретный класс. Chris Boran
Я не знаю ... Я использую JDK 1.6, но я не думаю, что должна быть разница ... Chris Boran
Это наверняка работает для меня с вашим кодом - без переопределений. Сейчас еслиfireEvent() был объявлен сAbstractEvent как тип параметра, это была бы другая история.
Я прошу прощения: я набрал в примере неправильно. Вы, конечно, правы относительно квалификации меток, но реальная проблема, которая меня озадачивает, заключается в том, что event.getId () не является допустимым переключаемым параметром. Chris Boran
Нет, я боюсь, что это не работает. Если бы это было так, то было бы жаловаться на мой случай: линии, а не на мой event.getId () в операторе switch. Кроме того, если я переопределяю getId () в моем конкретном классе и возвращаю только супер-getId (), все это работает. Chris Boran
2

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

Enum getId();

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

EDIT: The resistance made me curious, so I whipped up some code:

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

The result is java.lang.Enum, not Num. T is erased to Enum at compile time, so you can't switch on it.

РЕДАКТИРОВАТЬ:

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

Это было то, о чем я думал - автобокс по какой-то причине не работает - но ПОЧЕМУ это важный вопрос. Chris Boran
@ Yishai - ах, но ты проверил совершенно другую проблему. Код Крисаexplicitly передает экземпляр MyEvent.Type своему конструктору MyEvent, поэтому его переключатель работает просто отлично.
Нет, fireEvent работает только с конкретным классом MyEvent. Никто не имеет дело с AbstractEvent - это просто удобство для меня. Chris Boran
@ Да, это правда, что типы стираются во время выполнения иgetName() вернет Enum, но во время компиляции у компилятора есть доступ к параметризованным типам.
Это будет иметь значение, только если у нас есть грубая ссылка наAbstractEvent.
2

Works for me.

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

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

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

Я использую компилятор Java 6 (r14) через Eclipse Chris Boran

Похожие вопросы