目标类型具有通配符时的泛型方法类型推断
Generic method type inference when the target type has a wildcard
我了解编译器使用目标类型来确定使泛型方法调用适用的类型参数。例如,在以下语句中:
List<String> listOne = Collections.emptyList();
其中 Collections.emptyList
在其签名中有一个类型参数 T
public static final <T> List<T> emptyList() {
在这种情况下,T
的推断类型参数是 String
。
现在考虑以下问题:
List<?> listTwo = Collections.emptyList();
这种情况下的推断类型是什么?是Object
吗?或者由于通配符告诉编译器任何类型都是可能的,所以它并不重要?
通配符的每次使用都有与之关联的不同类型。 (通常 JLS 将其称为 "fresh type"。)这就是编译器错误的工作原理:
List<?> list0 = ... ;
List<?> list1 = ... ;
list0.add(list1.get(0)); // error
因为 list0
和 list1
被编译器赋予了不同的类型,因此在大多数情况下
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) {}
编译器发出的错误实际上告诉我们它为 list0
和 list1
.
生成的类型的一些信息
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 期间生成的。它向我们展示的是,在检查方法调用表达式时,list0
和 list1
被赋予了彼此不同的类型。 (对于那些需要错误解释的人:这是因为 test
的声明断言两个列表必须具有相同的 T
。)
所以既然我们现在知道通配符与引用类型相关联,我们可以在
这样的情况下看到
List<?> empty = Collections.emptyList();
调用将被推断为类似于 "a fresh type where the upper bound is Object"。或者我们可以象征性地说编译器可能会看到类似
的内容
// target type --> inferred invocation type
// v v
List<CAP#1> empty = Collections.<CAP#1>emptyList();
虽然:当然我们总是会猜测一点,因为这取决于编译器如何实现它。理论上,对于像上面 emptyList()
的简单赋值这样的情况,它不需要做任何工作来生成正确的字节码。
还有,不好意思,我今天懒得刷spec了。本质上,这里的类型推断通过生成一组约束来证明方法调用应该或不应该编译。 18.5.2中描述的算法以这种方式合并了一个通配符。
What is the inferred type in this case? Is it Object? Or it doesn't
really matter due to the wildcard telling the compiler any type is
possible?
在某种程度上,这是一个哲学问题,因为类型参数对编译的字节码没有任何影响,所以具体是什么并不重要。唯一重要的是是否不可能满足边界和上下文。只要编译器可以证明 存在 某种类型可以工作,那么在我看来,它应该能够继续编译它而无需提出实际类型.
我了解编译器使用目标类型来确定使泛型方法调用适用的类型参数。例如,在以下语句中:
List<String> listOne = Collections.emptyList();
其中 Collections.emptyList
在其签名中有一个类型参数 T
public static final <T> List<T> emptyList() {
在这种情况下,T
的推断类型参数是 String
。
现在考虑以下问题:
List<?> listTwo = Collections.emptyList();
这种情况下的推断类型是什么?是Object
吗?或者由于通配符告诉编译器任何类型都是可能的,所以它并不重要?
通配符的每次使用都有与之关联的不同类型。 (通常 JLS 将其称为 "fresh type"。)这就是编译器错误的工作原理:
List<?> list0 = ... ;
List<?> list1 = ... ;
list0.add(list1.get(0)); // error
因为 list0
和 list1
被编译器赋予了不同的类型,因此在大多数情况下
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) {}
编译器发出的错误实际上告诉我们它为 list0
和 list1
.
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 期间生成的。它向我们展示的是,在检查方法调用表达式时,list0
和 list1
被赋予了彼此不同的类型。 (对于那些需要错误解释的人:这是因为 test
的声明断言两个列表必须具有相同的 T
。)
所以既然我们现在知道通配符与引用类型相关联,我们可以在
这样的情况下看到List<?> empty = Collections.emptyList();
调用将被推断为类似于 "a fresh type where the upper bound is Object"。或者我们可以象征性地说编译器可能会看到类似
的内容// target type --> inferred invocation type
// v v
List<CAP#1> empty = Collections.<CAP#1>emptyList();
虽然:当然我们总是会猜测一点,因为这取决于编译器如何实现它。理论上,对于像上面 emptyList()
的简单赋值这样的情况,它不需要做任何工作来生成正确的字节码。
还有,不好意思,我今天懒得刷spec了。本质上,这里的类型推断通过生成一组约束来证明方法调用应该或不应该编译。 18.5.2中描述的算法以这种方式合并了一个通配符。
What is the inferred type in this case? Is it Object? Or it doesn't really matter due to the wildcard telling the compiler any type is possible?
在某种程度上,这是一个哲学问题,因为类型参数对编译的字节码没有任何影响,所以具体是什么并不重要。唯一重要的是是否不可能满足边界和上下文。只要编译器可以证明 存在 某种类型可以工作,那么在我看来,它应该能够继续编译它而无需提出实际类型.