避免使用不同类型的集合的原始类型

时间:2014-01-29 15:27:44

标签: java generics raw-types

我有以下情况:

我使用了command模式,如下所示:

public interface Command<T extends EObject>
{
 void runCommand(T classToMap, String fieldForMapping);
}

正如有人可能会注意到的那样,我正在使用Eclipse Modelling Framework,但这对此问题无关紧要。

在另一个类中,我有一个HashMap作为mappingDirectory,该键返回另一个Map。

private final static HashMap<String, LinkedHashMap<String, Command>> mappingDirectory = new HashMap<String, LinkedHashMap<String, Command>>();

对于这个mappingDirectory,我添加了各种各样的地图:

private final static LinkedHashMap<String, Command> classOneMappings = new LinkedHashMap<String, Command>();
private final static LinkedHashMap<String, Command> classTwoMappings = new LinkedHashMap<String, Command>();

......等等。这些地图充满了键和提到的命令界面:

classOneMappings.put("someKey", new Command<ClassOne>()
  {
     @Override
     public void runCommand(ClassOne classToMap, String fieldForMapping)
     {
        classToMap.setName(fieldForMapping);
     };
  });

最后,我有许多实现了不同类型的命令接口。那么如何才能避免仅使用CommandmappingDirectory以及包含的地图的原始类型?

我认为可以这样工作:

HashMap<String, LinkedHashMap<String, Command<? extends EObject>>> mappingDirectory = new HashMap<String, LinkedHashMap<String, Command<? extends EObject>>>();

并声明其他地图的特定类型全部 EObjects

private final static LinkedHashMap<String, Command<ClassOne>> classOneMappings = new LinkedHashMap<String, Command<ClassOne>>();

但遗憾的是将它们放入mappingDirectory会导致编译错误。

也许我只是在忽视某些事情,所以任何帮助都会受到赞赏!

2 个答案:

答案 0 :(得分:2)

最外层级通配符与嵌套通配符之间的区别在于,嵌套通配符不会捕获。

考虑以下2个声明:

List<? extends Number> list = new ArrayList<Integer>();     // 1
List<List<? extends Number>> list2 = new ArrayList<List<Integer>>();   // 2

List<List<? extends Number>> list3 = new ArrayList<List<? extends Number>>();// 3

虽然第一个声明完全有效,但我们知道List<? extends Number>可以捕获ArrayList<Integer>,但第二个声明无效。 List<List<? extends Number>>,不会捕获ArrayList<List<Integer>>。你必须只使用第三个声明。

因此,如果您已声明Map,请将相同的逻辑应用于地图:

private static HashMap<String, LinkedHashMap<String, Command<? extends EObject>>> mappingDirectory = 
        new HashMap<String, LinkedHashMap<String, Command<? extends EObject>>>();

您只能添加Map这样的内容:

private static LinkedHashMap<String, Command<? extends EObject>> classOneMappings = 
        new LinkedHashMap<String, Command<? extends EObject>>();
private static LinkedHashMap<String, Command<? extends EObject>> classTwoMappings = 
        new LinkedHashMap<String, Command<? extends EObject>>();

答案 1 :(得分:1)

注1:泛型是不变的!网上有很多文章讨论这个话题。

注意2:您在数据结构的两个“层”中使用泛型。这有时会令人困惑。

您的问题如下:您正在使用匿名类声明创建Command类型的子类型。但是,您将目录的内部映射声明中的值类型限制为仅限Command。通过以下变体,您还可以使用子类型:

Map<String, Map<String, ? extends Command<?>>> mappingDirectory = ...

现在你可以(并且应该)声明

Map<String, Command<ClassOne>> classOneMappings = ...

并将其放入目录:

mappingDirectory.put("...", classOneMappings);

(请注意,我只使用了接口类型Map。)


编辑:

对于以下示例,我将使用类NumberIntegerDouble(所有包java.lang)。他们在这里做得很好。在实例化泛型时,我还将使用Java 7进行漂亮的类型推断。

我将从Command界面开始:

interface Command<T extends Number> {
    void run(T number, String field);
}

通过它,您可以执行以下操作:

class NestedGenerics {
    private static final Map<String, Map<String, ? extends Command>> DIRECTORY = new HashMap<>();

    public static void main(String[] args) {
        Map<String, Command<Integer>> integerMap = new HashMap<>();
        Map<String, Command<Double>> doubleMap = new HashMap<>();

        integerMap.put("integer_command", new Command<Integer>() {
            @Override
            public void run(Integer number, String field) {
                System.out.println(field + ": " + number);
            }
        });
        doubleMap.put("double_command", new Command<Double>() {
            @Override
            public void run(Double number, String field) {
                System.out.println(field + ": " + number);
            }
        });

        DIRECTORY.put("integers", integerMap);
        DIRECTORY.put("doubles", doubleMap);

        DIRECTORY.get("integers").get("integer_command").run(Integer.valueOf(42), "integer field");
        DIRECTORY.get("doubles").get("double_command").run(Double.valueOf(42.0), "double field");
    }
}

这里的一个小缺点是,您在目录的声明中使用Command类型作为原始类型。但是当您将各种Command子类型的各种地图放入此目录时,绑定到Command的{​​{1}}类(提醒:Number)可能足够用于此目的。

这个例子编译并运行得很好。它的输出是:

  

整数字段:42

     

双场:42.0