前言
操作符在Java中我们也叫运算符,操作符重载是Kotlin语法糖中一个比较有趣的内容。为什么说它有趣呢?因为它可以实现让两个对象相加或者相减等操作。在Java语言中我们常见的操作符有+、-、*、/、++、--、%等。通常我们都是使用这些运算符来操作基本数据类型或字符串。对两个数字进行相加就是求这两个数字之和,对两个字符串相加就是将这两个字符串拼接起来。而在Kotlin中它允许我们对这些操作符进行重载,从而实现一些更加简洁而实用的功能。
1.operator关键字
在Kotlin中要重载一个操作符,我们使用关键字operator。而实现一个操作符的重载我们使用函数来完成,每一个可以重载的操作符都有一个固定的函数名与之对应。我们只要在相应的函数前加上operator关键字即可。比如说减操作符对应固定的函数名为minus,加操作符对应的函数名为plus。如下示例代码:
data class Weather(var temperature: Float) {
// 重载减运算符
operator fun minus(weather: Weather) : Weather {
return Weather(this.temperature - weather.temperature)
}
}
fun main() {
val lastDay = Weather(15.5f)
val today = Weather(21.5f)
val temperatureInterval = today - lastDay
println("今天和昨天的温度相差:${temperatureInterval.temperature}")
}
// 输出
今天和昨天的温度相差:6.0
我们声明了一个天气的数据类Weather,并在其主构造函数中声明了一个temperature的属性。在Weather的内部使用minus函数重载了减操作符。这样我们就可以让两个Weather对象进行相减的操作。关于操作符重载我们有两点需要注意:
- 1.operator关键字和操作符重载对应的函数名是固定的,不可更改
- 2.重载函数的参数和返回值是可变的,这个需要我们根据实际的需求来
比如上面我们给minus函数添加了一个Weather类型的参数,并且让minus函数的返回值也是Weather类型。我们在调用的时候其实就是类似obj3 = obj1 - obj2。如果我们想让一个天气对象和一个具体的温度值相减,然后再返回另外一个天气对象,我们就可以这么写:
data class Weather(var temperature: Float) {
operator fun minus(temperature: Float) : Weather {
return Weather(this.temperature - temperature)
}
}
fun main() {
val temperature = 15.5f
val today = Weather(21.5f)
val temperatureInterval = today - temperature
println("温度相差:${temperatureInterval.temperature}")
}
// 输出
温度相差:6.0
看到这里我想你应该能明白操作符重载的真正含义了。其实它就是Kotlin给我们提供的一个语法糖,在反编译成Java代码的时候,就是该对象调用其对应的函数。比如第一个代码示例就是today.minus(lastDay),第二个代码示例就是today.minus(temperature)。当然我们也可以在代码里直接这么调用,只不过直接调用就体现不了操作符重载的乐趣了。为了方便理解,这里贴出第一个代码示例的反编译结果:
public static final void main() {
Weather lastDay = new Weather(15.5F);
Weather today = new Weather(21.5F);
Weather temperatureInterval = today.minus(lastDay);
String var3 = "今天和昨天的温度相差:" + temperatureInterval.getTemperature();
System.out.println(var3);
}
2.调用操作符()
介绍到操作符重载,我想这里有必要将调用操作符单独拿出来介绍下。因为在Kotlin中我们在对一个函数类型的实例调用的时候,我们经常两种写法混着来,但是却不知道其真正的原因是什么。如下示例代码:
fun normal(block: () -> Unit) {
block()
block.invoke()
}
我们在对高阶函数normal中的函数类型参数调用的时候,可以使用block(),也可以使用block.invoke()。我们知道无论在Java中还是在Kotlin中我们对一个方法的调用就是使用(),而调用操作符()在Kotlin中对应的重载函数正是invoke函数。而这才是我们可以使用两种方式来对函数类型参数实例调用的真正原因。
在高阶函数和Lambda表达式的文章中我们介绍到,其实函数类型在Java中是用接口来实现的。使用Lambda表达式来初始化一个函数类型,其实就相当于使用一个匿名类来初始化一个接口。使用Kotlin开发的读者一定可以感受到,我们在Kotlin中使用Lambda表达式初始化函数类型可以替代Java中使用匿名类创建接口的方式来完成相关的回调。在Android Studio中依次打开Tools -> Kotlin -> Show Kotlin ByteCode,在右边的弹出框中我们点击Decompile按钮,我们得到如下normal函数反编译成Java的代码:
public static final void normal(@NotNull Function0 block) {
Intrinsics.checkNotNullParameter(block, "block");
block.invoke();
block.invoke();
}
点击Function0我们就可以进入到Functions.kt文件中:
public interface Function0<out R> : Function<R> {
public operator fun invoke(): R
}
public interface Function1<in P1, out R> : Function<R> {
public operator fun invoke(p1: P1): R
}
public interface Function2<in P1, in P2, out R> : Function<R> {
public operator fun invoke(p1: P1, p2: P2): R
}
为了方便阅读,这里只贴出了前3个函数类型所对应的接口。到这里我们总算是看到了熟悉的operator关键字和invoke函数了。相信现在的你应该可以很清楚的掌握为什么函数类型的实例可以调用invoke函数了。
3.常用的操作符和对应的重载函数
在Kotlin中允许我们重载的操作符有很多,这里我把一些常用的操作符和对应的重载函数罗列出来,方便我们在使用的时候查看。
1.一元操作符
表达式 | 实际调用函数 |
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
2.递增与递减
表达式 | 实际调用函数 |
a++ | a.inc() |
a-- | a.dec() |
3.二元操作符
表达式 | 实际调用函数 |
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..b | a.rangeTo(b) |
4.in操作符
表达式 | 实际调用函数 |
a in b | b.constains(a) |
a !in b| | !b.contains(a) |
5.索引访问操作符
表达式 | 实际调用函数 |
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) |
6.调用操作符
表达式 | 实际调用函数 |
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) |
总结
操作符重载是Kotlin中一个很有趣的语法糖,我们可以通过操作符重载实现很多简洁而实用的功能。比如访问操作符[ ],让我们可以使用list[i]的方式来获取一个集合中的元素。in操作符,让我们可以使用for(value in list)的方式来遍历一个集合。+操作符使协程的上下文对象支持相加CoroutineScope(Dispatchers.Main + SupervisorJob())。
关于操作符重载的内容就介绍到这里了,下篇文章我们将继续讲解Kotlin中的基础知识泛型。我们下期再见~