kotlin 知识点五 泛型和委托

泛型的基本用法

准确来讲,泛型并不是什么新鲜的事物。Java 早在1.5 版本中就引入了泛型的机制,Kotlin 自然

也就支持了泛型功能。但是Kotlin 中的泛型和Java 中的泛型有同有异。我们在本小节中就先学习

泛型的基本用法,也就是和Java 中相同的部分

首先解释一下什么是泛型。在一般的编程模式下,我们需要给任何一个变量指定一个具体的类

型,而泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码将会拥有更好

的扩展性。

举个例子,List是一个可以存放数据的列表,但是List并没有限制我们只能存放整型数据或字符

串数据,因为它没有指定一个具体的类型,而是使用泛型来实现的。也正是如此,我们才可以

使用List、List之类的语法来构建具体类型的列表。

那么要怎样才能定义自己的泛型实现呢?这里我们来学习一下基本的语法。

泛型主要有两种定义方式:一种是定义泛型类,另一种是定义泛型方法,使用的语法结构都是

。当然括号内的T并不是固定要求的,事实上你使用任何英文字母或单词都可以,但是通常

情况下,T是一种约定俗成的泛型写法。

如果我们要定义一个泛型类,就可以这么写:

复制代码
class MyClass<T> { 
 fun method(param: T): T { 
 return param 
 } 
} 

此时的MyClass就是一个泛型类,MyClass中的方法允许使用T类型的参数和返回值。

我们在调用MyClass类和method()方法的时候,就可以将泛型指定成具体的类型,如下所

示:

复制代码
val myClass = MyClass<Int>() 
val result = myClass.method(123) 

这里我们将MyClass类的泛型指定成Int类型,于是method()方法就可以接收一个Int类型的

参数,并且它的返回值也变成了Int类型。

而如果我们不想定义一个泛型类,只是想定义一个泛型方法,应该要怎么写呢?也很简单,只

需要将定义泛型的语法结构写在方法上面就可以了,如下所示:

复制代码
class MyClass { 
 fun <T> method(param: T): T { 
 return param 
 } 
} 

此时的调用方式也需要进行相应的调整:

复制代码
val myClass = MyClass() 
val result = myClass.method<Int>(123) 

可以看到,现在是在调用method()方法的时候指定泛型类型了。另外,Kotlin 还拥有非常出色

的类型推导机制,例如我们传入了一个Int类型的参数,它能够自动推导出泛型的类型就是Int

型,因此这里也可以直接省略泛型的指定:

复制代码
val myClass = MyClass() 
val result = myClass.method(123) 

Kotlin 还允许我们对泛型的类型进行限制。目前你可以将method()方法的泛型指定成任意类

型,但是如果这并不是你想要的话,还可以通过指定上界的方式来对泛型的类型进行约束,比

如这里将method()方法的泛型上界设置为Number类型,如下所示:

复制代码
class MyClass { 
 fun <T : Number> method(param: T): T { 
 return param 
 } 
} 

这种写法就表明,我们只能将method()方法的泛型指定成数字类型,比如Int、Float、

Double等。但是如果你指定成字符串类型,就肯定会报错,因为它不是一个数字。

另外,在默认情况下,所有的泛型都是可以指定成可空类型的,这是因为在不手动指定上界的

时候,泛型的上界默认是Any?。而如果想要让泛型的类型不可为空,只需要将泛型的上界手动

指定成Any就可以了。

接下来,我们尝试对本小节所学的泛型知识进行应用。编写了一个build函数,代码如下所示:

复制代码
fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
 block() 
 return this 
} 

这个函数的作用和apply函数基本是一样的,只是build函数只能作用在StringBuilder类上

面,而apply函数是可以作用在所有类上面的。现在我们就通过本小节所学的泛型知识对

build函数进行扩展,让它实现和apply函数完全一样的功能。

思考一下,其实并不复杂,只需要使用将build函数定义成泛型函数,再将原来所有强制指

定StringBuilder的地方都替换成T就可以了。新建一个build.kt 文件,并编写如下代码:

复制代码
fun <T> T.build(block: T.() -> Unit): T { 
 block() 
 return this 
} 

大功告成!现在你完全可以像使用apply函数一样去使用build函数了,比如说这里我们使用

build函数简化Cursor 的遍历:

复制代码
contentResolver.query(uri, null, null, null, null)?.build { 
 while (moveToNext()) { 
 ... 
 } 
 close() 
} 

好了,关于Kotlin 泛型的基本用法就介绍到这里,这部分用法和Java 中的泛型基本上没什么区

别,所以应该还是比较好理解的。接下来我们进入本节Kotlin 课堂的另一个重要主题------委托。

类委托和委托属性

委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委

托给另外一个辅助对象去处理。这个概念对于Java 程序员来讲可能相对比较陌生,因为Java 对

于委托并没有语言层级的实现,而像C# 等语言就对委托进行了原生的支持。

Kotlin 中也是支持委托功能的,并且将委托功能分为了两种:类委托和委托属性。下面我们逐个

进行学习。

首先来看类委托,它的核心思想在于将一个类的具体实现委托给另一个类去完成。在前面的章

节中,我们曾经使用过Set这种数据结构,它和List有点类似,只是它所存储的数据是无序

的,并且不能存储重复的数据。Set是一个接口,如果要使用它的话,需要使用它具体的实现

类,比如HashSet。而借助于委托模式,我们可以轻松实现一个自己的实现类。比如这里定义

一个MySet,并让它实现Set接口,代码如下所示:

复制代码
class MySet<T>(val helperSet: HashSet<T>) : Set<T> { 
 override val size: Int 
 get() = helperSet.size 
 override fun contains(element: T) = helperSet.contains(element)
 override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)
 override fun isEmpty() = helperSet.isEmpty() 
 override fun iterator() = helperSet.iterator() 
} 

可以看到,MySet的构造函数中接收了一个HashSet参数,这就相当于一个辅助对象。然后在

Set接口所有的方法实现中,我们都没有进行自己的实现,而是调用了辅助对象中相应的方法实

现,这其实就是一种委托模式。

那么,这种写法的好处是什么呢?既然都是调用辅助对象的方法实现,那还不如直接使用辅助

对象得了。这么说确实没错,但如果我们只是让大部分的方法实现调用辅助对象中的方法,少

部分的方法实现由自己来重写,甚至加入一些自己独有的方法,那么MySet就会成为一个全新

的数据结构类,这就是委托模式的意义所在。

但是这种写法也有一定的弊端,如果接口中的待实现方法比较少还好,要是有几十甚至上百个

方法的话,每个都去这样调用辅助对象中的相应方法实现,那可真是要写哭了。那么这个问题

有没有什么解决方案呢?在Java 中确实没有,但是在Kotlin 中可以通过类委托的功能来解决。

Kotlin 中委托使用的关键字是by,我们只需要在接口声明的后面使用by关键字,再接上受委托

的辅助对象,就可以免去之前所写的一大堆模板式的代码了,如下所示:

复制代码
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
} 

这两段代码实现的效果是一模一样的,但是借助了类委托的功能之后,代码明显简化了太多。

另外,如果我们要对某个方法进行重新实现,只需要单独重写那一个方法就可以了,其他的方

法仍然可以享受类委托所带来的便利,如下所示:

复制代码
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
 fun helloWorld() = println("Hello World") 
 override fun isEmpty() = false 
} 

这里我们新增了一个helloWorld()方法,并且重写了isEmpty()方法,让它永远返回

false。这当然是一种错误的做法,这里仅仅是为了演示一下而已。现在我们的MySet就成为

了一个全新的数据结构类,它不仅永远不会为空,而且还能打印helloWorld(),至于其他

Set接口中的功能,则和HashSet保持一致。这就是Kotlin 的类委托所能实现的功能。

掌握了类委托之后,接下来我们开始学习委托属性。它的基本理念也非常容易理解,真正的难

点在于如何灵活地进行应用。

类委托的核心思想是将一个类的具体实现委托给另一个类去完成,而委托属性的核心思想是将

一个属性(字段)的具体实现委托给另一个类去完成。

我们看一下委托属性的语法结构,如下所示:

复制代码
class MyClass { 
 var p by Delegate() 
} 

可以看到,这里使用by关键字连接了左边的p属性和右边的Delegate实例,这是什么意思呢?

这种写法就代表着将p属性的具体实现委托给了Delegate类去完成。当调用p属性的时候会自

动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的

setValue()方法。

因此,我们还得对Delegate类进行具体的实现才行,代码如下所示:

复制代码
class Delegate { 
 var propValue: Any? = null 
 operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
 return propValue 
 } 
 operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
 propValue = value 
 } 
} 

这是一种标准的代码实现模板,在Delegate类中我们必须实现getValue()和setValue()这

两个方法,并且都要使用operator关键字进行声明。

getValue()方法要接收两个参数:第一个参数用于声明该Delegate类的委托功能可以在什么

类中使用,这里写成MyClass表示仅可在MyClass类中使用;第二个参数KProperty<>是
Kotlin 中的一个属性操作类,可用于获取各种属性相关的值,在当前场景下用不着,但是必须在
方法参数上进行声明。另外,<
>这种泛型的写法表示你不知道或者不关心泛型的具体类型,只

是为了通过语法编译而已,有点类似于Java 中<?>的写法。至于返回值可以声明成任何类型,根

据具体的实现逻辑去写就行了,上述代码只是一种示例写法。

setValue()方法也是相似的,只不过它要接收3个参数。前两个参数和getValue()方法是相

同的,最后一个参数表示具体要赋值给委托属性的值,这个参数的类型必须和getValue()方

法返回值的类型保持一致。

整个委托属性的工作流程就是这样实现的,现在当我们给MyClass的p属性赋值时,就会调用

Delegate类的setValue()方法,当获取MyClass中p属性的值时,就会调用Delegate类的

getValue()方法。是不是很好理解?

不过,其实还存在一种情况可以不用在Delegate类中实现setValue()方法,那就是

MyClass中的p属性是使用val关键字声明的。这一点也很好理解,如果p属性是使用val关键

字声明的,那么就意味着p属性是无法在初始化之后被重新赋值的,因此也就没有必要实现

setValue()方法,只需要实现getValue()方法就可以了。

相关推荐
I'm Jie10 小时前
Gradle 多模块依赖集中管理方案,Version Catalogs 详解(Kotlin DSL)
android·java·spring boot·kotlin·gradle·maven
Java小白笔记10 小时前
BigDecimal用法示例
java·开发语言·spring boot
l1t10 小时前
Python 字符串反转方法
linux·开发语言·python
Eiceblue10 小时前
使用 Python 写入多类型数据至 Excel 文件
开发语言·python·excel
luquinn10 小时前
用canvas切图展示及标记在原图片中的位置
开发语言·前端·javascript
程序员阿鹏10 小时前
OOM是如何解决的?
java·开发语言·jvm·spring
爱潜水的小L10 小时前
自学嵌入式day37,网络编程
开发语言·网络·php
阿蒙Amon10 小时前
C#每日面试题-类和结构的区别
开发语言·c#
BoomHe10 小时前
Android 13 (API 33)开发自己的 Settings ,如何配置 WiFi BT 权限
android
Bin二叉10 小时前
南京大学cpp复习(c10——多态、操作符重载)
开发语言·c++·笔记·学习