当前位置: 首页>资讯 >

Kotlin 泛型:类型参数约束 世界热推荐

来源: 腾讯云 | 时间: 2023-02-26 05:00:34 |

上一篇文章讲了 Kotlin 泛型:基本使用,接下来我们再进一步了解泛型使用相关的进阶知识。


(资料图片)

本篇是 Kotlin 泛型类型参数约束的讲解,更多内容可点击链接查看。

Kotlin 泛型:基本使用Kotlin 泛型:类型参数约束

系列持续更新中,欢迎关注订阅。

为什么需要类型参数约束

在上一篇文章里,我们使用泛型定义了一个泛型列表List,使用这个列表,我们可以在使用的时候,实例化出各种具体类型的列表,比如字符串列表List、整型列表List、浮点数列表List

interface List { // 泛型接口    fun set(index: Int, obj: T) // 用于方法,下同    fun get(index: Int): T?}// 字符串列表val stringList: List = TODO()// 整型列表val intList: List = TODO()// 浮点数列表val doubleList: List = TODO()

假如我们希望实现一个泛型拓展函数,计算返回「数值类型列表」中的每一个元素的和,会发现有点棘手:因为「类型参数」T 可以是任意类型,我们根本无法编写出一个适用于「任意类型」的sum()函数。

fun  List.sum(): Double {    var total = 0.0 // 错误,类型不匹配    this.forEach {        total += it.toDouble() // 错误,无法找到 toDouble 方法    }    return total}

有的同学可能想出这样的方法:先判断元素是否数值类型,是则计算和,否则返回异常值。

fun  List.sum(): Double {    if (T !is Number) return -1.0    var total = 0.0    this.forEach {        total += it.toDouble()    }    return total}

这样虽然也解决问题,我们得到了一个看似能「强大」到能计算「所有类型的列表」的总和的拓展函数。

但实际上,这是误用:把这个函数用在一个非数值列表上实际上完全没有意义。它实际上对非数值类型不生效,但它却误导了使用者,引入了潜在问题,也失去了使用泛型的提供的很重要的一个好处:通过编译器在编译期进行类型检查,找出潜在的类型错误,进而保证程序的健壮。

什么是类型参数约束

对于上述场景,最理想的实现应该满足这些条件:

只有数值类型的列表才能调用这个拓展函数拓展函数对「类型参数」所具备的特征有必要的了解,如知道它是一个Number类型

因此,我们需要使用泛型参数约束,它能够帮我们为「类型形参」添加一些信息,也就是设置一些约束条件。

上界约束

「上界约束」可以用来达成上面的条件。

将一个类型指定为「类型形参」的「上界约束」,那么在使用具体类型作为「类型实参」时,这个具体的类型必须是这个上界约束的类型或者它的子类型。

「上界约束」是这样定义的:在类型参数名称之后,添加冒号和作为类型形参的类型。没有指定类型上界时,是这么定义的:,将Number指定为上界类型后,是这样的:

上面的例子用「上界约束」改写后是下面这样:

fun  List.sum(): Double {    var total = 0.0    this.forEach {        total += it.toDouble() // 可以调用 Number 类型的 toDouble 方法    }    return total}val stringList = listOf("a", "b", "c")stringList.sum() // 错误,找不到方法引用val intList = listOf(1, 2, 3)intList.sum() // 正确val doubleList = listOf(1.0, 2.0, 3.0)doubleList.sum() // 正确

聪明的同学可能会说,上面这个例子哪用这么麻烦,用fun List.sum(): Double不也一样能行?

真的能行吗?我的答案是不一定。如果只是简单把上面的函数签名改了,大家可以那就不行。要能行,害得结合后面将要介绍的「变型」相关知识,这里先卖个关子不作展开,等介绍到到的时候再回过头来说。

下面这个例子就比较好地演示了「上界约束」的威力:

interface Comparable {  fun compareTo(other: T): Int}class Person(val name: String, val age: Int) : Comparable {  override fun compareTo(other: Person) = age.compareTo(other.age)}fun > max(first: T, second: T): T {  return if (first.compareTo(second) > 0) first else second}val p1 = Person("Alice", 29)val p2 = Person("Bob", 31)val olderPerson = max(p1, p2) // 正确val a1 = Any()val a2 = Any()val bigger = max(a1, a2) // 错误,找不到合适的 max 方法
max函数使用上界约束,要求传入的参数的类型必须实现Comparable接口,能够用于比较同类型的数据这个上界约束保证了max只能用于实现了Comparable接口的对象同时,上界约束也让函数体在实现的时候,能知道传入对象上具有compareTo方法,可以使用这个方法进行比较由于Person类实现了Comparable接口,因此可以作为参数传入max函数但因为Any类没有实现Comparable,尝试作为参数传入max函数,编译器将识别出来,中止代码的编译。

多重约束

在实际工作中,我们面临的业务场景可能会对「类型参数」提出更多的要求,也就需要我们对添加更多的约束。

举个具体的例子:

假设我们在编写一个打印机程序,打印机用Printer类表示。

所有可打印的内容都可以通过这个打印机进行打印,满足条件的内容用Printable表示。

由于打印机是一个外部设备,数据需要传输到打印机上才能打印,因此数据需要实现Serializable接口,以便能够使用 Java 的序列化机制进行传输。

我们使用泛型类来实现打印机Printer,显然这个类型参数需要满足两个条件:

T必须实现Printable接口T必须实现Serializable接口

这两个条件无法用前一节的简单类型参数约束来表达,因此 Kotlin 引入了「多重约束」。「多重约束」可以让在一个类型参数上指定多个约束,它使用where语法来表达:

interface Printable {    fun getContent(): ByteBuffer}class Printer()    where T : Printable, T : Serializable {         fun print(doucument: T) {        // 编写具体实现,如先发送,再打印 etc...    }}

同样的,聪明的同学可能会想,哪用这么复杂,让Printable接口继承自Serializable接口不就行了?雀实,在这个例子里能这么做,因为它是个简单的例子,而且业务场景也很单一。

但如果我们是打印机厂商,我们有不同型号的打印机,有的是作为外设连接到电脑使用,提供的配套程序运行在电脑上(因此需要序列化传输数据),而有的是打印机自带打印控制程序,程序运行在打印机上(因此不需要序列化传输数据)。

那么我们在编写这些设备程序时,就不应将PrintableSerializable耦合在一起,原因很简单:PrintableSerializable本身就没有强关联。这也是面向对象编程的一个很重要的原则的体现:多用组合,少用继承。

另外,由于 Kotllin 的继承关系是单继承,如果我们新增的打印机,要求被打印的数据满足另一个维度的特性,那么我们不仅需要新增一个接口继承自Printable,还需要修改所有使用到Printable的类,而这将影响到所有历史代码,包括已实现的打印机。

为了新增一种设备,搞得这么轰轰烈烈,值得吗?我想 QA 同学在回归其他打印机设备的时候,心里想得肯定是给编写代码的你寄刀片吧?

利用范型约束实现非空范型

Kotlin 有一个为人称道的特性:不可空。但当我们使用范型时,这个特性在不幸的失效了。

class Box(private val instance: T) {    fun process() {        if (instance == null) {            return        }        // 正常处理    }}fun main() {    val nullableBox = Box(null) // 使用可空类型实参    val nonNullableBox = Box(Any)(Any()) // 使用非空类型实参}

在上面这个例子里,通过使用「可空的类型实参」,Box中的泛型属性也同样变得可空,这使得泛型类在具体实现的时候,需要考虑参数为空的情况,也让编写代码的具体实现变得复杂。

在 Kotlin 里,「类」和「类型」是两个不同的概念,举个例子就能很容易地理解它们的区别:

「类」是我们在代码里通过class Ainterface Bobject C这种方式定义的,在编译时,它们会转成字节码,有物质实体。「类型」则不一样,每一个「类」至少有两个「类型」,如class A会有AA?两个类型,一个非空类型,一个可空类型。这两个类型没有对应的物质实体,它们只在编译时生效,运行时并不存在。

理解了它们的区别,就能明白为什么同样是基于 JVM 字节码,Kotlin 能在 Java 的基础之上实现更严格的可空/非空特性,而 Groovy 却反其道做成了一门动态类型的语言。

本质上「类型」是一个存在于编译过程的「逻辑」概念,而「类」则是存在于字节码的「物理实体」。

回过来说范型。当我们定义一个范型类/范型函数时,由于「类型参数」在被「类型实参」替换时可使用「可空类型」和「非空类型」这两种类型,这会迫使我们在做具体实现要考虑可空类型,带来了不必要的复杂性。

解法也很简单,我们可以使用类型参数约束,要求传入的「类型实参」必须继承自Any类型,由于Any是所有非空类型的父类型,:

class Box(private val instance: T) {    fun process() {        // 不需要进行空判断        // 正常处理    }}fun main() {    val nonNullableBox = Box(Any)(Any()) // 正常编译    val nullableBox = Box(null) // 编译错误,传入类型必须是 Any 或它的子类型}

关键词: Kotlin Android 面向对象编程 移动开发

 

热文推荐

Kotlin 泛型:类型参数约束 世界热推荐

上一篇文章讲了Kotlin泛型:基本使用,接下来我们再进一步了解泛型使用相关的进阶知识。

2023-02-26

2022年乌鲁木齐跨境电商进出口额同比增长12倍

发布会现场。人民网李龙摄人民网乌鲁木齐2月24日电(李龙)“2022年,对外贸企业开展跨境电商、市场采购贸易试点等外贸新业态业务‘一对一’专

2023-02-25

dstwo使用教程_dstwo

1、DSTWO更加出色,我开始就使用的就是AK2,但后来还是换了DSTWO。2、因为AK2的游戏兼容性实在是比不过DST

2023-02-25

中国石油大学 北京中国国际能源舆情研究中心_天天看点

1、中国石油大学(北京)中国国际能源舆情研究中心成立于2012年3月31日。2、中国国际能源舆情研究中心是由中国石油

2023-02-25

民警巧用三级微信群,找回八旬走失老人获赠锦旗_播报

极目新闻记者周萍英特约通讯员白菲斐通讯员殷俊涛龚鸿杨2月23日,市民李先生来到湖北枣阳市公安局吴店派出所值班室将

2023-02-25

南宁市总工会首场职工心理健康团体辅导活动举办

据南宁市总工会微信公众号消息,2月23日上午,由南宁市总工会主办、南宁市总工会职工心理健康幸福空间承办的“克服职业倦怠享受快乐工作”2023

2023-02-25

吴权夫妇

1、吴权夫妇,2011年6月18日,曾在《伟大的诞生》中有出色表现的权梨世和DavidO一起出演了MBC人气真人秀节

2023-02-25

广汇汽车服务有限责任公司

1、广汇汽车服务有限责任公司于2006年06月02日在桂林市工商行政管理局登记成立。2、法定代表人李建平,公司经营范

2023-02-25

时尚魔女

1、《时尚魔女》是一款装扮小游戏。2、游戏大小为1971K。文章到此就分享结束,希望对大家有所帮助。

2023-02-25

今年以来全市场已发行净值型银行理财达2941只|全球微头条

今年以来全市场已发行净值型银行理财达2941只,固收类,全市场,银行理财,理财产品,理财公司

2023-02-25

英科医疗02月24日被深股通减持58.48万股

02月24日,英科医疗被深股通减持58 48万股,最新持股量为653 67万股,占公司A股总股本的0 99%。

2023-02-25

乌金丸 当前快讯

1、乌金丸的主要药物成分为香附(醋炙)。2、木香等。文章到此就分享结束,希望对大家有所帮助。

2023-02-25

2月24日基金净值:景顺长城竞争优势混合最新净值0.8863,跌1.23%-环球焦点

2月24日,景顺长城竞争优势混合最新单位净值为0 8863元,累计净值为0 8863元,较前一交易日下跌1 23%。历史数据显示该基金近1个月下跌4 18%,

2023-02-25

每日简讯:青岛建管中心造价处党支部召开一季度“先锋住建·支部圆桌会”

记者赵波通讯员安昭2月22日下午,青岛市建筑工程管理服务中心造价处党支部走进市造价协会,召开一季度“先锋住建·支部

2023-02-24

特斯拉在拉萨-25℃车坏了,客服的回应粗鲁且不要脸 环球百事通

近期有特斯拉几个网友开的特斯拉在拉萨零下25℃,车坏了,零下二十五度空调坏了,这是多么令人窒息的事情,于是和客服打了电话,客服的回应粗

2023-02-24

陈胤妃 全球播资讯

1、陈胤妃,出生于四川省,MTV双语主持人,中国内地女演员。2、2012年,在MTV全球音乐频道主持人大赛中获得冠军,成

2023-02-24

推动农业农村经济持续向好具体详细内容是什么

推动农业农村经济持续向好今天的热度非常高,现在也是在热搜榜上了,那么具体的推动农业农村经济持续向好是什么情况呢,大家可以

2023-02-24

dnf手游怎么充值_韩版dnf手游怎么充值 全球今头条

1、dnf手游如何充值?2、打开微信,如下图,进入腾讯充值微信官方账号,看地下城与勇士(PC游戏),看到入口。3、点击进

2023-02-24

云谷索道

1、云谷新索道始坐落于黄山内,建成于2006年12月,2007年9月投入试运营。2、全长2666米,是世界最先进的单

2023-02-24

英雄连勇气传说序列码_英雄连勇气传说序列号

1、英雄连之勇气传说序列号用法:进入游戏后1 首先-入侵诺曼底输入CDKEY3438-fdf3-fd15-7fa6-94

2023-02-24