Kotlin学习系列之:运算符重载(operator overloading)

x33g5p2x  于2022-03-08 转载在 其他  
字(3.1k)|赞(0)|评价(0)|浏览(562)

1.引入:运算符重载,最初接触到这个概念是在C++里,Java中是没有的,取而代之的是通过特定接口来实现,比如进行排序比较大小时,我们可以实现Comparable接口。而Kotlin中,又重新支持该特性,因为这样会显得更加直观。

2.如何实现运算符重载:

  • 方式一:在类内定义,以成员方法(member)的形式

比如,我们定义一个Point类:

  1. data class Point(val x: Int, val y: Int)

为了方便,这里将其声明为data class(data class的介绍请参看系列中此篇)。现在我们要支持对两个Point对象的使用运算符+实现相加功能,我们可以如何实现呢?

  1. package com.xlh.kotlin.convention
  2. data class Point(val x: Int, val y: Int) {
  3. /**
  4. * 方式一:通过member的形式
  5. * 1. operator 关键字 :运算符重载中必须使用该关键字
  6. * 2. 函数名约定
  7. */
  8. operator fun plus(o: Point): Point {
  9. return Point(x + o.x, y + o.y)
  10. }
  11. }
  12. fun main(){
  13. val p1 = Point(1, 2)
  14. val p2 = Point(3, 4)
  15. println(p1 + p2) //打印 Point(x=4, y=6)
  16. }

正如我在注释里标注的一样,这里我们只需要注意两点:

  • operator关键字: 表明该方法用于运算符重载
  • 方法名:方法名是固定的,如果想要重载运算符+,则方法名必须是plus;如果想要重载运算符-,则方法名必须是minus。详情参看下表:
operatorfunction 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)的方式实现运算符重载
  1. operator fun Point.minus(other: Point): Point{
  2. return Point(x - other.x, y - other.y)
  3. }
  4. fun main(){
  5. val p1 = Point(1, 2)
  6. val p2 = Point(3, 4)
  7. println(p2 - p1) // 打印Point(x=2, y=2)
  8. }

3.在Java中如何调用:

  1. public static void main(String[] args) {
  2. Point p1 = new Point(10, 20);
  3. Point p2 = new Point(20, 30);
  4. // member
  5. Point plusResult = p1.plus(p2);
  6. // extension
  7. Point minusResult = PointKt.minus(p2, p1);
  8. System.out.println("p1 + p2 = " + plusResult);
  9. System.out.println("p2 - p1 = " + minusResult);
  10. }

4.单向运算符重载不支持交换律.

  1. operator fun Point.times(scale: Double): Point{
  2. return Point((x * scale).toInt(), (y * scale).toInt())
  3. }

对于Point,我们这里重载了*,于是我们可以这样去使用:

  1. val p1 = Point(1, 2)
  2. println(p1 * 2.0) //输出Point(x=2, y=4)

但是,我们不可以写成2.0 * p1,这样编译不会通过。如果要想使用2.0 * p1这种形式,那么我们必须相对地去实现Double.times()

  1. operator fun Double.times(p: Point): Point {
  2. return Point((this * p.x).toInt(), (this * p.y).toInt())
  3. }

5.返回类型可以和参与运算的左右两边类型都不相同。例如:

  1. operator fun Char.times(count: Int): String{
  2. return toString().repeat(count)
  3. }

于是我们可以有:

  1. println('a' * 3) //输出aaa

这里,Char * Int 最终可以得倒一个String类型

6.前面我们所列举的例子都是二元运算符的重载,下面我们来看看一元运算符的重载。

operatorfunction name
+aunaryPlus
-aunaryMinus
!anot
a, ainc
--a, a--dec

我们就以自增运算符为例:

  1. operator fun BigDecimal.inc() = this + BigDecimal.ONE
  2. var n1 = BigDecimal.ONE
  3. println(n1++) //打印1
  4. println(++n1) //打印3

注意:对于需要进行自增或自减的变量必须是var声明的,也很好理解,自增或自减的过程是会修改变量的值的,声明为val自然不合适

7.比较运算符重载

  • ==:equals()。这个大家应该很熟悉了(结构性相等的比较),我们这里只补充一点,大家有没有想过这样的一个问题,如果左边的值为null,会不会空指针呢?答案是不会的,因为a == b,相当于:a?.equals(b) ?: (b == null)
  • === : 这个不允许被重载(引用性相等的比较)
  • 、<、>=、<= : 这类常用用在集合或数组的排序当中,或者是求最值。我们可以定义一个Student类:

  1. class Student(val firstName: String, val lastName: String) : Comparable<Student> {
  2. override fun compareTo(other: Student): Int {
  3. return compareValuesBy(this, other, Student::lastName, Student::firstName)
  4. }
  5. }

于是可以有:

  1. val p1 = Student("san", "zhang")
  2. val p2 = Student("si", "li")
  3. println(p1 > p2) //true

如果你足够细心,你会发现这里的compareTo并没有使用operator关键字修饰,为什么呢?因为在父接口Comparable中,已经使用了operator:

  1. public interface Comparable<in T> {
  2. /**
  3. * Compares this object with the specified object for order. Returns zero if this object is equal
  4. * to the specified [other] object, a negative number if it's less than [other], or a positive number
  5. * if it's greater than [other].
  6. */
  7. public operator fun compareTo(other: T): Int
  8. }

参考文献:《Kotlin in action》

相关文章