问:请说说下面代码片段中注释行执行结果和原因?
DynamicArray<Integer> ints = new DynamicArray<>();
DynamicArray<? extends Number> numbers = ints;
Integer a = 200;
numbers.add(a); //这三行add现象?
numbers.add((Number) a);
numbers.add((Object) a);
public void copyTo (DynamicArray < ? super E > dest){
for (int i = 0; i < size; i++) {
dest.add(get(i)); //这行add现象?
}
}
答:上面代码段注释行执行情况解释如下。
三个 add 方法都是非法的,无论是 Integer,还是 Number 或 Object,编译器都会报错。因为 ? 表示类型安全不知,? extends Number 表示是 Number 的某个子类型,但不知道具体子类型, 如果允许写入,Java 就无法确保类型安全性,所以直接禁止。
最后方法的 add 是合法的,因为 <? super E> 形式与 <? extends E> 正好相反,超类型通配符表示 E 的某个父类型,有了它我们就可以更灵活的写入了。
本题特别重要:一定要注意泛型类型声明变量 ?时写数据的规则。
问:请说说下面代码片段中注释行执行结果和原因?
Vector<? extends Number> x1 = new Vector<Integer>(); //正确
Vector<? extends Number> x2 = new Vector<String>(); //编译错误
Vector<? super Integer> y1 = new Vector<Number>(); //正确
Vector<? super Integer> y2 = new Vector<Byte>(); //编译错误
答:上面代码编译运行情况如注释所示,本题主要考察泛型中的 ? 通配符的上下边界扩展问题。
通配符对于上边界有如下限制:Vector<? extends 类型1> x = new Vector<类型2>(); 中的类型1指定一个数据类型,则类型2就只能是类型1或者是类型1的子类。
通配符对于下边界有如下限制:Vector<? super 类型1> x = new Vector<类型2>(); 中的类型1指定一个数据类型,则类型2就只能是类型1或者是类型1的父类。
问:下面程序合法吗?
class Bean<T super Student > { //TODO }
答:编译时报错,因为Java 类型参数限定只有 extends 形式,没有 super 形式。
问:下面程序有什么问题?该如何修复?
public class Test {
public static void main(String[] args) throws Exception {
List<Integer> listInteger = new ArrayList<Integer>();
printCollection(listInteger);
}
public static void printCollection(Collection<Object> collection) {
for (Object obj : collection) {
System.out.println(obj);
}
}
}
答:语句 printCollection(listInteger); 编译报错,因为泛型的参数是没有继承关系的。修复方式就是使用 ?通配符,printCollection(Collection<?> collection),因为在方法 printCollection(Collection<?> collection) 中不可以出现与参数类型有关的方法,譬如 collection.add(),因为程序调用这个方法的时候传入的参数不知道是什么类型的,但是可以调用与参数类型无关的方法,譬如
collection.size()。
问:请解释下面程序片段的执行情况及原因?
public class Test {
public static <T> T add(T x, T y) {
return y;
}
public static void main(String[] args) {
int t0 = Test.add(10, 20.8);
int t1 = Test.add(10, 20);
Number t2 = Test.add(100, 22.2);
Object t3 = Test.add(121, "abc");
int t4 = Test.<Integer>add(10, 20);//指定泛型
int t5 = Test.<Integer>add(100, 22.2);//指定泛型
Number t6 = Test.<Number>add(121, 22.2);//指定泛型
}
}
答:
(1)t0 编译直接报错,add 的两个参数一个是 Integer,一个是 Float,所以取同一父类的最小级为 Number,故 T 为 Number 类型,而 t0 类型为 int,所以类型错误。
(2)t1 执行赋值成功,add 的两个参数都是 Integer,所以 T 为 Integer 类型。
(3)t2 执行赋值成功,add 的两个参数一个是 Integer,一个是 Float,所以取同一父类的最小级为 Number,故 T 为 Number 类型。
(4)t3 执行赋值成功,add 的两个参数一个是 Integer,一个是 Float,所以取同一父类的最小级为 Number,Number是Object的子类,故 T可 为 Object 类型。
(5)t4 执行赋值成功,add 指定了泛型类型为 Integer,所以只能 add 为 Integer 类型或者其子类的参数。
(6)t5 编译直接报错,add 指定了泛型类型为 Integer,所以只能 add 为 Integer 类型或者其子类的参数,不能为 Float。
(7)t6 执行赋值成功,add 指定了泛型类型为 Number,所以只能 add 为 Number 类型或者其子类的参数,Integer 和 Float 均为其子类,所以可以 add 成功。
t0、t1、t2、t3 其实演示了调用泛型方法不指定泛型的几种情况,t4、t5、t6 演示了调用泛型方法指定泛型的情况。在调用泛型方法的时可以指定泛型,也可以不指定泛型;在不指定泛型时泛型变量的类型为该方法中的几种类型的同一个父类的最小级(直到 Object),在指定泛型时该方法中的几种类型必须是该泛型实例类型或者其子类。切记,java 编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,再进行编译的。
问:下面两个方法有什么区别?为什么?
public static <T> T get1(T t1, T t2) {
if (t1.compareTo(t2) >= 0) ;
return t1;
}
public static <T extends Comparable> T get2(T t1, T t2) {
if (t1.compareTo(t2) >= 0) ;
return t1;
}
答:get1 方法直接编译错误,因为编译器在编译前首先进行了泛型检查和泛型擦除才编译,所以等到真正编译时 T 由于没有类型限定自动擦除为 Object 类型,所以只能调用 Object 的方法,而 Object 没有 compareTo 方法。
get2 方法添加了泛型类型限定可以正常使用,因为限定类型为 Comparable 接口,其存在 compareTo 方法,所以 t1、t2 擦除后被强转成功。所以类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过不管该限定是类还是接口都使用 extends 和 & 符号,如果限定类型既有接口也有类则类必须只有一个且放在首位,如果泛型类型变量有多个限定则原始类型就用第一个边界的类型变量来替换。