Kotlin笔记(五):泛型基础,委托

1. 泛型

Java自1.5版本引入了泛型的概念, Kotlin自然也支持泛型,Kotlin的泛型跟Java的泛型有相同之处,也有一些特别之处.

1.1 泛型类和泛型方法

在一般的编程模式下,我们需要给任何一个变量指定一个具体的类型,而泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码将会拥有更好的扩展性。

举个例子,List是一个可以存放数据的列表,但是List并没有限制我们只能存放整型数据或字符串数据,因为它没有指定一个具体的类型,而是使用泛型来实现的。也正是如此,我们才可以使用List< Int>、List< String>之类的语法来构建具体类型的列表。

泛型主要有两种定义方式:一种是定义泛型类,另一种是定义泛型方法,使用的语法结构都是< T>。当然括号内的T并不是固定要求的,事实上你使用任何英文字母或单词都可以,但是通常情况下,T是一种约定俗成的泛型写法。如果我们要定义一个泛型类,就可以这么写:

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

而如果我们不想定义一个泛型类,只是想定义一个泛型方法,应该要怎么写呢?也很简单,只需要将定义泛型的语法结构写在方法上面就可以了,如下所示:

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

Kotlin还允许我们对泛型的类型进行限制。目前你可以将method()方法的泛型指定成任意类型,但是如果这并不是你想要的话,还可以通过指定上界的方式来对泛型的类型进行约束,比如这里将method()方法的泛型上界设置为Number类型,如下所示:

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

这种写法就表明,我们只能将method()方法的泛型指定成数字类型,比如Int、Float、Double等。但是如果你指定成字符串类型,就肯定会报错,因为它不是一个数字。

默认情况下,所有的泛型都是可以指定成可空类型的,这是因为在不手动指定上界的时候,泛型的上界默认是Any?。而如果想要让泛型的类型不可为空,只需要将泛型的上界手动指定成Any就可以了。

我们可以定义一个build()方法并通过使用泛型来实现类似apply函数一样的功能.

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

只需要使用将build函数定义成泛型函数,再将原来所有强制指定StringBuilder的地方都替换成T就可以了。新建一个build.kt文件,并编写如下代码:

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

1.2 类委托和委托属性

委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。这个概念对于Java程序员来讲可能相对比较陌生,因为Java对于委托并没有语言层级的实现,而像C#等语言就对委托进行了原生的支持。

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

类委托,它的核心思想在于将一个类的具体实现委托给另一个类去完成。类似于代理模式. 我们用HashSet来实现一个自定义的Set类.

css 复制代码
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关键字,再接上受委托的辅助对象,就可以免去之前所写的一大堆模板式的代码了,如下所示:

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

另外,如果我们要对某个方法进行重新实现,只需要单独重写那一个方法就可以了,其他的方法仍然可以享受类委托所带来的便利,如下所示:

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

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

委托属性的语法结构,如下所示:

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

这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。

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

css 复制代码
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()方法就可以了。

委托功能本身不难理解,真正的难点在于如何灵活地进行应用。

学习了Kotlin的委托功能之后,我们就可以对by lazy的工作原理进行解密了,它的基本语法结构如下:

css 复制代码
val p by lazy { ... }

现在再来看这段代码,是不是觉得更有头绪了呢?实际上,by lazy并不是连在一起的关键字,只有by才是Kotlin中的关键字,lazy在这里只是一个高阶函数而已。在lazy函数中会创建并返回一个Delegate对象,当我们调用p属性的时候,其实调用的是Delegate对象的getValue()方法,然后getValue()方法中又会调用lazy函数传入的Lambda表达式,这样表达式中的代码就可以得到执行了,并且调用p属性后得到的值就是Lambda表达式中最后一行代码的返回值。

这样看来,Kotlin的懒加载技术也并没有那么神秘,掌握了它的实现原理之后,我们也可以实现

一个自己的lazy函数。

我们首先定义了一个Later类,并将它指定成泛型类。Later的构造函数中接收一个函数类型参数,这个函数类型参数不接收任何参数,并且返回值类型就是Later类指定的泛型。接着我们在Later类中实现getValue()方法,代码如下所示:

css 复制代码
class Later<T>(val block: () -> T) {
 	var value: Any? = null
 	operator fun getValue(any: Any?, prop: KProperty<*>): T {
 		if (value == null) {
 			value = block()
 		}
 		return value as T
 	}
}

由于懒加载技术是不会对属性进行赋值的,因此这里我们就不用实现setValue()方法了。

代码写到这里,委托属性的功能就已经完成了。虽然我们可以立刻使用它,不过为了让它的用法更加类似于lazy函数,最好再定义一个顶层函数。这个函数直接写在Later.kt文件中就可以了,但是要定义在Later类的外面,因为只有不定义在任何类当中的函数才是顶层函数。代码如下所示:

css 复制代码
fun <T> later(block: () -> T) = Later(block)

我们将这个顶层函数也定义成了泛型函数,并且它也接收一个函数类型参数。这个顶层函数的作用很简单:创建Later类的实例,并将接收的函数类型参数传给Later类的构造函数。现在,我们自己编写的later懒加载函数就已经完成了,你可以直接使用它来替代之前的lazy函数,如下所示:

css 复制代码
val uriMatcher by later {
 	val matcher = UriMatcher(UriMatcher.NO_MATCH)
 	matcher.addURI(authority, "book", bookDir)
 	matcher.addURI(authority, "book/#", bookItem)
 	matcher.addURI(authority, "category", categoryDir)
 	matcher.addURI(authority, "category/#", categoryItem)
 	matcher
}

虽然我们编写了一个自己的懒加载函数,但由于简单起见,这里只是大致还原了lazy函数的基本实现原理,在一些诸如同步、空值处理等方面并没有实现得很严谨。因此,在正式的项目中,使用Kotlin内置的lazy函数才是最佳的选择。

相关推荐
xvch1 天前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
zhangphil2 天前
Android Coil ImageLoader MemoryCache设置Key与复用内存缓存,Kotlin
android·kotlin
mmsx2 天前
kotlin Java 使用ArrayList.add() ,set()前面所有值被 覆盖 的问题
android·开发语言·kotlin
lavins2 天前
android studio kotlin项目build时候提示错误 Unknown Kotlin JVM target: 21
jvm·kotlin·android studio
面向未来_2 天前
JAVA Kotlin Androd 使用String.format()格式化日期
java·开发语言·kotlin
alexhilton2 天前
选择Retrofit还是Ktor:给Android开发者的指南
android·kotlin·android jetpack
GordonH19912 天前
Kotlin 优雅的接口实现
android·java·kotlin
wangz763 天前
Android 下用kotlin写一个sqlite
android·sqlite·kotlin·jetpack compose
yzpyzp3 天前
kotlin中RxHttp的toAwaitResponse和awaitResult函数的使用
android·kotlin
帅次3 天前
Flutter 异步编程利器:Future 与 Stream 深度解析
android·flutter·ios·小程序·kotlin·webview·android-studio