泛型 #
为什么需要泛型 #
以集合为例,如果写死类型,集合就不通用,只能放某个类或者子类
但是实际上,一个集合的设计需要给所有类型使用,包括未知的用户自定义类型
如果使用 Object 代替,存储时方便,取出时需要显式强制类型转换
与此同时,集合中还可以存放多种类型数据,导致强转、具体操作等均不安全
如果判断类型再强转,会增加代码复杂度
(Object 没有限制范围的能力)
如果考虑存放时限制类型,但只能限制自己的编码,如果存在多人编码,调用自己的集合,获取到的数据可能就不是安全的类型。久而久之自己都可能忘记
如果将某个类型对某种集合的操作封装起来,使用封装类操作集合,则导致有多少类型就要封装多少个集合操作类,代码超级冗余,且只能处理已有类型,不能处理新类型。导致只要一个新类要用集合,就要写一个封装类,不可取
从根本上来说,泛型是为了实现Java面向对象环境下,类之间容纳未知类的安全操作能力,以解决上述问题带来的编码困境
比如,在某个对象新建时就指定了泛型参数
- 该对象的方法只能使用指定类型,避免乱传参数
- 编译时就会报错,避免运行时出问题
- 取用元素也不必转换
- 可以编写一套代码,适用所有类型
泛型方法 #
泛型方法可以定义在泛型类,也可以定义在普通类
class A<T> {
public void method(T t) {
// do something...
}
}
class A {
public <T> void method(T t) {
// do something...
}
}
定义泛型方法需要在泛型类前面指定泛型参数,从而在方法中使用具体泛型参数,或者在泛型方法前定义
但使用时不需要手动指定具体类型,会根据调用进行类型推断
如果是基本类型,会自动装箱为包装类
由于静态类型方法不能访问类上定义的泛型参数,因为类上的泛型参数只能在对象实例化时指定,所以静态方法要使用泛型就必须定义成泛型方法
class A {
public static <T> void method(T t) {
// do something...
}
}
类型擦除 #
List<String> strings = new ArrayList<>();
List<Integer> ints = new ArrayList<>();
Println(ints.getClass() == strings.getClass())
# 结果为true。仿佛类型参数不存在
这是因为:
- 编译阶段,编译器进行类型检测,不通过则报错
- 通过后,会把泛型擦除。类型参数会被替换成它的一个上界,没有指定上界就会替换成Object
- 运行时,没有泛型类,只有普通类,所以前述判断会为true(都被擦除为原始类型List)
- 擦除泛型后的类型称为原始类型
类型参数存在导致的弊端:
- 由于泛型参数不支持基本类型,类型擦除结果是具体的引用类型
- 运行时只能对原始类型进行类型检测:obj.instanceof List 会编译错误。因为不存在List.class,擦除后只有List.class
- 不能实例化类型参数,运行时无法确定具体类型,也无法知道T是否有无参构造器
- 不能实例化泛型数组T[],如果能实例化泛型数组,使用数组时容易引发类型转换异常。比如类型擦除后Object[]不能强转为String[]等