我不认为我真的了解Java泛型。这两种方法有什么区别?为什么第二个没有编译,错误如下所示。
由于
static List<Integer> add2 (List<Integer> lst) throws Exception {
List<Integer> res = lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
static <T extends List<Integer>> T add2 (T lst) throws Exception {
T res = lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
Exception in thread "main" java.lang.RuntimeException: Uncompilable source code - incompatible types
required: T
found: capture#1 of ? extends java.util.List
答案 0 :(得分:6)
对于第二种编译方法,您必须将newInstace()
的结果强制转换为T
:
static <T extends List<Integer>> T add2 (T lst) throws Exception {
T res = (T) lst.getClass().newInstance();
for (Integer i : lst) res.add(i + 2);
return res;
}
关于两种方法之间的区别,让我们忘记实现,只考虑签名。
编译代码之后,两个方法都将具有完全相同的签名(因此如果具有相同的名称,编译器将给出错误)。这是因为所谓的类型擦除。
在Java中,编译后所有类型参数都会消失。它们被最通用的原始类型取代。在这种情况下,两种方法都将编译为List add2(List)
。
现在,这将显示两种方法之间的区别:
class Main {
static <T extends List<Integer>> T add1(T lst) { ... }
static List<Integer> add2(List<Integer> lst) { ... }
public static void main(String[] args) {
ArrayList<Integer> l = new ArrayList<Integer>();
ArrayList<Integer> l1 = add1(l);
ArrayList<Integer> l2 = add2(l); // ERROR!
}
}
标记为// ERROR!
的行将无法编译。
在第一种方法add1
中,编译器知道它可以将结果分配给类型为ArrayList<Integer>
的变量,因为签名表明方法的返回类型与参数的那个。由于参数的类型为ArrayList<Integer>
,因此编译器会将T
推断为ArrayList<Integer>
,这样您就可以将结果分配给ArrayList<Integer>
。
在第二种方法中,所有编译器都知道它将返回List<Integer>
的实例。它无法确定它是ArrayList<Integer>
,因此您必须进行显式转换,ArrayList<Integer> l2 = (ArrayList<Integer>) add2(l);
。请注意,这不会解决问题:您只是告诉编译器停止抱怨并编译代码。您仍会收到警告(未经检查的强制转换),可以通过使用@SuppressWarnings("unchecked")
注释方法来使其静音。现在编译器会很安静,但你可能仍然在运行时得到ClassCastException
!
答案 1 :(得分:3)
指定第一个接受List&lt; Integer&gt;并返回List&lt; Integer&gt;。列表是一个接口,暗示是一个实现List的具体类的实例作为参数传递,并且实现List的一些其他具体类的实例作为结果返回,而这两个类之间没有任何进一步的关系他们都实施了List。
第二个收紧它:它被指定接受一些实现List&lt; Integer&gt;的类。作为参数,并返回与结果完全相同的类或后代类的实例。
所以例如你可以这样调用第二个:
ArrayList list; // initialization etc not shown
ArrayList result = x.add2(list);
但不是第一个,除非你添加了一个类型转换。
这是另一个问题。 ; - )
@Bruno Reis解释了编译错误。
答案 2 :(得分:1)
为什么第二个没有编译,错误如下所示。
显示的错误是实际报告您尝试运行代码但编译失败。最好将IDE配置为不运行带编译错误的代码。或者如果你坚持让这种情况发生,至少要报告实际的编译错误以及行号等。
答案 3 :(得分:1)
“我认为我不太了解Java泛型。”
没有人......
该问题与getClass()
的有趣返回类型有关。看它的javadoc。并this recent thread。
在您的两个示例中,lst.getClass()
都会返回Class<? extends List>
,因此,newInstance()
会返回? extends List
- 或者更正式地说,是由W
引入的新类型参数W extends List
javac W
在第一个示例中,我们需要将List<Integer>
分配给W
。这是assignment conversion允许的。首先,List
可以转换为List
,因为W
是List
的超级类型。然后,由于List
是原始类型,因此允许使用可选的未经检查的转换,将List<Integer>
转换为W
,并使用强制编译器警告。
在第二个示例中,我们需要将T
分配给W
。我们在这里运气不佳,没有路径可以从T
转换为W
。这是有道理的,因为就javac而言,T
和List
可能是W
的两个不相关的子类。
当然,我们知道T
是getClass()
,如果允许,分配将是安全的。这里的根本问题是x.getClass()
丢失了类型信息。如果Class<? extends X>
在没有删除的情况下返回{{1}},那么您的两个示例都会在没有警告的情况下进行编译。它们确实是类型安全的。
答案 4 :(得分:0)
泛型是一种保证类型安全的方法。 例如:
int[] arr = new int[4];
arr[0] = 4; //ok
arr[1] = 5; //ok
arr[2] = 9; //ok
arr[3] = "Hello world"; // you will get an exception saying incompatible
类型。
默认情况下,Java中的数组是typeSafe。整数数组仅用于表示 包含整数,没有别的。
现在:
ArrayList arr2 =new ArrayList();
arr2.add(4); //ok
arr2.add(5); //ok
arr2.(9); //ok
int a = arr2.get(0);
int b = arr2.get(1);
int c = arr3.get(2);
您将获得异常,例如无法转换Object 实例到整数。
原因是ArrayList存储对象而不是像 上面的数组。
正确的方法是显式转换为整数。你必须这样做 因为类型安全性尚未得到保证。 例如:
int a =(int)arr2.get(0);
要为集合使用类型安全性,只需指定集合包含的对象类型即可。 例如:
ArrayList<Integer> a = new ArrayList<Integer>();
After insertion into the data structure, you can simply retrieve it like you
would do with an array.
例如:
int a = arr2.get(0);