【Kotlin学习】泛型——泛型类型参数、擦除和实化类型参数

泛型类型参数

泛型允许你定义带类型形参的类型,当这种类型的实例被创建出来的时候,类型形参被替换成称为类型实参的具体类型。比如有一个list类型的变量,弄清它能存储哪种事物是很有意义的。类型形参可以准确地进行描述,和一般类型一样,kotlin编译器也能推导出类型实参

在这里插入图片描述

因为传给listOf的值是字符串,编译器推导出你在创建一个List<String>若你想创建一个空列表,此时编译器无法推导,所以要显式声明类型。就创建列表来说,可以选择在变量声明中说明泛型的类型,也可以在创建列表的函数中说明类型实参

在这里插入图片描述

泛型函数和属性

若希望一个使用列表的函数在任何列表上都能使用,需要编写一个泛型函数,泛型函数有它自己的类型实参,这些类型实参在每次函数调用时都必须替换成具体的类型实参

在这里插入图片描述

第一个<T>是类型形参,第二个<T>是接收者的类型形参,第三个<T>是返回类型的类型形参。当你在一个具体的列表上调用这个函数时可以显式指定类型实参,但大部分情况编译器会帮你推导出来

在这里插入图片描述

这个例子钟自动生成的lambda参数it的类型是String。编译器必须把它推导出来,在函数声明中lambda参数是泛型类型T(即(T)->Boolean函数的参数类型)。编译器推断T就是String,因为它直到函数应该在List上调用,而它的接收者readers的真实类型是List<String>

可以为类或接口的方法、顶层函数,以及扩展函数声明类型参数,还可以用同样的语法声明泛型的扩展属性

在这里插入图片描述

不能声明泛型非扩展属性

普通(非扩展)属性不能拥有类型参数,不能在一个类的属性中存储多个不同类型的值,因此声明泛型非扩展函数没有任何意义

声明泛型类

kotlin通过在类名称后加上一对尖括号,并把类型参数放在尖括号内来声明泛型类及泛型接口。一旦声明后就可以在类的主体内像其他类型一样使用函数参数

在这里插入图片描述

若你的类继承了泛型类或泛型接口,你就得为基础类型的泛型形参提供一个类型实参,它可以是具体类型或者另一个类型形参

在这里插入图片描述

StringList类被声明成只能包含String元素,所以它使用String作为基础类型的类型实参。而ArrayList类定义乐它自己的类型参数T并把它指定为父类的类型实参。注意ArrayList<T>中的T和List<T>中的T不一样,它是全新的类型形参,不必保留一样的名称。一个类甚至可以把它自己作为类型实参引用,比如实现Comparable接口的类

类型参数约束

类型参数约束可以限制作为(泛型)类和(泛型)函数的类型实参的类型。比如计算列表元素之和能用在List<Int>List<Double>上,但不能用在List<String>中,此时可以定义一个类型参数约束,说明sum的类型形参必须是数字

若你把一个类型指定为泛型类型形参的上界约束,在泛型类型具体的初始化中,其对应的类型实参就必须是这个具体类型或者它的子类型

定义约束的方法

冒号放在类型参数名称之后,作为类型形参上界的类型紧随其后

在这里插入图片描述

一旦指定了类型形参T的上界,你就可以把类型T的值当作它的上界的值使用

在这里插入图片描述

T的上界是泛型类型Comparable<T>。String类继承了Comparable<String>,这样使得String变成max函数的有效类型实参

需要在一个类型参数上指定多个约束,语法稍微有点不同

在这里插入图片描述

让类型形参非空

若你声明的是泛型类或泛型函数,任何类型实参,包括那些可空的类型实参都可以替换它的类型形参。没有指定上界的类型形参将会使用Any?这个默认的上界。如果你想保证替换类型形参的始终是非空类型,可以通过指定一个约束来实现。如果你除了可空性之外没有任何限制,可以使用Any代替默认的Any?作为上界

在这里插入图片描述

可以通过指定任意非空类型作为上界来让类型参数非空

运行时的泛型:擦除和实化类型参数

运行时的泛型:类型检查和转换

kotlin的泛型在运行时和java一样也被擦除了,意味着泛型类实例不会携带用于创建它的类型实参的信息。因为类型实参没有被存储下来,所以你不能检查它们,在is检查中不可能使用类型实参中的类型:if(value is List<String>),这一语句不会编译

擦除泛型类型信息的好处:应用程序使用的内存总量较小,因为要保存在内存中的类型信息更少

kotlin不允许使用没有指定类型实参的泛型类型。若你想检查一个值是否是列表而不是set或其他对象,可以使用特殊的星号投影法来做这种检查:if(value is List<*>)。实际上泛型类型拥有的每个类型形参都要一个*。前面的例子中检查了value是否是list,并没有得到关于它的元素类型的任何信息

注意在asas?转换中仍然可以使用一般的泛型类型。但如果该类有正确的基础类型但类型实参是错误的,转换也不会失败,因为在运行时转换发生的时候类型实参是未知的,这样的转换会导致编译器发出“unchecked cast”的警告

在这里插入图片描述

如果传入一个set,则会报IllegalArgumentException,如果传入一个list则会报ClassCastException。在字符串上列表上调用该函数,得到的不是IllegalArgumentException,因为你没办法判断实参是不是一个List<Int>,因此类型转换会成功。但在函数执行期间异常抛出了,因为sum函数试着从列表中读取Number值然后把它们加在一起,把String当Number用的尝试会导致类型转换异常

声明带实化类型参数的函数

内联函数的类型实参能被实化,因此你可以在运行时引用实际的类型实参

在这里插入图片描述

把函数声明成inline以及用reified标记类型参数,就可以用该函数检查value是否是T的实例

例子:filterIsInstance

在这里插入图片描述

这种情况下,类型实参在运行时是已知的,该函数检查列表中的值是不是指定为该类型实参的类的实例

为什么实化只对内联函数有效

编译器把实现内联函数的字节码插入每一次调用发生的地方,每次你调用带实化类型参数的函数时,编译器都知道这次特定调用中用作类型实参的确切类型,也就是直接把T替换成了某个具体类型。这种类型的函数不能在java代码中调用

使用实化类型参数代替类引用

另一种使用场景是为了接收java.lang.Class类型参数的API构造适配器

例子:JDK中的ServiceLoader,它接收一个代表接口或抽象类的java.lang.Class,并返回实现了该接口的或继承了该抽象类的类的实例

在这里插入图片描述

::class.java展现了如何获取其对应的kotlin类,这和java中的Service.class是完全等同的

简化

在这里插入图片描述

简化startActivity参数

在这里插入图片描述

实化类型参数的限制

能使用实化类型参数的场景

1.类型检查和类型转换
2.使用kotlin反射API
3.获取相应的java.lang.Class
4.作为调用其他函数的类型实参

不能使用的场景

1.创建指定为类型参数的类的实例
2.调用类型参数类的伴生对象的方法
3.调用带实化类型参数函数的时候使用非实化类型形参作为类型实参
4.把类、属性或者非内联函数的类型参数标记成reified

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值