Java 高级特性の泛型

665 阅读6分钟

这是我参与8月更文挑战的第 22 天,活动详情查看:8月更文挑战

何为泛型

正如字面意思,泛化的类型,指在编码时无法确定某一个具体的类型,需要先使用一个占位符(建议大写,全英),当运行时传入具体的类型来替换这一个泛型标记

为什么需要泛型

伪需求

假设我们需要一个列表去存 String 类型的数据,那这个结构的设计为

class MyListForString{
  String get();
  void set(Sring int)
}

然后,发现需要里一个列表去存取 Integer 类型的数据,就需要重新定义一个结构

class MyListForInteger{
  Integer get();
  void set(Integer int)
}

然后,我们需要一个列表存储 xx 类型的数据。。

图片.png

用 Object 优化

这个需求抽出来,其实就是需要有一个列表结构,然后能够存储一个指定的类型(这个类型又可能会根据需要进行变化),并且需要能正确的取出对应的类型,现在,我们为了应对变化的需求,重复编写差不多的 MyListForXX

根据多态以及向上转型的知识点(不懂的 xdm 可以来看看我准备的前置知识点 Java 三大基础特性の多态),我们可以用 Object 优化为成下面的代码块

class MyList{
  Object get();
  void set(Object obj)
}

现在已经能做到通用性了,就是用起来,emm,得小心一点

MyList list = new MyList();
list.set(1);
list.set("1");
Integer a = (Integer)list.get();  // 代码中取出来的类型实际是 Object, 我们需要手动强制为存入时的类型
String s = (String)list.get();

正如伪代码演示的这样,我们存入数据的时候,没有限制,但是,取出来的话,就需要小心翼翼了,毕竟谁也不知道 list 中存入了什么类型的数据,或许,我们可以修改实例创建的描述来实现一点点毫无约束力的限制?

MyList listOnlyForInt = new MyList();  // 注意看实例名
list.set(1);

泛型优化

或许是受够了这种软弱无力的约束,java 在 JDK5 中终于引入了泛型

用泛型的思路来对上面的代码进行修改,好处是显而易见的,我们在创建 MyList 的实例时,就告诉了 JVM 这个实例操作的泛型 T 的实例类型是 String,JVM 会在我们存入数据的数据校验数据类型是否匹配,会在我们取出的时候,自动的强转为对应的类型

JVM 的内部并没有什么魔法,底层的使用的类型还是 Object,但是,JVM 会根据我们传递的具体泛型类型来做入参时的校验,出参时的强转

class MyList<T>{
  T get();
  void set(T int)
}
MyList<String> strs = new MyList<String>();
String s = strs.get();

java.util.ArrayList

来阅读一下我们经常使用的集合类 ArrayListget(), add() 的源码

1、集合里实际上使用了 Object[] 的数组

2、add() 将数据存入到了 object 的数组当中,这里涉及到一个向上转型的隐藏操作(变相的验证,所有的 class 都是继承至 Object

3、get() 对获取到的数据,强转为泛型 E 的实际类型

public E get(int index) {	// line: 432
  rangeCheck(index);
  return elementData(index);
}
E elementData(int index) {  // line: 421
  return (E) elementData[index];
}
public boolean add(E e) {  // line: 461
  ensureCapacityInternal(size + 1);  // Increments modCount!!
  elementData[size++] = e;
  return true;
}
transient Object[] elementData; // non-private to simplify nested class access  // line: 135

原来这就是泛型

从上面例子来看,细心的同学应该发现了,“咦,原来我写 List, Set, Map 那些集合的操作就是用上了泛型鸭?”

随手写几个:

List<String> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();
HashMap<String, User> hashMap = new HashMap<>();

没错,泛型的基础使用就那么回事

图片.png

泛型的边界

在泛型的世界里,其实也有类似继承关系的说法,比如 <T extends String>, <T super String>

extends

class MyList<T extends String>{
  T get();
}

这段代码看起来没有多大的改变,唯一的区别就是泛型 T 是继承自 String 的某一个类型

此外,因为是继承关系,我们可以获取到任意类型的泛型实例 T,并将他们向上转型并当成 String 处理,然后通过多态的思路去实际调用泛型 T 中对应的方法

super

class MyList<T super String>{
  void set(T int)
}

这里可以理解为,我们运行时传入的泛型 T,是 String 的父类,具体是哪一级的父类就不得而知

extends 与 super 的 区别

superextends 不同的一点就是, 使用了 <T extends XXX> 的泛型操作可以 取值 当成 XXX 处理,使用了 <T super XXX> 的泛型只能将 XXX 对应的实例 传入 给泛型类型处理

类型擦除

Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
Class c3 = new ArrayList().getClass();
System.out.println(c1 == c2);	// true
System.out.println(c1 == c3);	// true

从上述的代码来看,ArrayList 虽然申明了具体的泛型,或者是不带泛型,他们对应的 Class 全是一样的?变相的说明,在 class 中,根本就不存在泛型的痕迹?

方法上的泛型

虽然整篇文章都在讲 class 上的泛型,实际上,泛型也是能应用与方法上的

但是泛型是没有办法直接初始化的,需要我们传入一个具体的泛型实例,或者是对应的泛型 class

泛型方法的定义格式为 [作用域] [static] <T> T 方法名(参数列表)

以我封装的一个 JsonUtil.toObject() 为例,方法的定义就是 public static <T> T toObject()

public static <T> T toObject(String json, Class<T> clz) {
  if (json == null) {
    try {
      return clz.newInstance();
    } catch (Exception e) {
      LogUtil.exception("JsonUtil.toObject: <T>", e);
    }
  }
  return json_.toObject(json, clz);
}
public static <T> T toObject(String json, T t) {	// 或者我们也可以传入泛型的一个具体的实例,不过,这种情况会非常的少见
  toObject(json, t.getClass());
}
// 伪代码调用
User user = JsonUtil.toObject(jsonStr, User.class);

泛型的标记

在本篇文章中,大量使用了标记 T,来表示泛型,我们也可以换成其他的符号来标记,但都要求,先使用 <标记> 的方式声明这是一个泛型操作,比如演示代码

class List<E>{}

<A> A get();

<A, B, C> Tuple<A,B,C> tupleInstance(){}

元组

忘记介绍一个应用了泛型还比较好玩的结构,元组,这个数据结构的意义是 为方法返回多个参数,当然,我们用 map, list 也能实现,但不如元组这么优雅

首先,我们定义一个返回 2 个泛型类型的 元组 结构

class Tuple<A, B>{
  final A a;
  final B b;
  public Tuple(A a1, B b1){
    a = a1;
    b = b1;
  }
}

就和初始化一个普通的 class 一样,用法也没什么差异,变化的地方就是这个元组内部的2个字段,他们的 class 是在运行时确定下来了,我们可以在需要的时候,任意变化他们的类型

Tuple<Integer, String> tuple = new Tuple<>(1, "1");
Integer a = tuple.a;
String b = tuple.b;

java 里的泛型特性就先介绍这么多,hxd 给我的点赞和收藏是最大的鼓励

图片.png


原创文章,未经允许,禁止转载

-- by 安逸的咸鱼