Kotlin刨根问底(三):你真的懂泛型,会用吗?
2020/2/20 23:05:53
本文主要是介绍Kotlin刨根问底(三):你真的懂泛型,会用吗?,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本文灵感来源:群友提出泛型相关的问题,感觉很多人对泛型并不是很了解~
Kotlin中的泛型和Java中的泛型其实大同小异,只是语法稍微有些不同。
大部分内容摘取自:《Kotlin实用指南》,感兴趣的可以订阅一波 ~
0x1、要点提炼
- 什么是泛型 →「将确定不变的类型参数化,保证集合中存储元素都是同一种」
- 泛型的好处 →「编译期类型安全检查」和「消除强类型转换,提高可读性」
- Java中泛型的使用 → 泛型类、泛型接口 和 泛型方法;
- Java假泛型实现原理 →「类型擦除(Type Erasure)」只存在与编译期,进JVM前会擦除类型
- Java有界泛型类型 → 限制允许实例化的泛型类型,「extends 父类型」
- Java通配符 → <T>与<?>的区别(定义-形参,实例化-实参)
- 上边界通配符 → <? extends 父类型>,父类型未知,只能读不能写,协变
- 下边界通配符 → <? super 子类型>,子类型未知,只能写不能读,逆变
- 无限定通配符 → <?>,等价于<? extends Object>,类型完全未知,只能读不能写,不变
- Kotlin中的型变 →「声明处型变」out(协变)与in(逆变),类型映射<*>
- Kotlin获取泛型类型 → 匿名内部类,反射,实例化类型参数代替类引用,内联函数
0x2、什么是泛型
![](/upload/202002/20/202002202305518259.png)
0x3、Java中泛型的使用
泛型可用于类、接口和方法的创建,对应:泛型类,泛型接口和泛型方法,代码示例如下:
![](/upload/202002/20/202002202305518494.png)
注意事项:
- 1、泛型的参数只能是「类类型」,不能是简单数据类型(int, float等);
- 2、泛型可以有多个泛型参数
Tips:泛型类型的命名不是必须为T,也可以使用其他「单个大写字母」,没有强制的命名规范,但为了便于阅读,有一些约定成俗的命名规范:
- 通用泛型类型:T,S,U,V
- 集合元素泛型类型:E
- 映射键-值泛型类型:K,V
- 数值泛型类型:N
0x4、Java假泛型实现原理
和C#中的泛型不同,Java和Kotlin中的泛型都是假泛型,实现原理就是「类型擦除(Type Erasure)」。
Java编译器在生成Java字节码中是不包含泛型中的类型信息的,只存在于代码编译阶段,进JVM前会被擦除。
不信?写个简单的代码体验下:
![](/upload/202002/20/202002202305519451.png)
运行结果如下:
![](/upload/202002/20/202002202305520291.png)
从输出结果可以看到获得的类型确实被擦除了,此时的「类类型」皆为ArrayList;
问 → 那我们定义的泛型类型(Integer, String)到底去哪了?
答 → 被替换成了「原始类型」(字节码中的真正类型)
问 → 那原始类型具体是什么类型?
答 → 「限定类型」,无限定的话都用Object替换。
问 → ???
答 → 且听我娓娓道来~
还是上面的代码,进Java字节码看看(View -> Show Bytecode)
![](/upload/202002/20/202002202305521062.png)
的确,Integer和String都被替换成了Object,那这个「限定类型」呢?写个例子试试:
![](/upload/202002/20/202002202305521619.png)
看下字节码:
![](/upload/202002/20/202002202305522175.png)
行吧,此时的「原始类型」为「限定类型」即Animal类。
没限定类型的,都替换成Object类,也使得我们可以通过一些操作,绕过泛型。
比如,我们利用「反射」往Integer类型的List插入一个String值是不会报错的:
![](/upload/202002/20/202002202305523230.png)
运行结果如下:
0x5、Java有界泛型类型
如果不对泛型类型做限制,泛型类型可实例化为任意类型的话,可能会产生某些安全隐患。为了限制允许实例化的泛型类型,可在泛型类型后追加 extends 父类型,代码示例如下:
![](/upload/202002/20/202002202305524597.png)
有界泛型类型在一定程度上限制了泛型类型,提高了程序的安全性;因为定义了边界,所以可以调用父类或父接口的方法。
0x6、Java通配符
Java泛型本身「不变」的,不支持「协变」和「逆变」,是通过通配符(?)来实现的
① <T>与<?>的区别
<T>「泛型标识符」用于泛型「定义时」可理解为「形参」;
<?>「通配符」用于泛型「实例化时」可理解为「实参」。
代码示例如下:
![](/upload/202002/20/202002202305525710.png)
② 各种通配符
- 上边界通配符<? extends 父类型>
实例化时可确定为「父类型的未知类型」故「只能读不能写」,从而使得类型是「协变的」。
代码示例如下:
![](/upload/202002/20/202002202305526052.png)
- 下边界通配符:<? super 子类型>
实例化时可确定为「子类型的未知类型」故「只能写不能读」从而使得类型是「逆变的」。
(不能读指的是不能按照泛型类型读取)<代码示例如下:
![](/upload/202002/20/202002202305528024.png)
上边界通配符只能读不能写,改为下边界通配符
![](/upload/202002/20/202002202305528513.png)
- 无限定通配符:<?>
等同于上边界通配符<? extends Object>,「类型完全未知」故「只能读不能写」从而使得类型是「不变的」。
Tips:看完这里,估计有部分读者还是有点懵逼,这里总结下:
- 不变 → 子类和父类没关系,正常存取,不用通配符就好,Java集合本身就是不变的;
- 协变 → A是B的子类型,泛型<A>也是泛型<B>的子类型,只想取,用extends。
- 逆变 → A是B的子类型,泛型<B>也是泛型<A>的子类型,只想存,用super。
- PECS法则(Producer Extends,Consumer Super) 参数化类型是一个生产者,则使用:<? extends T> 如果它是一个消费者,则使用<? super T>
0x7、Kotlin中的型变
和Java泛型一样,Kotlin中的泛型也是「不变的」,没有「通配符类型」,但有两个其他的东西:「声明处型变」(declaration-site variance) 与 「类型投影」(type projections)
① 声明处型变
其实就是用「out」和「in」关键字来替换
- 「out」协变,等同于Java中的<? extends>,能读不能写,代码示例如下:
![](/upload/202002/20/202002202305528933.png)
- 「in」逆变,等同于Java中的<? super>,能写不能读,代码示例如下:
![](/upload/202002/20/202002202305530007.png)
② 类型投影
其实就是对应Java中的*通配符:
- Java中<?>等同于<* extends Object>
- Kotlin中<*>等同于out Any
0x7、Kotlin获取泛型类型
在Kotlin中可以通过下述四种方法获取泛型的类型(前两种Java也适用):
① 匿名内部类
原理:匿名内部类的声明在编译时进行,实例化在运行时进行。代码示例如下:
![](/upload/202002/20/202002202305531178.png)
② 反射
获取运行时泛型参数类型,子类可获得父类泛型的具体类型。代码示例如下:
![](/upload/202002/20/202002202305532673.png)
③ 实例化类型参数代替类引用
定义一个扩展函数用于启动Activity,代码示例如下:
fun <T: Activity> FragmentActivity.startActivity(context: Context, clazz: Class<T>) { startActivity(Intent(context, clazz)) } // 调用 startActivity(context, MainActivity::class.java) 复制代码
④ 内联函数
Kotlin中使用「inline」关键字定义一个内联函数,配合「reified」具体化(类型不擦除),得到使用泛型类型的Class。修改后的代码示例如下:
inline fun <reified T : Activity> Activity.startActivity(context: Context) { startActivity(Intent(context, T::class.java)) } // 调用 startActivity<MainActivity>(context) 复制代码
![](/upload/202002/20/202002202305533053.png)
参考文献:
这篇关于Kotlin刨根问底(三):你真的懂泛型,会用吗?的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-01-18android.permission.read_media_video
- 2024-01-18android_getaddrinfo failed eai_nodata
- 2024-01-18androidmo
- 2024-01-15Android下三种离屏渲染技术
- 2024-01-09Android 蓝牙使用
- 2024-01-06Android对接华为AI - 文本识别
- 2023-11-15代码安全之代码混淆及加固(Android)
- 2023-11-10简述Android语音播报TTS
- 2023-11-06Android WiFi工具类
- 2023-07-22Android开发未来的出路