0x1、要点提炼
- 什么是泛型 →「将确定不变的类型参数化,保证集合中存储元素都是同一种」
- 泛型的好处 →「编译期类型安全检查」和「消除强类型转换,提高可读性」
- Java中泛型的使用 → 泛型类、泛型接口 和 泛型方法;
- Java假泛型实现原理 →「类型擦除(Type Erasure)」只存在与编译期,进JVM前会擦除类型
- Java有界泛型类型 → 限制允许实例化的泛型类型,「extends 父类型」
- Java通配符 → <T>与<?>的区别(定义-形参,实例化-实参)
- 上边界通配符 → <? extends 父类型>,父类型未知,只能读不能写,协变
- 下边界通配符 → <? super 子类型>,子类型未知,只能写不能读,逆变
- 无限定通配符 → <?>,等价于<? extends Object>,类型完全未知,只能读不能写,不变
- Kotlin中的型变 →「声明处型变」out(协变)与in(逆变),类型映射<*>
- Kotlin获取泛型类型 → 匿名内部类,反射,实例化类型参数代替类引用,内联函数
0x2、什么是泛型
0x3、Java中泛型的使用
泛型可用于类、接口和方法的创建,对应:泛型类,泛型接口和泛型方法,代码示例如下:
注意事项:
- 1、泛型的参数只能是「类类型」,不能是简单数据类型(int, float等);
- 2、泛型可以有多个泛型参数
Tips:泛型类型的命名不是必须为T,也可以使用其他「单个大写字母」,没有强制的命名规范,但为了便于阅读,有一些约定成俗的命名规范:
- 通用泛型类型:T,S,U,V
- 集合元素泛型类型:E
- 映射键-值泛型类型:K,V
- 数值泛型类型:N
0x4、Java假泛型实现原理
和C#中的泛型不同,Java和Kotlin中的泛型都是假泛型,实现原理就是「类型擦除(Type Erasure)」。
Java编译器在生成Java字节码中是不包含泛型中的类型信息的,只存在于代码编译阶段,进JVM前会被擦除。
不信?写个简单的代码体验下:
运行结果如下:
从输出结果可以看到获得的类型确实被擦除了,此时的「类类型」皆为ArrayList;
问 → 那我们定义的泛型类型(Integer, String)到底去哪了?
答 → 被替换成了「原始类型」(字节码中的真正类型)
问 → 那原始类型具体是什么类型?
答 → 「限定类型」,无限定的话都用Object替换。
问 → ???
答 → 且听我娓娓道来~
还是上面的代码,进Java字节码看看(View -> Show Bytecode)
的确,Integer和String都被替换成了Object,那这个「限定类型」呢?写个例子试试:
看下字节码:
行吧,此时的「原始类型」为「限定类型」即Animal类。
没限定类型的,都替换成Object类,也使得我们可以通过一些操作,绕过泛型。
比如,我们利用「反射」往Integer类型的List插入一个String值是不会报错的:
运行结果如下: