1. 怎样理解泛型的局限性
- 泛型的局限性主要与 Java 的实现机制有关
- Java 中泛型是通过类型擦除来实现的,类型参数在编译时会被替换为
Object
或限定的上界 - 运行时 Java 虚拟机不知道泛型这回事,这就带来了很多局限性
2. 下面写法是否合法,为什么,解决方法是
1 | Pair<int> minmax = new Pair<int> (1, 100); |
- 结论:不合法
- 原因:类型参数在编译时会被替换为
Object
,所以 Java 泛型中不能使用基本数据类型 - 解决方法:使用基本数据类型对应的包装类
3. 下面写法是否合法,为什么
1 | Pair<Integer>.class; |
- 结论:不合法
- 原因:这个泛型对象(反射)只有一份,与泛型无关
4. 下面代码的输出是什么,为什么
1 | Pair<Integer> p1 = new Pair<Integer> (1, 100); |
- 结果:都是
true
- 原因:一个泛型对象的
getClass()
方法的返回值与原始类型对象是相同的
5. 下面代码是否合法,为什么
1 | // 代码1 |
- 结果:代码 1 不合法;代码 2 合法
- 原因:
instanceof
关键字后面是接口或类名,instanceof
是运行时判断,也与泛型无关,但支持通配符
6. 下面代码是否合法,为什么,解决方法是
1 | class Child extends Base Implements Comparable<Child> { |
结论:Java 编译报错
Comparable
接口不能被实现两次,且两次实现的类型参数还不同,一次是Comparable<Base>
,一次是Comparable<Child>
。因为类型擦除后,实际上只能有一个解决方法:只能重写
Base
类的实现1
2
3
4
5
6
7
8
9
10
11
12class Child extends Base {
public int compareTo(Base o) {
if(!(o instanceof Child)) {
throw new IllegalArgumentException();
}
Child c = (Child) o;
// 比较代码
return 0;
}
//其他代码
}
7. 下面定义重载方法是否正确,为什么
1 | public static void test(DynamicArray<Integer> intArr) |
- 结论:错误
- 原因:重载方法后,因为类型擦除,它们的声明是一样的
8. 下面代码是否合法,为什么
1 | //T 是类型参数 |
- 结论:非法
- 原因:因为如果允许,那么用户会以为创建的就是对应类型的对象,但由于类型擦除,Java 只能创建
Object
类型的对象- Java 运行时根本不知道
T
是什么,也就无法创建T
类型的对象。容易引起误解,所以 Java 干脆禁止这么做 - 即:Java 不支持通过类型参数创建对象
- Java 运行时根本不知道
9. 接上题,如果确实希望根据类型创建对象呢,举个栗子
需要设计 API 接受类型对象,即
Class
对象,并使用Java
中的反射机制如果类型有默认构造方法,可以调用
Class
的newInstance()
方法构建对象1
2
3
4
5
6
7
8
9
10public static <T> T create(Class<T> type) {
try {
return type.newInstance();
} catch (Exception e) {
return null;
}
}
Date date = create(Date.class);
StringBuilder sb = create(StringBuilder.class);
10. 下面代码是否合法,为什么
1 | public class Singleton<T> { |
- 结论:非法
- 原因:如果合法,那么对于每种实例化类型,都需要有一个对应的静态变量和方法。但由于类型擦除,
Singleton
类型只有一份,静态变量和方法都是类型的属性,且与类型参数无关。所以,对于泛型类声明的类型参数,可以在实例变量和方法中使用,但在静态变量和静态方法中是不能使用的 - 注意:对于静态方法,它可以是泛型的,可以声明自己的类型参数,这个参数与泛型类的类型参数是没有关系的
11. Java 泛型支持多个上界吗,举个栗子
- 支持,多个上界之间以
&
分隔 Demo
:T extends Base & Comparable & Serializable;
Base
为上界类,Comparable
和Serializable
为上界接口- 如果有上界类,类应该放在第一个,类型擦除时,会用第一个上界替换
12. 下面代码是否合法,为什么
1 | Pair<Object, Integer> [] options = new Pair<Object, Integer> [] { |
- 结论:Java 编译报错
- 原因:Java 不支持创建泛型数组
13. 下面代码是否合法,为什么
1 | Integer[] ints = new Integer[10]; |
- 结论:合法
- 原因:数组是 Java 直接支持的概念,它知道数组元素的实际类型,知道
Object
和Number
都是Integer
的父类型,所以后面两种赋值都是允许的
14. 下面代码是否正确,为什么
1 | Integer[] ints = new Integer[10]; |
- 结论:编译没有问题,运行时抛出
ArrayStoreException
异常 - 原因:因为 Java 知道实际的类型是
Integer
,所以写入String
时会抛出异常
15. Java 为什么不支持创建泛型数组
- 因为如果 Java 支持创建泛型数组,那么可能会出现类型不匹配的数组赋值的错误操作,它既不会引起编译错误,也不会立即触发运行异常,却相当于埋下一颗定时炸弹💣,不定什么时候爆发
- 为避免这种情况,Java 干脆就禁止创建泛型数组
16. 现实中需要能够存放泛型对象的容器,怎么办
使用原始数组。比如
1
2
3Pair[] options = new Pair[] {
new Pair<String, Integer> ("1 元", 7), new Pair<String, Integer> ("2 元", 2), new Pair<String, Integer> ("10 元", 1)
};更好的选择是,使用泛型容器。比如
1
2
3
4DynamicArray<Pair<String, Integer>> options = new DynamicArray<> ();
options.add(new Pair<String, Integer> ("1 元", 7));
options.add(new Pair<String, Integer> ("2 元", 2));
options.add(new Pair<String, Integer> ("10 元", 1));
17. 有时,我们希望转换泛型容器为一个数组。比如,对于 DynamicArray
,我们可能希望它有这么一个方法:public E[] toArray()
,而希望可以如下这么用。即,先使用动态容器收集一些数据,然后转换为一个固定数组,对于这个常见的合理的需求,该怎样实现这个 toArray()
方法
1 | DynamicArray<Integer> ints = new DynamicArray<Integer> (); |
可以利用 Java 中的运行时类型信息和反射机制
Java 必须在运行时知道要转换成的数组类型,类型可以作为参数传递给 toArray() 方法,比如
1
2
3
4
5
6
7public E[] toArray(Class<E> type) {
Object copy = Array.newInstance(type, size);
System.arraycopy(elemendData, 0, copy, size);
return (E[]) copy;
}
Integer[] arr = ints.toArray(Integer.class);
18. 泛型和数组的关系
- 一般并不需要特别记忆,根据提示进行正确处理即可
- Java 不支持创建泛型数组
- 如果要存放泛型对象,可以使用原始类型的数组,或者使用泛型容器
- 泛型容器内部使用
Object
数组,如果要转换泛型容器为对应类型的数组,需要使用反射