我有一个简单的泛型方法,它创建一个n个元素的列表并返回它:
import java.util.*;
class A {
public static <T> List<T> init(int n) {
List<T> l = new ArrayList<T>();
while (n --> 0) l.add(null);
return l;
}
public static void main(String[] args) {
List<String> x = init(5);
f(x);
}
public static void f(List<String> l) {
System.out.println("l: " + l);
}
}
按预期工作:
$ javac A.java && java A
l: [null, null, null, null, null]
但如果我消除了额外的变量:
import java.util.*;
class A {
public static <T> List<T> init(int n) {
List<T> l = new ArrayList<T>();
while (n --> 0) l.add(null);
return l;
}
public static void main(String[] args) {
f(init(5));
}
public static void f(List<String> l) {
System.out.println("l: " + l);
}
}
它不再编译
$ javac A.java && java A
A.java:9: f(java.util.List<java.lang.String>) in A cannot be applied to (java.util.List<java.lang.Object>)
f(init(5));
^
1 error
为什么?
但这仍然有效:
import java.util.*;
class A {
public static <T> List<T> init(int n) {
List<T> l = new ArrayList<T>();
while (n --> 0) l.add(null);
return l;
}
public static <T> T id(T t) {
return t;
}
public static void main(String[] args) {
List<String> x = init(5);
f(id(x));
}
public static void f(List<String> l) {
System.out.println("l: " + l);
}
}
为什么?
答案 0 :(得分:3)
在第一个和第三个例子中,你说过你说的是List<String>
,而你说的是f(init(5))
的第二个,可能是List<Integer>
。我不是100%那是唯一的原因,但请查看:)
答案 1 :(得分:3)
Java依赖于推理来确定在没有明确定义它们的情况下类型变量是什么。
在你的第一个例子中:
List<String> x = init(5);
f(x);
编译器推断您正在调用<String> init
,因为x
是List<String>
。
在你的第二个例子中:
f(init(5));
编译器无法推断您正在调用<String> init
,因为您没有明确告诉它(通过A. <String> init(5)
),也没有将它分配给适当的变量。
在你的第三个例子中:
List<String> x = init(5);
f(id(x));
编译器推断您正在调用<List<String>> id
,并将List<String>
返回f
。
编译器对通用推理并不太聪明。除非您通过使用变量或直接将它们传递给方法明确说明类型参数是什么,否则它将无法解决它们。
如果您对特定内容感到好奇,以下是JLS的相关部分:
答案 2 :(得分:2)
如果省略额外变量,编译器在调用T
时无法推断类型参数init(5)
。它假定T
为Object
,因此编译错误。
将额外变量声明为List<String> x
,编译器将T
推断为String
。
答案 3 :(得分:2)
首先,修复;
f(A.<String>init(5)); // compiles
现在,原因:原始代码已编译,因为java可能推断由于被分配给类型变量而导致类型。但是当传递到类型参数时,推理不起作用。
修补程序使用语法在调用类型化方法时显式指定类型。
答案 4 :(得分:1)
f(init(5));
使用直接从f()
收到的参数调用init()
。但是,init()
现在返回List
T
。如果未指定T
,则Java仅使用Object
,因为它是所有对象的基类。 List<Object>
不是List<String>
,因此方法签名和参数不匹配。
List<String> x = init(5);
f(x);
此处您将List<Object>
放入List<String>
类型的变量中。这会转换它,因为String
当然是Object
的子类。转换也会成功,因为null
也可以转换为String
以及任何其他类。然后x
的类型与方法签名匹配。
List<String> x = init(5);
f(id(x));
这基本上是一回事。现在,由于x
的类型为List<String>
,id()
的类型T
为String
。这样,id()
的返回值也是List<String>
,与签名匹配。