泛型

泛型 #

为什么需要泛型 #

以集合为例,如果写死类型,集合就不通用,只能放某个类或者子类
但是实际上,一个集合的设计需要给所有类型使用,包括未知的用户自定义类型

如果使用 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。仿佛类型参数不存在

这是因为:

  1. 编译阶段,编译器进行类型检测,不通过则报错
  2. 通过后,会把泛型擦除。类型参数会被替换成它的一个上界,没有指定上界就会替换成Object
  3. 运行时,没有泛型类,只有普通类,所以前述判断会为true(都被擦除为原始类型List)
  4. 擦除泛型后的类型称为原始类型

类型参数存在导致的弊端:

  1. 由于泛型参数不支持基本类型,类型擦除结果是具体的引用类型
  2. 运行时只能对原始类型进行类型检测:obj.instanceof List 会编译错误。因为不存在List.class,擦除后只有List.class
  3. 不能实例化类型参数,运行时无法确定具体类型,也无法知道T是否有无参构造器
  4. 不能实例化泛型数组T[],如果能实例化泛型数组,使用数组时容易引发类型转换异常。比如类型擦除后Object[]不能强转为String[]等

extends 和 super #