Kotlin-超牛B的操作符重载+中缀符

322 阅读6分钟

第十一讲 操作符重载&中缀表达式

前介

CC++ 中是有操作符重载的概念的。在 Java 中我们认知的 +-*/ 只能在数字类型中做计算使用。而在 Kotlin 中我们可以通过操作符重载的形式改变其含义。

重载操作符的运用

其实在 Kotlin 中很多类型都已帮我们默认定义了一些操作符重载,为我们后续开发带来极大的遍历。我们可以重载一些常见的操作符,如 +-*/ 让我们代码可读性更强。

曾经我接到一个任务,将一段 Python 代码翻译成 Kotlin 代码,Python中可以对矩阵进行+*/的运算,而 Kotlin 中只有对应的方法 plus times div 的方法,我就是通过 Kotlin 的操作符重载的功能,让其翻译的最终代码和 Python 写法上尽量保持一致。

数组相加

我定义了 2 个不可变数组,然后通过 +2 个数组合并了,在 Java 的世界中,是不存在数组相加的情况。

fun main() {
    /**
     * 创建2个数组
     */
    val array1 = arrayOf("1", "2", "3")
    val array2 = arrayOf("4", "5", "6")

    val array3 = array1 + array2
    /**
     * 遍历输出
     */
    array3.forEach(::println)
}

输出结果:
1
2
3
4
5
6

List集合相加减

上面看了数组相加,集合可以不可以呢?想一想,当然是可以的啦!我们来看下吧!

fun main() {
    /**
     * 创建2个可变集合
     */
    val list1 = mutableListOf<String>("1", "2", "3")
    val list2 = mutableListOf("1", "5", "6")

    /**
     * 集合相加
     */
    val list3 = list1 + list2
    /**
     * 遍历输出
     */
    list3.forEach(::println)


    println("========减法===========")
    /**
     * 集合相减
     */
    val minusResult = list1 - list2
    /**
     * 遍历输出减法
     */
    minusResult.forEach(::println)
}

输出结果:
1
2
3
1
5
6
========减法===========
2
3

从结果可以看出,相加是合并 2List 集合。相减含义是去除减数在被减数中存在的元素。

Map 也是支持相加减的,有兴趣的可以自己去尝试。当然 Kotlin 提供的操作符重载方法还有很多,我只是随便举了个例子,其他的大家自行挖掘吧。

如何自定义重载操作符

如果想重载操作符,只需要通过关键词 operator 修饰对应方法即可,并且重载的操作符对应一个固定昵称的方法。

举个🌰,如果我想为我自己定义的对象重载 + 的操作符,只需要通过 operator 修饰 plus 方法即可(这个 plus 是固定哦,+ 操作符对应的就是 plus 方法)。

/**
 * 程序员实体
 */
data class CodePeople(val name: String, val age: Int)

/**
 * UI实体
 */
data class UiPeople(val name: String, val age: Int)

/**
 * 团队
 */
data class Tream(
    val codes: MutableList<CodePeople> = mutableListOf()
    , val uis: MutableList<UiPeople> = mutableListOf()
) {
    /**
     * 重载 + 团队,增加程序员方法
     */
    operator fun plus(code: CodePeople) {
        codes + code
    }

    /**
     * 重载 + 团队,增加 UI 方法
     */
    operator fun plus(ui: UiPeople) {
        uis + ui
    }
}

/**
 * 测试
 */
fun main() {
    val tream = Tream()
    /**
     * 可以看到可以调用 + 了
     */
    tream + CodePeople("阿文", 18)
    tream + UiPeople("小丽", 18)
}

通过上面的代码,我们可以注意到,在 Tream 类中重载了 2plus 方法,对应的行参就是要被加的类型。其实看到这里大家应该能猜想到,重载操作符的实现原理。其实就是 Kotlin 编译器,编译时将 + 替换成调用 plus 方法。

其实我上面的例子并不是最佳写法。一般来说,重载操作符都以扩展函数的形式去定义。这样我们不光可以为自己的类做重载操作符操作,也可以为系统的类做重载操作符操作。

举个例子:

/**
 * andorid viewGroup 没有 + 操作符的的含义
 * 通过扩展函数的形式编写重载操作符
 */
operator fun ViewGroup.plus(view: View): ViewGroup {
    addView(view)
    return this
}

fun test(context: Context) {
    val frameLayout = FrameLayout(context)
    val textView = TextView(context)
    val textView2 = TextView(context)
    /**
     * 可以连续为 ViewGroup 对象,通过 + 的方式,添加 View
     */
    frameLayout + textView + textView2
}

可重载的操作符

看了上面的重载操作符是不是感觉很方便呀?我们只需要通过 operator 修饰对应的方法即可。那么对应哪些操作符可以重载呢?对应的方法名又是什么呢?接下来统计下可以重载的函数。

一元操作符

操作符 对应重载函数
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
a-- a.dec()

二元操作符

操作符 对应重载函数
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)、 a.mod(b) (已弃用)
a..b a.rangeTo(b)

in操作符

操作符 对应重载函数
a in b b.contains(a)
a !in b !b.contains(a)

索引操作符

操作符 对应重载函数
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, ……, i_n] a.get(i_1, ……, i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, ……, i_n] = b a.set(i_1, ……, i_n, b)

调用操作符

操作符 对应重载函数
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, ……, i_n) a.invoke(i_1, ……, i_n)

广义赋值

操作符 对应重载函数
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b), a.modAssign(b)(已弃用)

相等与不等操作符

操作符 对应重载函数
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

比较操作符

操作符 对应重载函数
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

中缀表达式

上面说过重载操作符,都是重载已经存在的操作符。那我们能不能类似自定义一个操作符呢?比如将一些无意义的关键词变的有意义?中缀表达式可以完成这个功能。

中缀表达式的运用

在说中缀表达式之前,我们回顾下集合篇的 Map。我们在定义可变 Map 的时候,定义时初始化元素使用了 to,这个 to 是什么意思呢?

fun main() {
    val results = mutableMapOf<String, Int>("阿文" to 18, "小丽" to 18)
}

其实这里面的 to 就是一个中缀表达式,将 to 关键词从无意义变成了有意义。

看下 to 的定义,可以看到为任意类型增加了一个 to 的扩展方法,且方法通过 infix 修饰,返回 Pair 对象。mutableMapOf 可以将 Pair 类型的对象增加成元素(其实就是 keyvalue 结构)。

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

自定义中缀表达式

通过前面的 to 讲解。我们知道了中缀表达式只需要通过 infix 修饰函数即可(一般来说,中缀表达式也是通过扩展函数的形式去创建)。

我们来举个例子。

/**
 * 程序员实体
 */
data class CodePeople(val name: String, val age: Int)

/**
 * UI实体
 */
data class UiPeople(val name: String, val age: Int)

/**
 * 团队
 */
data class Tream(
    val codes: MutableList<CodePeople> = mutableListOf()
    , val uis: MutableList<UiPeople> = mutableListOf()
)
/**
 * 通过扩展函数
 * 中缀表达式 merge 合并2个Tream
 */
infix fun Tream.merge(tram: Tream) {
    /**
     * 复习下操作符重载
     */
    codes + tram.codes
    uis + tram.codes
}

/**
 * 测试
 */
fun main() {
    val tream1 = Tream()
    val tream2 = Tream()
    /**
     * 合并
     */
    tream1 merge tream2
}