有没有一种方法可以在EnumMap中存储“可扩展枚举”?

3
我正在参考 Joshua Bloch 的《Effective Java》中的第34项范例。我想采用他使用的方法,即让每个相关的枚举实现一个基本接口,并从“子枚举”初始化 EnumMap。请参见下面的代码部分。我遇到了一个语法错误,但我不理解为什么会出错。我并没有确定要使用这种实现方法,但我想了解为什么它不能正常工作。
请注意,此示例假定每个类定义都在其自己的文件中。
public interface BaseEnum { 
    ... 
}

public enum EnumOps1 implements BaseEnum { 
    ... 
}

public class Widget {
    public Widget() {
         regMap = new EnumMap<EnumOps1, WidgetData>(EnumOps1.class);

         for (EnumOps1 op : EnumOps1.values()) {
             regMap.put(op, getWidgetData(op.key()));  // line with syntax error
         }
    }

    protected Map<? extends BaseEnum, WidgetData> regMap;
} 

语法错误细节:

接口 java.util.Map<K,V> 中的方法put不能应用于给定的类型
必需类型:? extends BaseEnum, WidgetData 的捕获#1
发现类型:EnumOps1, WidgetData

2个回答

6
这是一个关于通配符使用的问题。你应该将你的map声明为Map<BaseEnum, WidgetData>,并将EnumMap声明为例如HashMap<BaseEnum, WidgetData>
在SO上有很多关于这个问题的讨论,但可以参考PECS (Producer Extends Consumer Super)是什么
编辑:
不幸的是,你是对的 - 你不能在那里使用EnumMap。这是因为你正在尝试使用一个接口,并且EnumMap规定(因为它需要类型T extends Enum<T>)它必须只是一个枚举。
你的选择基本上归结为:
1)使用EnumMap<EnumOps1,...>,失去多态性。
2)使用HashMap<BaseEnum,...>,一切都正常,但你必须使用非枚举映射。
3)像你现在尝试的那样使用通配符,但你会遇到我之前链接的PECS限制,例如你可以添加或删除元素,但不能同时进行(super vs extends)。

当我尝试声明和实例化时,我遇到了另一个语法错误:类型参数BaseEnum不在其范围内。该错误出现在实例化行(regMap = ...)上。有什么想法吗? - stever
我同意你的回答是正确的。然而,为什么那一行代码会抛出错误并不完全清楚。在所有实例化或调用EnumMap的情况下,都会传递一个枚举值。如果我漏掉了一些显而易见的东西,请原谅。 - stever
代码与原帖中的相同。我想更好地理解当我像我这样实例化EnumMap时将枚举传递给EnumMap.put方法有什么问题。我知道你列出的其他选择也可以工作。 - stever
问题在于你的 Map 键的类型是 ? extends BaseEnum,这意味着 regMap 变量的键是 BaseEnum某个未指定的子类。向其中添加元素是 不安全的,因为根据静态类型(即 某个未指定的子类型),你无法知道必须添加到集合中的具体子类型。如果你弄错了,就会违反类型安全性,编译器无法验证,因此禁止这样做。这有意义吗? - Steven Schlansker
是的,这很有帮助。EnumOps1与BaseEnum不同,因为泛型不是协变的,所以编译器会报错。让我困惑的是书中的例子。这些例子是正确且好的,但类型总是在编译时静态/已知的。只是以巧妙的方式完成。感谢您的努力! - stever

3

经过数小时的研究和尝试后,我做了一些事情。我希望利用EnumMap的好处,并不仅限于一个枚举类,而是许多不同的实现相同接口的枚举类。 在下面的示例中,我有一个名为Layout的接口,我有几个枚举类扩展它。然后我创建了一个帮助类Record,它利用EnumMap来处理和存储将一些String值映射到我的枚举类中的枚举,并且我需要它接受不同的实现Layout接口的枚举类。因此,我创建了以下类(以下内容已缩短以提供思路):

public class Record<T extends Enum<T> & Layout>
{
    private Map<T, String> fields;

    /**Constructor accepting the class name of one of the enums implementing layout
    */
    public Record(Class<T> layout_type)
    {
        fields = new EnumMap<T, String>(layout_type);
        for (T type : layout_type.getEnumConstants())
        {
            fields.put(type, "");
        }
    }

    /*Here's an example of a method that manipulates (adds or replaces) an enum map  
    *item and its corresponding String value in the enum map
    *This won't warn any unchecked exceptions or anything and I can pass any
    *enum from my enum class that implements layout enum.NAME 
    */
    public void setRecordFieldValue(final T item, String value)
    {
        fields.put(item, value);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接