对比Java学Kotlin扩展
Posted 陈蒙_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对比Java学Kotlin扩展相关的知识,希望对你有一定的参考价值。
扩展概述
以 JDK 内置的集合 ArrayList 为例,如果我们想给其添加一个能力:交互两个元素 swap(index1, index2)。我们应该怎么做?常见的有如下方式:
- 工具类+静态方法,以 ArrayList 和要交换的两个 index 为入参,操作 ArrayList 交换元素;
- 继承 ArrayList 新写一个类,在类里面创建 swap() 方法,前提是基类是可以继承的,比如 java.lang.String 就不行;
- 使用组合的方式,写一个 ArrayListWrapper,内置一个 ArrayList,对外提供 swap() 方法
其实,这几种方式都存在劣势。不管是工具类还是继承的方式还是装饰者模式的组合类,都是新建了一个类,很可能有同事以前已经创建了这个类了。但是如果你不知道新类的名字是啥,就很尴尬了。或者懒得问干脆自己新建一个,一个新的“轮子”诞生了。
如果我们在 androidStudio 里面输入 ArrayList 就能自动提示 swap 这个函数就好了,就跟原本就有这个方法一样。唉~~Kotlin 就提供了这种能力,这就是扩展。
扩展函数
继续以 swap() 方法为例。我们在 Kotlin 中使用如下格式完成,先定义 swap() 方法:
fun <T> MutableList<T>?.swap(i1: Int, i2: Int) {
if (this == null) {
return
}
val tmp = this[i1]
this[i1] = this[i2]
this[i2] = tmp
}
不失一般性的,如果我们想向某个已经存在的类 ReceiverClassType 中“添加”一个函数 func,那么我们可以写成:
ReceiverClassType.func()
如果允许接收空值,则可以写成:
ReceiverClassType?.func()
知道了定义格式,如何使用扩展函数呢?这样:
val list = mutableListOf(1, 2, 3)
list.swap(0, 2)
扩展函数在安卓开发中也有用武之地,比如我们要给原生控件 ImageView 扩展支持加载网络图片的功能,有了扩展之后我们可以这么做:
import android.widget.ImageView
import com.bumptech.glide.Glide
fun ImageView.loadImage(url: String) {
Glide.with(this).load(url).into(this)
}
再比如,我们要给原生控件 TextView 添加富文本的能力,后端网络接口返回给前端一个 JSON 数组,数组里面的每个元素代表文本中的一段子串的样式:
fun TextView.setRichText(jsonArray: String) {
val builder: SpannableStringBuilder = parse(jsonArray)
setText(builder)
}
简单了解用法之后,一大串问题就来了:
- 扩展函数的实现原理是什么?
- 扩展函数放在什么位置呢?
- 可以有可见性修饰符吗?
- 可以作为类的成员方法吗?如果可以,支持重载、重写、多态吗?
- 如果扩展函数与成员函数冲突了,谁的优先级更高呢?
- 如何从 Java 代码里面调用扩展函数呢?
实现原理
继续以上面的 swap() 方法为例,我们看下其反编译出来的 Java 代码:
public final class SwapKt {
public static final void swap(@Nullable List $this$swap, int index1, int index2) {
if ($this$swap != null) {
Object tmp = $this$swap.get(index1);
$this$swap.set(index1, $this$swap.get(index2));
$this$swap.set(index2, tmp);
}
}
}
其实就是一个工具类+static方法,并没有真的向目标类添加新的方法。
如果我们想在 Java 代码中引用扩展函数,我们需要使用 SwapKt.swap() 的形式:
import example.kotlin.pkg.SwapKt;
import java.util.ArrayList;
ArrayList<Integer> list = new ArrayList<>();
list.add(0);
list.add(1);
SwapKt.swap(list, 0 ,1);
放置位置
我们知道,kotlin 的普通函数是可以放置在单个文件里面(也就是 top-level),也可以作为成员函数放置在类体内,扩展函数也不例外。
我们能以 top-level 的形式放在单个文件 Swap.kt 中:
package example.kotlin.pkg
fun <T> MutableList<T>?.swap(index1: Int, index2: Int) {
// ...
}
扩展函数也可以作为成员函数,即将类 A 的扩展函数在类 B 中定义,我们称 A 为接收类型(Receiver Type),称 B 为分发类型(Dispatch Type)。比如下面示例代码中的 Host 就是接收类型,而 Connection 是分发类型:
class Host(val hostname: String) {
fun printHost() {
print(hostname)
}
}
class Connection(val host: Host, val port: Int) {
fun printPort() {
print(port)
}
fun Host.printConnectionString() {
host.printHost()
print(":")
printPort()
}
fun connect() {
host.printConnectionString()
}
}
fun main() {
Connection(Host("kotl.in"), 443).connect()
//Host("kotl.in").printConnectionString() // error, the extension function is unavailable outside Connection
}
既然在分发类型中定义接收类型的扩展方法,那在扩展方法中引用 this 究竟是谁呢?接收类型还是分发类型的?实际是接收类型的,如果想引用分发类型的 this,需要使用 this@DispatchType:
class Connection {
fun Host.getConnectionString() {
toString() // calls Host.toString()
this@Connection.toString() // calls Connection.toString()
}
}
既然扩展函数可以以成员函数的形式出现,那么就有成员函数的相应“待遇”:重写、重载、多态等,但这些“待遇”都是分发类型才有的,而接收类型是没有的,接收类型的函数都是静态引用,只跟接收类型有关,先来看个例子:
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) { // 只由 Shape 决定,而非其子类
println(s.getName())
}
printClassName(Rectangle()) // 输出 Shape
在来看个更复杂的:
open class Shape
class Rectangle: Shape()
open class ShapeCaller {
open fun Shape.getName() = "Shape in ShapeCaller"
open fun Rectangle.getName() = "Rectangle in ShapeCaller"
fun call(s: Shape) {
println(s.getName())
}
}
class RectangleCaller: ShapeCaller() {
override fun Shape.getName() = "Shape in RectangleCaller"
override fun Rectangle.getName() = "Rectangle in RectangleCaller"
}
fun main() {
ShapeCaller().call(Shape()) // 输出 Shape in ShapeCaller
ShapeCaller().call(Rectangle()) // 输出 Shape in ShapeCaller
RectangleCaller().call(Shape()) // 输出 Shape in RectangleCaller
RectangleCaller().call(Rectangle()) // 输出 Shape in RectangleCaller
}
由于 call(s: Shape) 里面的接收类型是 Shape,所有不能是 ShapeCaller 还是 RectangleCaller 都只会调用 Shape.getName(),但是调用父类还是子类里面的 Shape.getName() 却是符合多态的,具体决定于分发类型是 ShapeCaller 还是 RectangleCaller。
如果扩展函数和成员函数签名完全一致,那么成员函数优先级更高。
扩展属性
与扩展函数类似,Kotlin 还支持扩展属性。使用方法也类似,比如我们要给 MutableList 扩展一个本不存在的属性 lastIndex:
val <T> MutableList<T>?.lastIndex: Int
get() {
if (this == null) {
return -1
}
return size - 1
}
注意,因为扩展属性是本不存在的,因此其没有 Backing Field,我们也无法直接对其赋值,因此扩展属性只能是 val 类型的,否则会报错:
fun play() {
val list = mutableListOf<String>()
print(list.lastIndex)
list.lastIndex = 1 // 报错
// 结合扩展函数 swap() 实现交换首位元素
list.swap(0, list.lastIndex)
}
最佳实践
在实际开发中,优先使用成员函数还是扩展函数呢?答案是优先使用成员函数,只有在无法使用成员函数,比如无法修改既有类或者虽然可以修改但是想控制使用权限的时候,再使用扩展函数。
参考资料
以上是关于对比Java学Kotlin扩展的主要内容,如果未能解决你的问题,请参考以下文章