您的位置:首页 > 编程语言 > Java开发

java学习笔记--基础知识--泛型-深入

2019-06-13 17:37 1686 查看

参考文档:

https://segmentfault.com/a/1190000017721623?_ea=5882314

http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html

 

泛型相关概念和名词

泛型: Generic Type

  • Generic type && type parameter:    举例: interface List<E> {}, List<E>就是generic type, 而 E 就是 type parameter
  • parameterized type && type argument: 举例:List<String> stringList;  List<String>就是parameterized type,是List<E>的一个实例,而String则是type argument

Generic type -》 泛型      Type parameter: 泛型形参

Parameterized type -》参数化类型        type argument : 泛型实参

 

通配符: wildcard

泛型中的通配符分为3类

  • ?- unbound, 没有任何限定的通配符
  • ? extends Class - upper bounded, 上限限定,必须是Class或者Class的子类
  • ? super Class - lower bounded, 下限限定, 必须是Class或者Class的父类

与通配符结合后,Parameterized type也分为3种:

  • conceret Parameterized type, 例如  List<String>,   中文名就叫 固定参数化类型 吧
  • unbounded Parameterized type,  例如 List<?>, 无绑定参数化类型
  • bounded Parameterized type, 例如 List<? extends Number>,   List<? super Integer>,  有绑定参数化类型

 

类型信息擦除 Type Erasure

泛型仅仅存在于编译期间,JVM是感知不到泛型信息的。 在编译的时候,编译器通过Type Erasure清除Type parameter和Type Argument。

我们先来了解下,编译器怎么处理泛型的代码,2种方式:

  1. Code Specialization: 编译器为每个泛型实例都产生一份代码。 譬如说, 编译器会给 List<Integer>产生一份代码,也会为List<String>产生一份代码。C++采用这种方式处理Template。
  2. Code Sharing: 编译器对泛型只生成一份唯一的代码, 然后所有的这个泛型的实例都会映射到这个一份唯一的代码上,进行类型检查和必要的类型转换。 java采用是这种方式。

java编译器实现Code Sharing的方法称为 Type Erasure

Type Erasure就是将通过忽略Type parameter和Type argument的方式,将一个泛型实例映射到编译器生成的那份唯一代码上。 怎么映射呢? 其实这很难想明白。 

我们可以这样想象一下, Type Erasure就是将Type parameter和Type argument清除,将泛型的代码变成非泛型的普通代码(中间代码),然后编译成字节码(实际上,编译器编译时没有生成中间的代码,而是直接生成了字节码)。

 

看下图,左边是泛型代码,右边是Type Erasure之后的代码

Type Erasure的工作步骤如下:

  • 清除泛型定义中的Type parameter:分2步,第一删除Type parameter的定义,也就是<A> 和 <A extends Comparable<A>>; 第二步将所有的Type parameter替换成leftmost bound,如果没有bound则替换为Object。
  • 清除泛型实例中的Type argument, 例如 List<String>变成 List
  • 添加必要的桥接函数和类型转换代码(这个必要太关键了,想象不出来,编译器咋就能这么聪明!!!)

 

对照上面的图,我们一起来分析下,Type Erasure具体都做了什么:

上图原始代码中,我用3中颜色的方框进行的标识,绿框是Type parameter, 蓝框是Type Argument, 红框是我自己框的,我觉得是type parameter的定义。

  • 对于Conparable接口,被转换成了一个非泛型接口,unbounded type parameter被转换成了Object。
  • NumericValue 实现了一个非泛型的接口,编译器此处加入了一个桥接函数,就是字体加粗的那个compareTo函数,因为如果不添加这个函数,相当于NumericValue 没有实现接口。
  • max函数变成了一个普通函数, bounded type parameter A都被Comparable替换掉了, Iterator<A>变成了Iterator,并添加了类型转换代码。

 

泛型的类型系统

泛型的引入使得对象之间的关系变得更复杂了,猜测下面例子中,那个是不对的?

[code]        List<Number> a = new ArrayList<Number>();
ArrayList<Number> b = new ArrayList<Integer>();
List<? extends Number> c = new ArrayList<Integer>();
List<? super Number> d = new ArrayList<Object>();
List<? super Integer> e = d;

答案是第二行。

 

泛型实例之间是否能够认为是父子关系,我们需要从2个层面考虑:

1. 泛型的raw类型之间是否有继承关系。 (List<String>的raw类型是List)

2. 泛型实例中的Type argument是否有超集的关系

 

当两个泛型实例的Type argument相同时:例如,List<Number>  和 ArrayList<Number>, 因为List是ArrayList的父类型,所以可以认为List<Number>是ArrayList<Number>的父类型。

当两个泛型实例的Type argument不相同,而raw类型相同或具有父子关系时,如果type argument 是对方的超集时,才能说是对方的父类。  例如  List<? extends Number> 和 List<? extends Integer>,我们可以说前者是后者的父类

 

总结一下:

    A<T>  B<T>

    A<String>   B<String>:  如果A和B同类,或者A是B的父类,那么就可以说A<String> 是 B<String>的父类

    

    A<T>

    A<? extends ClassA>     A<? extends ClassB>: 如果 ? extends ClassA 包括的类的范围是 ? extends ClassB 包括的类的超集,那么可以说  A<? extends ClassA> 是 A<? extends ClassB>的父类

 

泛型数组

只记一句话吧, 我们只能为 unbounded 的泛型实例建立泛型数组,但是这样写啥意义也没有啊。

[code]List<?>[] b = new List<?>[10];

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: