正如前面讲的,当使用一个泛型类时(包括声明变量和创建对象两种情况),都应该为这个泛型类传入一个类型实参。如果没有传入类型时间参数,编译器就会提出泛型警告。假设现在需要定义一个方法,该方法里有一个集合形参,集合形参的元素类型是不确定的,那应该怎样定义呢?
考虑如下代码:
public void test(List c){
for (int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
}
上面程序当然没有问题:这个一段最普通的遍历 List 集合的代码。问题是上面程序中 List 是一个有泛型声明的接口,此处使用 List 接口时没有传入实际类型参数,这将引起泛型警告。为此,我们考虑为 List 接口传入实际的类型参数----因为 List 集合里的元素类型是不确定的,将上面的方法改为如下形式:
public void test(List<Object> c){
for(int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
}
表面上看起来,上面方法声明没有问题,这个方法声明确实没有任何问题。问题是调用该方法传入的实际参数值时可能不是我们所期望的,例如,下面代码试图调用该方法。
// 创建一个 List<String> 对象
List<String> strList = new ArrayList<>();
// 将 strList 作为参数来调用前面的 test 方法
test(strList);//1
编译上面程序,将在 1 处发生如下编译错误:
无法将 Test 中的 test(java.util.List<java.lang.Object>) 应用于 (java.util.List<java.lang.String>)
上面程序出现了编译错误,这表明 List<String> 对象不能被当成 List<Object> 对象使用,也就是说,List<String> 类并不是 List<Object> 类的子类。
如果 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是具有泛型声明的类或接口,G<Foo> 并不是 G<Bar> 的子类型!这一点非常值得注意,因为它与我们的习惯看法不同。
与数组进行对比,先看一下数组是如何工作的。在数组中,程序可以直接把一个 Integer[] 数组赋给一个 Number[] 变量。如果试图把一个 Double 对象保存到该 Number[] 数组中,编译可以通过,但在运行时抛出 ArrayStoreException 异常。例如如下程序。
package com.sym.demo4;