Использование 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();
}
Как только я это сделаю, все работает точно так, как я ожидал. Я не просто заинтересован в поиске обходного пути для этого (потому что у меня, очевидно, есть), я интересуюсь любым прозрением, которое люди могут иметь, чтобы ПОЧЕМУ это не работает, поскольку я ожидал, что он спустится с места в карьер.
Ответы
Ответ 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;
}
}
}