泛型通过在编译时检测到更多的代码 bug 从而使你的代码更加稳定
泛型的作用
- 在编译时更强的类型检查。
- 消除强制类型转换
泛型的类型
例如一个简单的box类
1 | public class box{ |
改成泛型之后
1 | public class box<T>{ |
所有的 Object 被 T 代替了。类型变量可以是非基本类型的的任意类型,任意的类、接口、数组或其他类型变量。
泛型类型参数和命名规范
1 | E - Element (由 Java 集合框架广泛使用) |
菱形
我们先来实例化一个泛型类
1 | Box<Integer> box = new Box<Integer>(); |
Java SE 7 开始泛型可以使用空的类型参数集<>,编译器可以推断出泛型的类型,所以后面的Integer可以省略
1 | Box<Integer> integerBox = new Box<>(); |
参数化类型
1 | Box<Integer> integerBox = new Box<>(1); |
原生类型
如果这个类是不要泛型那该怎么办呢,这时候就需要原生类型,也只有带泛型的类才具有原生类型
1 | Box rawBox = new Box(); |
java为了保持向后兼容,可以将参数化类型赋值给原生类型,反之则报错,同时原生类型也不可调用带有泛型的方法
1 | Box<String> stringBox = new Box<>(); |
警告显示原始类型绕过泛型类型检查,将不安全代码的捕获推迟到运行时。因此,开发人员应该避免使用原始类型。
泛型方法
类似于声明泛型类型,但类型参数的范围仅限于声明它的方法。允许使用静态和非静态泛型方法,以及泛型类构造函数。
1 | public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) { |
有界参数类型
对数字进行操作的方法可能只想接受Number或其子类的实例。这时,就需要用到有界类型参数。
1 | public <U extends Number> void inspect(U u){ |
调用该方法是参数是有界的,必须是Number及其子类
如果要定义多个边界,使用&符号就好了
1 | <T extends B1 & B2 & B3> |
同时边界也可以是一个接口
泛型的继承和子类型
由于Integer是继承自Number的,下面的代码是ok的
1 | public void someMethod(Number n) { /* ... */ } |
但如果是下面的方法呢
1 | public void boxTest(Box<Number> n) { /* ... */ } |
可能会想到使用Box
,因为Box
通配符
通配符(?)通常用于表示未知类型。
- 作为参数,字段或局部变量的类型;
- 作为返回类型。
上限有界通配符
使用上限有界通配符可以放宽对变量的限制
例如
1 | public static double sumOfList(List<? extends Number> list){ |
无界通配符
无界通配符类型通常用于定义未知类型,比如List<?>
- 需要使用Object类中提供的功能实现的方法
1
2
3
4
5public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
printList只能打印一个Object实例列表,不能打印List
- 泛型类中不依赖于类型参数的方法
1
2
3
4
5public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
下限有界通配符
下限有界通配符将该类型限制为特定类型或者超类 语法为<? super A>
1 | public static void addNumbers(List<? super Integer> list) { |
通配符及其子类
由于泛型的继承关系不同于类的继承,下列代码会出错
1 | List<B> listb = new ArrayList<>(); |
即使B是继承自B的,但还是会出错,他们的继承关系如图
如果这样写就没问题了
1 | List<? extends Integer> intList = new ArrayList<>(); |
由于Integer是继承自Number的,numList是Number的列表集合,关系如图
类型擦除
- 如果类型参数是无界的,则用泛型或对象替换泛型类型中的所有类型参数。因此,生成的字节码仅包含普通的类\接口和方法。
- 如有必要,插入类型铸件以保持类型安全。
- 生成桥接方法以保留扩展泛型类型中的多态性。
擦除泛型类型
类型擦除过程中,java编译器将擦除所有类型参数,类型参数有界时将其替换为第一个绑定,如果类型参数为无界,则替换为Object。
1 | class<T>{ |
T将会替换为Object
1 | public class Node<T extends Comparable<T>> { |
将会替换为第一个绑定类Comparable
擦除泛型方法
与类的泛型擦除类似
泛型的一些限制
无法使用基本类型实例化泛型
例如 不能使用int去代替Integer
1 | Pair<Integer, Character> p = new Pair<>(8, 'a'); |
无法创建类型参数实例
如
1 | public static <E> void append(List<E> list) { |
作为解决办法,可以通过反射来创建
1 | public static <E> void append(List<E> list, Class<E> cls) throws Exception { |
无法声明类型为类型参数的静态字段
如
1 | public class test{ |
无法使用具有参数化类型的强制转换或instanceof
无法创建参数化类型的数组
无法创建、捕获或抛出参数化类型的对象
无法重载每个重载的形式参数类型擦除到相同原始类型的方法
例如
1 | public class Example { |
因为他们进行类型擦除后有相同的签名