1. 怎样理解泛型
- 字面意思就是广泛的类型
- 类、接口和方法的代码可以应用于非常广泛的类型,代码与它们能够操作的数据类型不再绑定在一起,同一套代码可以用于多种数据类型
- 好处是不仅可以复用代码,降低耦合,而且可以提高代码的可读性和安全性
2. 怎样理解泛型类
- 类名后面多了一个
<T>
T
表示类型参数,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入- 类型参数可以有多个,多个类型之间以逗号分隔。比如:
Pair<String, Integer> pair = new Pair<String, Integer>("老隋", 100);
<String, Integer>
既出现在了声明变量时,也出现在了new
后面,比较繁琐。从 Java 7 开始,支持省略new
后面的类型参数。比如:Pair<String, Integer> pair = new Pair<>("老隋", 100);
3. 泛型的基本原理
- Java 有 Java 编译器 和 Java 虚拟机,编译器将 Java 源代码转换为
.class
文件,虚拟机加载并运行.class
文件 - 对于泛型类,Java 编译器会将泛型代码转换为普通的非泛型代码,将类型
T
擦除,替换为Object
,插入必要的强制类型转换 - Java 虚拟机在实际运行的时候,它是不知道泛型这回事的,只知道普通的类及代码
4. Java 为什么要用类型擦除的方式设计泛型呢
- 泛型是 Java 5 以后才支持的
- 这么设计是为了兼容性而不得已的一个选择
5. 泛型的好处是什么
- 更好的安全性。语言和程序设计的一个重要目标是将 bug 尽量消灭在摇篮里,能消灭在写代码的时候,就不必等到代码写完程序运行的时候。只使用 Object,代码写错的时候,开发环境和编译器不能帮我们发现问题、不会有错误提示。使用泛型,开发环境和编译器就可以提示可能的类型错误,这称之为类型安全,为程序多设置一道安全防护网
- 更好的可读性。使用泛型,还可以省去繁琐的重复的强制类型转换代码,再加上明确的类型信息,代码可读性也更好
6. 怎样理解容器类
- 泛型类最常见的用途是作为容器类
- 容器类,即容纳并管理多项数据的类
- Java 泛型的引入主要也是为了更好地支持 Java 容器
7. 实现一个类似 ArrayList 的动态数组
1 | //具体的类型还可以是一个泛型类,比如:DynamicArray<Pair<Integer, String>> arr = new DynamicArray<>(); |
8. 怎样理解泛型方法
类型参数为
T
,放在返回值前面类型参数可以有多个,以逗号分隔。比如
1
2
3
4public static <U, V> Pair<U, V> makePair(U first, V second) {
Pair<U, V> pair = new Pair<> (first, second);
return pair;
}一个方法是不是泛型的,与它所在的类是不是泛型没有关系
9. 怎样理解泛型接口
接口也可以是泛型的,实现接口时,应该指定具体的类型
Comparable
接口 和Comparator
接口 都是泛型的。代码如下1
2
3public interface Comparable<T> {
public int compareTo(T o);
}1
2
3
4public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
10. 怎样理解类型参数的限定
类型参数的上界限定通过
extends
关键字表示Java 支持限定这个参数的一个上界,这个上界可以是某个具体的类或者某个具体的接口,也可以是其他的类型参数
上界为某个具体的类:指定边界后,类型擦除时就不会转换为
Object
了,而是会转换为它的边界类型上界为某个接口
1
public static <T extends Comparable<T>> T max(T[] arr) {}
上界为其他类型参数
1
2
3
4
5public <T extends E> void addAll(DynamicArray<T> c) {
for(int i = 0; i < c.size; i++) {
add(c.get(i));
}
}
需要注意的是,虽然
Integer
是Number
的子类,但DynamicArray<Integer>
并不是DynamicArray<Number>
的子类,所以DynamicArray<Integer>
的对象也就不能赋值给DynamicArray<Number>
,即容器是容器,类型是类型