1:问:什么是 Java 泛型中的限定通配符和非限定通配符?有什么区别?
答:限定通配符对类型进行限制,泛型中有两种限定通配符,一种是 <? extends T> 来保证泛型类型必须是 T 的子类来设定泛型类型的上边界,另一种是 <? super T> 来保证泛型类型必须是 T 的父类来设定类型的下边界,泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。非限定通配符 <?> 表示可以用任意泛型类型来替代,可以在某种意义上来说是泛型向上转型的语法格式,因为 List<String> 与 List<Object> 不存在继承关系。
2:问:简单说说 List<Object> 与 List 原始类型之间的区别?
答:主要区别有两点。
原始类型和带泛型参数类型 <Object> 之间的主要区别是在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查,通过使用 Object 作为类型可以告知编译器该方法可以接受任何类型的对象(比如 String 或 Integer)。
我们可以把任何带参数的类型传递给原始类型 List,但却不能把 List<String> 传递给接受 List<Object> 的方法,因为会产生编译错误。
3:问:简单说说 List<Object> 与 List<?> 类型之间的区别?
答:这道题跟上一道题看起来很像,实质上却完全不同。List<?> 是一个未知类型的 List,而 List<Object> 其实是任意类型的 List,我们可以把 List<String>、List<Integer> 赋值给 List<?>,却不能把 List<String> 赋值给 List<Object>。譬如:
所以通配符形式都可以用类型参数的形式来替代,通配符能做的用类型参数都能做。 通配符形式可以减少类型参数,形式上往往更为简单,可读性也更好,所以能用通配符的就用通配符。 如果类型参数之间有依赖关系或者返回值依赖类型参数或者需要写操作则只能用类型参数。
4:问:List<? extends T>和List <? super T>之间有什么区别?
答:有时面试官会用这个问题来评估你对泛型的理解,而不是直接问你什么是限定通配符和非限定通配符,这两个 List 的声明都是限定通配符的例子,List<? extends T> 可以接受任何继承自 T 的类型的 List,而 List<? super T> 可以接受任何 T 的父类构成的 List。例如 List<? extends Number> 可以接受 List<Integer> 或 List<Float>。Java 容器类的实现中有很多这种用法,比如 Collections 中就有如下一些方法:
5:问:说说 <T extends E> 和 <? extends E> 有什么区别?
答:它们用的地方不一样,<T extends E> 用于定义类型参数,声明了一个类型参数 T,可放在泛型类定义中类名后面、接口后面、泛型方法返回值前面。 <? extends E> 用于实例化类型参数,用于实例化泛型变量中的类型参数,只是这个具体类型是未知的,只知道它是 E 或 E 的某个子类型。虽然它们不一样,但两种写法经常可以达到相同的目的,譬如:
6:问:说说 List<String> 与 List<Object> 的关系和区别?
答:这两个东西没有关系只有区别。
因为也许很多人认为 String 是 Object 的子类,所以 List<String> 应当可以用在需要 List<Object> 的地方,但是事实并非如此,泛型类型之间不具备泛型参数类型的继承关系,所以 List<String> 和 List<Object> 没有关系,无法转换。
7:问:请说说下面代码片段中注释行执行结果和原因?
答:上面代码段注释行执行情况解释如下。
三个 add 方法都是非法的,无论是 Integer,还是 Number 或 Object,编译器都会报错。因为 ? 表示类型安全无知,? extends Number 表示是 Number 的某个子类型,但不知道具体子类型, 如果允许写入,Java 就无法确保类型安全性,所以直接禁止。
最后方法的 add 是合法的,因为 <? super E> 形式与 <? extends E> 正好相反,超类型通配符表示 E 的某个父类型,有了它我们就可以更灵活的写入了。
本题特别重要:一定要注意泛型类型声明变量 ?时写数据的规则。
8:问:请说说下面代码片段中注释行执行结果和原因?
答:上面代码编译运行情况如注释所示,本题主要考察泛型中的 ? 通配符的上下边界扩展问题。
通配符对于上边界有如下限制:Vector<? extends 类型1> x = new Vector<类型2>(); 中的类型1指定一个数据类型,则类型2就只能是类型1或者是类型1的子类。
通配符对于下边界有如下限制:Vector<? super 类型1> x = new Vector<类型2>(); 中的类型1指定一个数据类型,则类型2就只能是类型1或者是类型1的父类。
9: 问:下面程序合法吗?
答:编译时报错,因为 Java 类型参数限定只有 extends 形式,没有 super 形式。
10: 问:下面程序有什么问题?该如何修复?
答:语句 printCollection(listInteger); 编译报错,因为泛型的参数是没有继承关系的。修复方式就是使用 ?通配符,printCollection(Collection<?> collection),因为在方法 printCollection(Collection<?> collection) 中不可以出现与参数类型有关的方法,譬如 collection.add(),因为程序调用这个方法的时候传入的参数不知道是什么类型的,但是可以调用与参数类型无关的方法,譬如
collection.size()。
11:问:请解释下面程序片段的执行情况及原因?
答:t0 编译直接报错,add 的两个参数一个是 Integer,一个是 Float,所以取同一父类的最小级为 Number,故 T 为 Number 类型,而 t0 类型为 int,所以类型错误。
t1 执行赋值成功,add 的两个参数都是 Integer,所以 T 为 Integer 类型。
t2 执行赋值成功,add 的两个参数一个是 Integer,一个是 Float,所以取同一父类的最小级为 Number,故 T 为 Number 类型。
t3 执行赋值成功,add 的两个参数一个是 Integer,一个是 Float,所以取同一父类的最小级为 Object,故 T 为 Object 类型。
t4 执行赋值成功,add 指定了泛型类型为 Integer,所以只能 add 为 Integer 类型或者其子类的参数。
t5 编译直接报错,add 指定了泛型类型为 Integer,所以只能 add 为 Integer 类型或者其子类的参数,不能为 Float。
t6 执行赋值成功,add 指定了泛型类型为 Number,所以只能 add 为 Number 类型或者其子类的参数,Integer 和 Float 均为其子类,所以可以 add 成功。
t0、t1、t2、t3 其实演示了调用泛型方法不指定泛型的几种情况,t4、t5、t6 演示了调用泛型方法指定泛型的情况。 在调用泛型方法的时可以指定泛型,也可以不指定泛型;在不指定泛型时泛型变量的类型为该方法中的几种类型的同一个父类的最小级(直到 Object),在指定泛型时该方法中的几种类型必须是该泛型实例类型或者其子类。切记,java 编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,再进行编译的。
12:问:下面两个方法有什么区别?为什么?
答:get1 方法直接编译错误,因为编译器在编译前首先进行了泛型检查和泛型擦除才编译,所以等到真正编译时 T 由于没有类型限定自动擦除为 Object 类型,所以只能调用 Object 的方法,而 Object 没有 compareTo 方法。
get2 方法添加了泛型类型限定可以正常使用,因为限定类型为 Comparable 接口,其存在 compareTo 方法,所以 t1、t2 擦除后被强转成功。所以类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过不管该限定是类还是接口都使用 extends 和 & 符号,如果限定类型既有接口也有类则类必须只有一个且放在首位,如果泛型类型变量有多个限定则原始类型就用第一个边界的类型变量来替换。
基础知识:
1: 为什么需要泛型?
1) : 类型安全 (在编译期间判断类型,类型不对则不通过)
2) 设计通用类型 (提高复用率)
2: 用在什么地方
1): 用在类上,叫做泛型类
public class Animal<T> {
private T t;
public T getData() {
return t;
}
}
2): 用在接口上,叫做泛型接口
public interface FansInterface<K, V> {
K add(V v);
}
3): 用在方法上,叫做泛型方法, (不一定非要在泛型类中,在普通类中也可以申请泛型方法)
public <V> void test(V v) {
}
3: 泛型注意事项
1): 泛型类型参数不能是基本类型。
-
: 一旦形参中使用了?通配符,那么除了写入null以外,不可以調用任何和泛型参数有关的方法,当然和泛型参数无关的方法是可以调用的,如:
public static void test(List<?> list) {
int size = list.size(); // 正确list.add(new Integer(1)); // 错误
}
看上面的形参使用了 <?> 通配符, 所以调用size()方法是与泛型无关的,可以调用,但要调用add()方法就会报错,因为<?>表示不确定的类型,一旦加入新类型后,就无法保证类型的安全性,所以list.add()方法是编译失败的。
4: Java泛型无法使用 instanceof关键字,例:
Box<Integer> integerBox = new Box<Integer>();
if (integerBox instanceof Box<Integer>) // 错误
因为编译器使用类型擦除, 在运行时不会跟踪类型参数, 所以无法使用instanceof关键字,integerBox 在经过编译器类型擦除后, 会变为 Box integerBox = new Box(); 所以无法使用instanceof关键字
5: 泛型中的继承关系
可以看出 Integer虽然继承自Number, 但是 Box<Integer>与 Box<Number> 却不是继承关系,
6: 泛型中的通配符
可以看出,通配符可以分为子类型限定,超类型限定和无限定
子类型的限定, 表示类型的上界,类似泛型的类型变量限定,
格式: ? extends X
作用: 1:用于安全的访问数据,
2: 可以访问X 以及 X的子类型
3: 只能写入null, 其余的类型都无法写入。