1.引入:运算符重载,最初接触到这个概念是在C++里,Java中是没有的,取而代之的是通过特定接口来实现,比如进行排序比较大小时,我们可以实现Comparable接口。而Kotlin中,又重新支持该特性,因为这样会显得更加直观。
2.如何实现运算符重载:
-
方式一:在类内定义,以成员方法(member)的形式
比如,我们定义一个Point类:
data class Point(val x: Int, val y: Int)
为了方便,这里将其声明为data class(data class的介绍请参看系列中此篇)。现在我们要支持对两个Point对象的使用运算符+实现相加功能,我们可以如何实现呢?
package com.xlh.kotlin.convention data class Point(val x: Int, val y: Int) { /** * 方式一:通过member的形式 * 1. operator 关键字 :运算符重载中必须使用该关键字 * 2. 函数名约定 */ operator fun plus(o: Point): Point { return Point(x + o.x, y + o.y) } } fun main(){ val p1 = Point(1, 2) val p2 = Point(3, 4) println(p1 + p2) //打印 Point(x=4, y=6) }
正如我在注释里标注的一样,这里我们只需要注意两点:
- operator关键字: 表明该方法用于运算符重载
-
方法名:方法名是固定的,如果想要重载运算符+,则方法名必须是plus;如果想要重载运算符-,则方法名必须是minus。详情参看下表:
operator function name + plus - minus * times / div % mod
然后我们就可以使用+运算符实现两个Point对象的相加。那么运算符重载本质上是怎样的呢,下面我们通过反编译来一探究竟。
来到out/production/JavaSETest(源码在idea下编写)目录下执行:javap -c 全类名(com.xlh.kotlin.convention.PointKt)
可以看得出来,对于此处的p1 + p2,相当于p1.plus(p2),然后会生成一个新的Point对象返回。如果你对于kotlin中的另一个特性——扩展的原理熟悉的话,你会发现这块反编译的结果和它极其相似。所以,我们还可以通过扩展的方式来实现运算符重载。
-
方式二:通过扩展(extension)的方式实现运算符重载
operator fun Point.minus(other: Point): Point{ return Point(x - other.x, y - other.y) } fun main(){ val p1 = Point(1, 2) val p2 = Point(3, 4) println(p2 - p1) // 打印Point(x=2, y=2) }
3.在Java中如何调用:
public static void main(String[] args) {
Point p1 = new Point(10, 20);
Point p2 = new Point(20, 30);
// member
Point plusResult = p1.plus(p2);
// extension
Point minusResult = PointKt.minus(p2, p1);
System.out.println("p1 + p2 = " + plusResult);
System.out.println("p2 - p1 = " + minusResult);
}
4.单向运算符重载不支持交换律.
operator fun Point.times(scale: Double): Point{
return Point((x * scale).toInt(), (y * scale).toInt())
}
对于Point,我们这里重载了*,于是我们可以这样去使用:
val p1 = Point(1, 2)
println(p1 * 2.0) //输出Point(x=2, y=4)
但是,我们不可以写成2.0 * p1,这样编译不会通过。如果要想使用2.0 * p1这种形式,那么我们必须相对地去实现Double.times()
operator fun Double.times(p: Point): Point {
return Point((this * p.x).toInt(), (this * p.y).toInt())
}
5.返回类型可以和参与运算的左右两边类型都不相同。例如:
operator fun Char.times(count: Int): String{
return toString().repeat(count)
}
于是我们可以有:
println('a' * 3) //输出aaa
这里,Char * Int 最终可以得倒一个String类型
6.前面我们所列举的例子都是二元运算符的重载,下面我们来看看一元运算符的重载。
operator | function name |
---|---|
+a | unaryPlus |
-a | unaryMinus |
!a | not |
++a, a++ | inc |
--a, a-- | dec |
我们就以自增运算符为例:
operator fun BigDecimal.inc() = this + BigDecimal.ONE
var n1 = BigDecimal.ONE
println(n1++) //打印1
println(++n1) //打印3
注意:对于需要进行自增或自减的变量必须是var声明的,也很好理解,自增或自减的过程是会修改变量的值的,声明为val自然不合适
7.比较运算符重载
- ==:equals()。这个大家应该很熟悉了(结构性相等的比较),我们这里只补充一点,大家有没有想过这样的一个问题,如果左边的值为null,会不会空指针呢?答案是不会的,因为a == b,相当于:a?.equals(b) ?: (b == null)
- === : 这个不允许被重载(引用性相等的比较)
-
>、<、>=、<= : 这类常用用在集合或数组的排序当中,或者是求最值。我们可以定义一个Student类:
class Student(val firstName: String, val lastName: String) : Comparable<Student> { override fun compareTo(other: Student): Int { return compareValuesBy(this, other, Student::lastName, Student::firstName) } }
于是可以有:
val p1 = Student("san", "zhang") val p2 = Student("si", "li") println(p1 > p2) //true
如果你足够细心,你会发现这里的compareTo并没有使用operator关键字修饰,为什么呢?因为在父接口Comparable中,已经使用了operator:
public interface Comparable<in T> { /** * Compares this object with the specified object for order. Returns zero if this object is equal * to the specified [other] object, a negative number if it's less than [other], or a positive number * if it's greater than [other]. */ public operator fun compareTo(other: T): Int }
参考文献:《Kotlin in action》