为了账号安全,请及时绑定邮箱和手机立即绑定

Java 高级特性の泛型

标签:
Java

何为泛型

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

为什么需要泛型

伪需求

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

class MyListForString{

  String get();

  void set(Sring int)

}


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

class MyListForInteger{

  Integer get();

  void set(Integer int)

}


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

https://img1.sycdn.imooc.com//6122446d000198d101140086.jpg

用 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<>();


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

https://img1.sycdn.imooc.com//6122446d000114a300960096.jpg

泛型的边界

在泛型的世界里,其实也有类似继承关系的说法,比如 <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 给我的点赞和收藏是最大的鼓励


作者:安逸的咸鱼
链接:https://juejin.cn/post/6999098482665619464
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消