将Java泛型与枚举一起使用

将Java泛型与枚举一起使用,java,generics,enums,switch-statement,Java,Generics,Enums,Switch Statement,更新:感谢所有帮助过我的人——这个问题的答案在于我在更复杂的代码中没有注意到什么,以及我对Java5协变返回类型不知道什么 原创帖子: 今天早上我一直在胡闹。虽然我知道我可以用不同的方式解决整个问题,但我发现自己一直在想为什么它没有按照我预期的方式工作。在花了一些时间阅读这篇文章之后,我发现我还没有接近理解,所以我把它作为一个问题提出来,看看我是否只是在做傻事,或者是否真的有什么我不理解的事情发生在这里 我创建了一个自定义事件层次结构,如下所示: public abstract class Ab

更新:感谢所有帮助过我的人——这个问题的答案在于我在更复杂的代码中没有注意到什么,以及我对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;
    }
}
其中,我的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()会导致编译器告诉我无法打开枚举,只能打开可转换的int值或枚举常量

可以将以下方法添加到MyEvent:

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

一旦我这么做了,一切都会按照我的预期进行。我不仅对找到一个解决方法感兴趣(因为我显然有一个),我还对人们可能有什么见解感兴趣,为什么这不能像我一开始就预期的那样起作用。

这与泛型无关。java中enum的Switch语句只能使用该特定enum的值,因此禁止实际指定enum名称。这应该起作用:

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

这可以在Java1.5下编译和运行。我在这里遗漏了什么?

简而言之,因为T被擦除为一个Enum类,而不是一个Enum常量,所以编译后的语句看起来像是打开了getID为Enum的结果,如以下签名所示:

Enum getId();
当您使用特定类型覆盖它时,您正在更改返回值并可以打开它

编辑:阻力让我好奇,所以我突然想出了一些代码:

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());
}
public enum Num{
一,,
两个
}
公共抽象类抽象{
公共摘要T getId();
}
公共抽象类Real扩展了抽象{
}
公共静态void main(字符串[]args)引发异常{
方法m=Real.class.getMethod(“getId”);
System.out.println(m.getReturnType().getName());
}
结果是java.lang.Enum,而不是Num.T在编译时被擦除为Enum,因此无法打开它

编辑:


我测试了每个人都在使用的代码示例,它们也适用于我,因此尽管我怀疑这是更复杂的实际代码中的问题的根源,但发布的示例实际上编译并运行良好。

Yishai是正确的,神奇的短语是“”,这是Java 5.0中新增的--您不能打开Enum,但您可以打开扩展枚举的类型类。MyEvent继承的AbstractEvent中的方法会被类型擦除。通过重写它,您将
getId()
的结果重定向到您的类型类,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;
    }
}
对我有用

完整的剪切和粘贴测试程序:

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;
        }
    }
}
enum MyEnum{
A、 B
}
类摘要{
私人期末考试;
公开摘要(E){
这个。e=e;
}
公共E get(){
返回e;
}
}
类派生的抽象扩展{
公共派生(){
超级(髓鞘A);
}
公共静态int-sw(派生){
开关(派生的.get()){
案例A:返回1;
默认值:返回342;
}
}
}

您使用的是某种特殊的编译器吗?

只有当我们对
AbstractEvent
有一个原始引用时,这才有意义。这就是我所想的——自动装箱由于某种原因不起作用——但为什么是重要的问题。不,fireEvent只适用于具体的MyEvent类。没有人处理AbstractEvent-这对我来说只是一种方便。@Yishai,类型确实在运行时被删除,并且
getName()
将返回Enum,但是在编译时,编译器可以访问参数化类型。@Yishai-啊,但是您已经测试了整个不同的问题。Chris的代码显式地将MyEvent.Type实例传递给他的MyEvent构造函数,所以他的开关工作正常。不,恐怕不行。如果是这样的话,那么它将抱怨我的case:line,而不是switch语句中的event.getId()。此外,如果我在我的具体类中重写getId(),只返回super的getId(),那么一切都会正常工作。关于大小写标签的限定,您当然是正确的,但是真正让我困惑的问题是,为什么event.getId()不是一个有效的可切换参数。对于您的代码,它最适合我-没有覆盖。现在,如果
firevent()
是以
AbstractEvent
作为参数类型声明的,那么情况就不同了。不,firevent()使用的是具体的类。我不知道。。。我使用的是JDK1.6,但我不认为会有什么不同……这太棒了——我错过了Java5的这个特性,完全是因为指针!回顾并梳理代码之后,我没有注意到我在抽象类中定义了第二个getId()方法(显然它比我在这里发布的示例更复杂)。正是这第二个getId()返回的枚举被switch语句调用,把我搞砸了!链接似乎已失效。我正在通过EclipseChris使用Java 6编译器(r14),您能显示包含
fireEvent
的类的类声明吗?问题是我的AbstractEvent太多了
Enum getId();
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());
}
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;
        }
    }
}
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;
        }
    }
}