目标类型具有通配符时的通用方法类型推断

时间:2015-01-10 15:58:38

标签: java generics type-inference

我理解编译器使用目标类型来确定使泛型方法调用适用的类型参数。例如,在以下声明中:

List<String> listOne = Collections.emptyList();

其中Collections.emptyList的签名

中有一个类型参数T
public static final <T> List<T> emptyList() {

在这种情况下,T的推断类型参数为String

现在考虑以下事项:

List<?> listTwo = Collections.emptyList();

在这种情况下,推断类型是什么?是Object吗?或者它并不重要,因为通配符告诉编译器任何类型都是可能的?

2 个答案:

答案 0 :(得分:4)

通配符的每种用法都有与之关联的不同类型。 (通常JLS将其称为“新类型”。)例如,这样的编译器错误如何工作:

List<?> list0 = ... ;
List<?> list1 = ... ;
list0.add(list1.get(0)); // error

因为编译器为list0list1提供了单独的类型,大部分都是

reference_type_of(List<?>) != reference_type_of(List<?>)

如果你尝试类似

,你可以开始看看它如何适用于类型推断
{
    List<?> list0 = ... ;
    List<?> list1 = ... ;
    test(list0, list1);
}
static <T> void test(List<T> list0, List<T> list1) {}

编译器发出的错误实际上告诉我们它为list0list1生成的类型。

error: method test in class Ideone cannot be applied to given types;
    test(list0, list1);
    ^
  required: List<T>,List<T>
  found: List<CAP#1>,List<CAP#2>
  reason: no instance(s) of type variable(s) T exist so that
          argument type List<CAP#2> conforms to formal parameter type List<T>
  where T is a type-variable:
    T extends Object declared in method <T>test(List<T>,List<T>)
  where CAP#1,CAP#2 are fresh type-variables:
    CAP#1 extends Object from capture of ?
    CAP#2 extends Object from capture of ?

(我的重点是粗体。)这些CAP#...类型是在capture conversion期间生成的。它向我们展示的是,在检查方法调用表达式时,list0list1被赋予了彼此不同的类型。 (对于那些需要对错误进行解释的人:这是因为test的声明断言两个列表必须具有相同的T。)

因为我们现在知道通配符与引用类型相关联,所以在类似

的情况下我们可以看到
List<?> empty = Collections.emptyList();

调用将被推断为类似“上限为Object的新类型”。或者象征性地我们可以说编译器可能会看到像

这样的东西
// target type       -->       inferred invocation type
//     v                           v
List<CAP#1> empty = Collections.<CAP#1>emptyList();

虽然:当然我们总是在猜测,因为它取决于编译器如何实现它。理论上,对于像emptyList()这样的上述简单赋值的情况,它不必为生成正确的字节码而工作。

另外,对不起,我今天感觉不像是规格。本质上,类型推断通过生成一组约束来证明方法调用应该或不应该编译。 18.5.2中描述的算法以这种方式包含通配符。

答案 1 :(得分:0)

  

在这种情况下,推断类型是什么?是对象吗?或者它没有   真的很重要,因为通配符告诉编译器任何类型   可能的?

在某种程度上,这是一个哲学问题,因为类型参数对编译的字节码没有任何影响,因此它并不重要。唯一重要的是它是否不可能满足界限和背景。只要编译器可以证明存在某些类型可以工作,那么在我看来,它应该能够继续编译它而不需要提出实际类型。