【问题标题】:Using Java Generics with Enums将 Java 泛型与枚举一起使用
【发布时间】:2009-07-23 17:31:03
【问题描述】:

更新:感谢所有提供帮助的人 - 这个问题的答案在于我在更复杂的代码中没有注意到什么,以及我对 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 值或枚举常量。

可以在 MyEvent 中添加以下方法:

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

一旦我这样做了,一切都会完全按照我的预期进行。我不仅对找到解决方法感兴趣(因为我显然有一个),我对人们可能对为什么这不起作用的任何见解感兴趣,因为我期望它立即生效。

【问题讨论】:

  • 克里斯,你能显示包含fireEvent的类的类声明吗?
  • 问题真的是我的 AbstractEvent 比我在这里发布的要复杂得多。我应该创建玩具实现并在发布之前对其进行测试!感谢所有提供帮助的人!答案是我的 getId() 方法是“协变返回类型” - 我在定义为 Enum 返回类型的类中隐藏了另一个实现。当我放弃它时,switch 语句开始编译。
  • -0 至少要干净...

标签: java generics enums switch-statement


【解决方案1】:

Yishai 是对的,神奇的短语是“covariant return types”,它是 Java 5.0 的新功能——你不能打开 Enum,但你可以打开扩展 Enum 的 Type 类。 AbstractEvent 中被 MyEvent 继承的方法会被类型擦除。通过覆盖它,您将getId() 的结果以Java 可以在运行时处理的方式重定向到您的Type 类。

【讨论】:

  • 这太棒了——我错过了Java5的这个特性,完全感谢你的指点!
  • 返回并梳理代码后,我没有注意到我在抽象类中定义了第二个 getId() 方法(显然它比我发布的示例更复杂这里)。正是第二个 getId() 返回 Enum 被 switch 语句调用并搞砸了!
  • 链接好像失效了。
【解决方案2】:

这与泛型无关。 Java 中枚举的 switch 语句只能使用该特定枚举的值,因此 禁止 实际指定枚举名称。这应该有效:

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 下编译和运行。我在这里错过了什么?

【讨论】:

  • 不,恐怕这行不通。如果是这种情况,那么它将在 switch 语句中抱怨我的情况:行而不是我的 event.getId() 。此外,如果我在我的具体类中重写 getId() 并且只返回超级的 getId() ,那么它一切正常。
  • 抱歉:我输入的示例有误。您当然对案例标签的资格是正确的,但是让我感到困惑的真正问题是为什么 event.getId() 不是有效的可切换参数。
  • 你的代码肯定对我有用 - 没有覆盖。现在,如果 fireEvent()AbstractEvent 作为参数类型声明,那将是另一回事。
  • 不,fireEvent() 使用具体类。
  • 我不知道...我使用的是JDK 1.6,但我不认为应该有区别...
【解决方案3】:

适合我。

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

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

你在使用一些特殊的编译器吗?

【讨论】:

  • 我正在通过 Eclipse 使用 Java 6 编译器 (r14)
【解决方案4】:

简而言之,因为 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());
}

结果是 java.lang.Enum,而不是 Num。 T 在编译时被擦除为 Enum,所以你不能打开它。

编辑:

我测试了每个人都在使用的代码示例,它们也适用于我,所以虽然我怀疑这是更复杂的实际代码中问题的基础,但发布的示例实际上编译并运行良好。

【讨论】:

  • 这只有在我们有对AbstractEvent的原始引用时才有意义。
  • 这就是我的想法——自动装箱由于某种原因不起作用——但为什么是重要的问题。
  • 不,fireEvent 仅适用于具体的 MyEvent 类。没有人在处理 AbstractEvent - 这对我来说只是一种方便。
  • @Yishai,确实类型在运行时被擦除,getName() 会返回 Enum,但在编译时,编译器可以访问参数化类型。
  • @Yishai - 啊,但你已经测试了整个不同的问题。 Chris 的代码显式将 MyEvent.Type 实例传递给他的 MyEvent 构造函数,所以他的开关工作得很好。
【解决方案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;
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-02-21
    • 1970-01-01
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多