Kotlin基础 - 第八章运算符重载

kotlin中的运算符重载



#### [kotlin官方文档 https://www.kotlincn.net/docs/reference/](https://www.kotlincn.net/docs/reference/) ####

内容参考《Kotlin实战》

什么是运算符重载?

简单来说,就是 Kotlin 通过调用自己代码中定义特定的函数名的函数(成员函数或者扩展函数),并且用 operator 修饰符标记,来实现特定的语言结构,例如如果你在一个类上面定义了一个特定函数命名 plus 的函数,那么按照 Kotlin 的约定,可用在这个类的实例上使用 + 运算符,下面是代码。

用于重载运算符的所有函数都必须使用 operator 关键字标记。

	data class Foo(val x: Int, val y: Int) {
	    operator fun plus(instance: Foo) = Foo(x + instance.x, y + instance.y)
	
	    override fun toString(): String {
	       return "$x + ${y}i"
	    }
	}
	
	
	fun main(args: Array) {
	    var aFoo = Foo(2, 5)
	    var otherFoo = Foo(4, 5)
	
	    println(aFoo + otherFoo)
		// 6 + 10i
	}

那么Java如何调用运算符函数呢?

重载的运算符实际上是被定义成一个函数,Java调用Kotlin运算符就跟调用普通函数一样调用就行。

重载算术运算符

算术运算符包括二元运算符、复合赋值运算符、一元运算符,当 Kotlin 在给一个集合添加元素的时候,是调用 add 方法,用到重载的话,我们就可以直接用 += 来进行这个操作,就会显得更加的优雅。。。

	//为类增加扩展函数	
	fun Any.println() = println(this)
	
	
	fun main(args: Array) {
	    val list = arrayListOf()
	    list.add("a")
	    list.println()
		//[user, admin, a]

	    list += "zbc"
		//[user, admin,a, zbc]
	    list.println()
	}

重载二元算术运算符

二元算术运算符就是常见的 +、-、*、/ 和取余 %,优先级与数学的是一样的,*、/% 要高于 +、- 的优先级。

下面我们列举对应的函数名:

表达式 函数名
a * b times
a / b div
a % b rem,mod(弃用)
a + b plus
a - b minus

下面我们来写个类,里面包含这几种函数,同时还有扩展函数的定义。

	data class Foo(val x: Int, val y: Int) {
	    operator fun plus(instance: Foo) = Foo(x + instance.x, y + instance.y)
	
	
	    operator fun div(instance: Foo) = Foo(x / instance.x, y / instance.y)
	
	    operator fun times(instance: Foo) = Foo(x * instance.x, y * instance.y)
	
	    operator fun rem(instance: Foo) = Foo(x % instance.x, y % instance.y)
	
	    operator fun minus(instance: Foo) = Foo(x  - instance.x, y -  instance.y)
	
	    override fun toString(): String {
	        return "$x + ${y}i"
	    }
	}
	
	fun Any.println() = println(this)
	
	
	fun main(args: Array) {
	    val aFoo = Foo(2, 6)
	    val otherFoo = Foo(5, 15)
		
		
	    (otherFoo + aFoo).println()		// 7 + 21i
	    (otherFoo - aFoo).println()		// 3 + 9i
	    (otherFoo * aFoo).println()		// 10 + 90i
	    (otherFoo / aFoo).println()		// 2 + 2i
	    (otherFoo % aFoo).println()		// 1 + 3i
	
	}

除了定义相同类型的运算数之外,还能定义运算数类型不同的运算符:

	data class Foo(val x: Int, val y: Int) {
	   
	    operator fun times(num: Double) = Foo( x * num.toInt(), y * num.toInt())
	
	     
	}
	
	fun Any.println() = println(this)
	
	
	fun main(args: Array) {
	    
		val otherFoo = Foo(5, 15)
	    (otherFoo * 1.5).println()
		//运行结果 7 + 22i
	}

当你通过这样子去调用这个运算符的时候

	(1.5 * f1).println()

这时候,编译器会提示你出错了

这是因为Kotlin的运算符不会自动至此交换性(交换运算符的左右两边)。

那要怎么样才能那样写呢?

需要定义一个单独的运算符

	fun Any.println() = println(this)
	operator fun Double.times(foo: Foo):Foo = Foo((this * foo.x).toInt(), (this * foo.y).toInt())
	
	
	fun main(args: Array) {
	     
	    val otherFoo = Foo(5, 15)
	 
	    (1.5 * otherFoo).println()
		// 运行结果 7 + 22i
	}
  • 运算符函数不是单一返回类型的,也是可以定义不同的返回类型,下面举个栗子:

      fun Any.println() = println(this)
        
      operator fun Char.times(count: Int): String = toString().repeat(count)
      
      fun main(args: Array) {
      
          ('a' *  10) .println()
      
      }
    

在上面的代码中,这个运算符是Char类型的扩展函数,参数类型是Int类型,所以是Char * Int这样的操作,返回类型是String。

注意:运算符和普通函数一样,可以重载operator函数,可以定义多个同名,但是参数不一样的方法。

重载复合赋值运算符

什么是复合赋值运算符?

  • 类似于 += 这样的,合并了两部操作的运算符,同时赋值,称为符合运算符。

下面我们列举对应的函数名:

表达式 函数名
a *= b timesAssign
a /= b divAssign
a %= b remAssign
a += b plusAssign
a -= b minusAssign

示例代码

	data class Foo(val x: Int, val y: Int) {
	    operator fun plus(instance: Foo) = Foo(x + instance.x, y + instance.y)
	
	
	    operator fun div(instance: Foo) = Foo(x / instance.x, y / instance.y)
	
	    operator fun times(num: Double) = Foo(x * num.toInt(), y * num.toInt())
	
	    operator fun rem(instance: Foo) = Foo(x % instance.x, y % instance.y)
	
	    operator fun minus(instance: Foo) = Foo(x - instance.x, y - instance.y)
	 
	    override fun toString(): String {
	        return "$x + ${y}i"
	    }
	
	
	}
	
	fun Any.println() = println(this)
	 
	 
	fun main(args: Array) {
	    var foo = Foo(3, 4)
	    val fooOther = Foo(4, 2)
	
	
	    foo += fooOther
	    foo.println()  //打印  7 + 6i
	
	}

上面的 += 等同于 foo = foo + fooOther(4, 2)这些操作当然是只对可变变量有效的

  • 默认情况下,复合赋值运算符是可以修改变量所引用的对象,同时重新分配引用,但是在将一个元素添加到一个可变集合的时候,+= 是不会重新分配引用的:

      val list = mutableListOf()
      list += 42
      list.println() // 打印[42]
    

改变不改变引用地址其实可以根基内存先关的知识进行判断

  • 同样我们可以对复合赋值运算符进行重载,同样可以定义多个同名,但是参数不一样的方法:

      fun Any.println() = println(this)	
      operator fun MutableCollection.plusAssign(element: Int) {
          add(element - 1)
      }
      
      fun main(args: Array) {
          val list = mutableListOf()
          list+=42
          list.println()  // 运行结果 [41]
      }
    
  • 如果在plus和plusAssign两个函数同时被定义且适用,那么编译器就会报错,最好在设计新类的时候保持(可变性)一致,尽量不同时定义plus和plusAssign运算。如Foo类是不可变的,那么只提供plus运算,如果一个类是可变的,如构造器,那么只需提供plusAssign和类似的运算就够了。

  • 实际上 += 可以被转换为 plus 或者 plusAssign 函数调用,而 Kotlin 的标准库中为集合支持这两种方法。

+- 运算符会返回一个新的集合。

+=-= 用于可变集合,会修改集合,如果是只读,那么就会返回一个修改过的副本,也就是说只有在只读集合被定义为 var 类型的时候,才能使用 +=-=

	fun main(args: Array) {
	    // 可变类型
	    val list = mutableListOf(1, 2)
	    // += 修改list
	    list += 3
	    // + 返回一个新的List
	    val newList = list + listOf(4, 5) // 除了使用单个元素参数,也可使用元素类型相同的集合
	    list.println() // 打印[1, 2, 3]
	    newList.println() // 打印[1, 2, 3, 4, 5]
	    var varList = listOf(1, 2)
	    // 只读集合类型为var
	    varList.println() // 打印[1, 2]
	    varList += 3
	    varList.println() // 打印[1, 2, 3]
	}

重载一元运算符

  • Kotlin中允许重载一元运算符,如-a,+a等等,同样我们列举支持的一元运算符和对应的函数名:
表达式 函数名
+a unaryPlus
-a unaryMinus
!a not
++a, a++ inc
–a, a– dec
  • 重载一元运算符过程与前面一样,通过预先定义的一个名称来声明函数(成员函数或者扩展函数),并且用 operator 修饰符标记。

注意:一元运算符是没有参数的。

	fun Any.println() = println(this)	
	operator fun Foo.unaryMinus() =  Foo(-x, -y)

	
	fun main(args: Array) {
	    (-Foo(3,4)).println()
	}
  • 当重载自增自减运算符符是,编译器自动支持前缀–a和后缀a–语义。

      fun Any.println() = println(this)	
      operator fun BigDecimal.inc() = this + BigDecimal.ONE + BigDecimal.ONE + BigDecimal.ONE
      
      
      fun main(args: Array) {
          var bd = BigDecimal(2)
          (bd++).println()  	//先参与运算,然后执行相加操作,运行结果  2   
          (++bd).println()	//先相加,得到结果参与运算,运行结果  6
      }
    

重载比较运算符

比较运算符,可以在除了基本数据类型外的任意对象上使用,当 Java 中使用 equalscompareTo 时,在 Kotlin 中,直接用运算符重载。

比较运算符分为等号运算符和排序运算符。

表达式 函数名
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

等号运算符equals

在我们平时使用判断字符串是否与某个字符串相等的时候,会使用 equals 函数来判断,然而在 Kotlin 中,我们可以是用 == 来代替 equals 函数,~= 来代替 !qeuals
Java 中如果使用 null 对象来 equals 的话,会爆空指针异常,而 Kotlin 中的 == 是支持可空类型的,因为会先判断是否为空,如 a == b 会先检查 a 是否为空,如果不是,就会调用a.equals(b),否则只有两个参数都是空值,结果才为真。

下面我们来重载 equals 运算符

	data class Foo(val x: Int, val y: Int) {
	     
	    override fun equals(other: Any?): Boolean = when {
	        // 使用恒等运算符来判断两个参数是否同一个对象的引用
	        this === other -> true
	        other !is Foo -> false
	        else -> other.x == this.x && other.y == this.y
	
	    }
	
	
	    override fun toString(): String {
	        return "$x + ${y}i"
	    }		
	}


			
	fun main(args: Array) {
	    val aFoo = Foo(3, 4)
	    val otherFoo = Foo(3, 4)
	    val foo = Foo(4, 4)
	    println(aFoo == otherFoo)  	//运行结果 true
	    println(aFoo == foo)		//运行结果 false
	    println(aFoo != foo)		//运行结果 true
		println(foo == null )		//运行结果 false
	}

注意:=== 与Java一样,检查两个参数是否是同一个对象的引用,如果是基本数据类型,检查值是否相同,===和!==不能被重载。

排序运算符 compareTo

Java 中,基本数据类型集合排序通常都是使用 <> 来比较,而其他类型需要使用 element1.compareTo(element2) 来比较的。而在 Kotlin 中,通过使用比较运算符 (>``<``>=``<=) 来进行比较。

  • 比较运算符会被转换成compareTo函数,compareTo的返回类型必须为Int。

      class Person(var firstName: String, var lastName: String) : Comparable {
          override fun compareTo(other: Person): Int =
              compareValuesBy(this, other, Person::firstName, Person::lastName)
      }
      
      
      
      fun main(args: Array) {
          val person1 = Person("李","云龙")
          val person2 = Person("李","云迪")
      
          println(person1 >= person2)  //打印 true
      }
    

compareValuesBy 函数是按顺序依次调用回调方法,两两一组分别做比较,然后返回结果,如果则返回比较结果,如果相同,则继续调用下一个,如果没有更多回调来调用,则返回0。

override标记

从上面可以看到,equalscompareTo 都是被 override 标记的,之所以会被标记,是因为在 Any 类中已经定义了 equals 函数,而所有的对象都默认继承 Any 类,所有才重载的时候需要使用 override 标记,而且 equals 不能定义为扩展函数,因为 Any 类的实现是重要优先于扩展函数。

同样,compareToComparable 接口中已经定义了,所有在重载的时候,需要使用 override 标记。

你可能感兴趣的:(android学习积,Kotlin)