java泛型方法声明的两种方式

时间:2016-06-01 12:32:52

标签: java generics

虽然类似问题有thisthis;那些不回答两种通用声明方式之间的区别是什么?为什么其中一种方法会编译而其他方式不在以下示例中? 不编译的那个基于official tutorial和我一样。

public void processMap1(Map<String, List<?>> map) {
}

public <T> void processMap2(Map<String, List<T>> map) {
}

public void f() {
    Map<String, List<String>> map = new HashMap<>();

    processMap1(map); // ERROR
    processMap2(map); // OK
}

我希望我的方法同时适用于Map<String, List<String>>Map<String, List<Object>>

1 个答案:

答案 0 :(得分:3)

您有一个方法需要X类型的对象。为了将对象传递给此方法,此对象的类型Y必须是X子类型

对于简单的情况,这可能是显而易见的:

void processNumber(Number number) { ... }
void callIt()
{
    Integer integer = null;
    processNumber(integer); // Fine. Integer is a subtype of Number

    String string = null;
    processNumber(string); // Error. String is NOT a subtype of Number
}

到目前为止,这么好。但是现在你进入参数化类型的混淆的子类型关系世界。 Angelika Langer在她的泛型常见问题解答中详细介绍了Which super-subtype relationships exist among instantiations of generic types?

  

这相当复杂,类型关系最好根据此FAQ条目中的说明设置表格。

所以我甚至不想尝试在这里重现这个,但只是试图解释(简化)为什么第二个版本有效,但第一个版本没有。

第二个版本工作的事实可以用编译器执行的类型推断来解释。基本上,它只是查看参数,看到它是Map<String, List<String>>,并推断方法声明的T必须为String才能使其生效。你可以想象这只是TString替换,然后方法完全匹配。 (它不是那么容易,但可以直观地理解为这样)

为什么第一个版本工作的原因看似简单。回到初始语句:该方法需要一个对象具有方法签名中声明的类型的子类型。关键是:

Map<String, List<String>> Map<String, List<?>>

的子类型

这个事实(在这种情况下)甚至可以通过省略方法调用来简化:

Map<String, List<String>> map = new HashMap<>();
Map<String, List<?>> otherMap = map; // Does not work

再次,我将留下&#34;类型理论的细节&#34;其后面是上面链接的FAQ条目,但至少解释了为什么不允许这样做:Map<String, List<?>>是一个将字符串映射到包含未知/未指定类型的列表的映射。因此,processMap1方法的以下实现将是有效的:

public void processMap1(Map<String, List<?>> map) 
{
    List<Number> numbers = new ArrayList<Number>();
    map.put("x", numbers);
}

想象一下,用Map<String, List<String>>调用此方法是有效的:

public void f() {
    Map<String, List<String>> map = new HashMap<>();
    processMap1(map); // Imagine this was possible
    List<String> shouldBeStrings = map.get("x");
}

然后您迟早会得到ClassCastException,因为您将Map<String, List<String>>传递给该方法,但该方法可以将List<Number>放入此地图中。

或简称:不允许这样做,因为它不是类型安全的。

编辑以回应评论(这有点超出原始问题,所以我会尽量保持简短):

对于第二种情况,没有T来捕获任何类型。类型为?,直观地说,它可以代表任何类型。对于从reverserev建议的列表调用,T仍为?,但可用于专门代表类型的列表中的元素。或者再次提到子类型关系:

List<T> 总是 List<?>的子类型(无论T是什么)

processMap1processMap2的建议来电不会出于上述原因:即使Y是[{1}}的子类型,类型{{1}也是如此} X的子类型。在这里,this question可能是相关的。