Kotlin操作符重载实例详解

作者:屹森 时间:2022-11-28 14:25:32 

算数运算操作符重载

在kotlin中我定义一个类

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

然后实例化两个对象

val p1 = Point(3,5)
val p2 = Point(5,7)

想表示p1的元素x加上p2的元素x,p1的元素y,加上p2的元素y.然后输出一个p3.

val p3 = Point(p1.x + p2.x, p2.y + p2.y)

以上这种写法没有任何问题。不过我们可以利用Kotlin扩展函数简化上面的操作,我们给Point增加一个plus,并附加operator关键字。(增加operator的关键字是为了区分plus并不是一个普通的成员方法)

data class Point(val x: Int, val y: Int) {
   operator fun plus(other: Point): Point {
       return Point(x + other.x, y + other.y)
   }
}

接下来再来实现上面的需求.

val p3 = p1 + p2

这样表达起来更简洁了。 另外我们可以把plus作为Point的扩展方法

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

operator fun Point.plus(other: Point): Point {
   return Point(x + other.x, y + other.y)
}

这种场景适用于Point存在于一个三方库,我们并能修改其中的内容. Kotlin中提供了以下操作符的重载. 只需要实现对应的方法即可。

Kotlin操作符重载实例详解

之前我们定一个了plus,参数是Point,实际上对于个操作符重载并不局限于同一种类型,接下来我们来定一个times,允许你去扩展Ponit.

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

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

fun main(args: Array<String>) {
   val p = Point(10, 20)
   println(p * 1.5)
}

注意kotlin不支持交换性,例如我这里写成1.5 * p这里是不允许的。除非你去定一个

operator fun Double.times(p: Point): Point

返回类型同样也可以不是同一个类型,例如, 定义一个对char类型重载*操作符重复count后返回一个string.

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

fun main(args: Array<String>) {
   println('a' * 3)
}

复合运算操作符重载

我们在编程过程中通常会去把这种写法p = p + p1 写成 p += p1 这种简化写法,在kotlin中同样也支持这种+=操作符自定义操作。这种自定义运算操作符在什么场景下使用呢? 举个例子,我们定义集合

val numbers = ArrayList<Int>()
numbers += 42
println(numbers[0])

以上写法会感觉更加简洁。 我们在集合中定义操作符重载方法plusAssign(这里还有minusAssign, timesAssign等等)

operator fun <T> MutableCollection<T>.plusAssign(element: T) {
   this.add(element)
}

不过kotlin-stblib库已经帮你实现好了关于集合的类似操作. 在集合中+,-会累加集合原始后返回一个新的集合,如果使用+=,-=, 集合是mutable,会在本集合直接修改内容,如果集合是read-only,会返回一个拷贝后的修改集合。(这意味着如果集合是read-only,它的声明必须要是var, 不然它不能接受新返回拷贝后的修改集合). 你可以使用单独的元素或者集合(类型必须一致)进行复合运算符操作和算数运算符.

val list = arrayListOf(1, 2)
list += 3                      
val newList = list + listOf(4, 5)
println(list)
result : [1, 2, 3]
println(newList)
result : [1, 2, 3, 4, 5]

一元运算操作符重载

我们在编程过程中使用类似++a, a++, --a, a--同样支持运算符重载。仅仅需要重写下面的操作符函数即可

Kotlin操作符重载实例详解

举个例子

operator fun BigDecimal.inc() = this + BigDecimal.ONE

fun main(args: Array<String>) {
   var bd = BigDecimal.ZERO
   println(bd++)
   println(++bd)
}

这里注意到我只定一个inc(),同时也是支持bd++和++bd.

比较操作符重载

kotlin中还支持==, !=, >, <操作符重载. 对于==, !=我们重写equals方法. 这里还是拿Point来举栗子

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

我们这里声明成了data类,这个关键字加上了编译器会自动帮你实现equals方法,我们现在去掉,看看自己去写equals怎么写

class Point(val x: Int, val y: Int) {
   override fun equals(obj: Any?): Boolean {
       if (obj === this) return true      //1
       if (obj !is Point) return false
       return obj.x == x && obj.y == y
   }
}

fun main(args: Array<String>) {
   println(Point(10, 20) == Point(10, 20))
   println(Point(10, 20) != Point(5, 5))  //2
   println(null == Point(1, 2))
}

这里我们需要关注一个//1 obj === this,这个===操作符是比较两个对象的引用是否一致,实际上和java中的==是一样的。 之前我们提到的操作符重载都会加上一个operator关键字,这里为什么是override?因为它重写了Any.class的equals方法.

Kotlin操作符重载实例详解

这里看下//2!= 其实就是equals结果的取反. 除了=和!=之外这里还有>和<, 通过重写compareTo可以实现

class Person(
       val firstName: String, val lastName: String
) : Comparable<Person> {

override fun compareTo(other: Person): Int {
       return compareValuesBy(this, other,
           Person::lastName, Person::firstName)
   }
}

fun main(args: Array<String>) {
   val p1 = Person("Alice", "Smith")
   val p2 = Person("Bob", "Johnson")
   println(p1 < p2)
}

这里的compareValuesBy顺便说说,它是kotlin-stblib提供的扩展函数

Kotlin操作符重载实例详解

集合和区域的约定

在kotlin中我们可以使用类似于操作数组的方法操作集合,例如a[b]这种. 对于其它类,可以自定义操作符实现类似的操作

operator fun Point.get(index: Int): Int {
   return when(index) {
       0 -> x
       1 -> y
       else ->
           throw IndexOutOfBoundsException("Invalid coordinate $index")
   }
}

fun main(args: Array<String>) {
   val p = Point(10, 20)
   println(p[1])
}

我们对于赋值操作同样也可以通过自定义operator

data class MutablePoint(var x: Int, var y: Int)

operator fun MutablePoint.set(index: Int, value: Int) {
   when(index) {
       0 -> x = value
       1 -> y = value
       else ->
           throw IndexOutOfBoundsException("Invalid coordinate $index")
   }
}

fun main(args: Array<String>) {
   val p = MutablePoint(10, 20)
   p[1] = 42
   println(p)
}

另外一个知识点是自定义in操作符

Kotlin操作符重载实例详解

in对于的contains函数,判断一个元素是否属于一个范围里.

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

data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
   return p.x in upperLeft.x until lowerRight.x &&
          p.y in upperLeft.y until lowerRight.y
}

fun main(args: Array<String>) {
   val rect = Rectangle(Point(10, 20), Point(50, 50))
   println(Point(20, 30) in rect)
   println(Point(5, 5) in rect)
}

迭代运算符重载

我们平时使用的

for (x in list) { ... }

将被转换为 list.iterator() 的调用,然后重复调用 hasNext 和 next 方法,就像在 Java 中一样。 请注意,在 Kotlin 中,它也是一种约定,这意味着可以将迭代器方法定义为扩展。这就解释了为什么可以迭代常规 Java 字符串:kotlin-stblib 在 Char-Sequence(String 的超类)上定义了一个扩展函数迭代器:

operator fun CharSequence.iterator(): CharIterator
>>> for (c in "abc") {}

其它类型也可以通过自定义iterator实现自己类特定的操作。

import java.util.Date
import java.time.LocalDate

operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
       object : Iterator<LocalDate> {
           var current = start

override fun hasNext() =
               current <= endInclusive

override fun next() = current.apply {
               current = plusDays(1)
           }
       }

fun main(args: Array<String>) {
   val newYear = LocalDate.ofYearDay(2017, 1)
   val daysOff = newYear.minusDays(1)..newYear
   for (dayOff in daysOff) { println(dayOff) }
}

解构声明

这个操作可以分解一个对象中成员,例如

>>> val p = Point(10, 20)
>>> val (x, y) = p            
>>> println(x)
10
>>> println(y)
20

解构声明看起来有点像变量声明,不过它组合了多个变量值。实际上它在kotlin中也属于自定义操作符.去解构多个变量需要定义componentN,N是变量的位置.

Kotlin操作符重载实例详解

class Point(val x: Int, val y: Int) {
   operator fun component1() = x
   operator fun component2() = y
}

对于data类,解构已经帮你声明好了 另外解构声明还可以用在循环中

fun printEntries(map: Map<String, String>) {
   for ((key, value) in map) {
       println("$key -> $value")
   }
}

fun main(args: Array<String>) {
   val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
   printEntries(map)
}

Map中包含了扩展方法component1,component2返回key和value. 实际上你可以将上面的循环部分翻译成

for (entry in map.entries) {
   val key = entry.component1()
   val value = entry.component2()
   // ...
}

来源:https://juejin.cn/post/7087129138070290463

标签:kotlin,操作符,重载
0
投稿

猜你喜欢

  • Javassist如何操作Java 字节码

    2021-08-09 08:21:28
  • Spring基于常用AspectJ切点表达式使用介绍

    2023-12-08 19:58:37
  • Android中ContextMenu用法实例

    2023-02-12 20:09:49
  • HttpServletResponse乱码问题_动力节点Java学院整理

    2021-10-18 17:48:51
  • C# 时间戳转换实例

    2022-12-07 12:17:57
  • Android获取手机系统版本等信息的方法

    2023-02-05 20:36:08
  • Android登录时密码保护功能

    2023-10-24 07:09:40
  • Mybatis执行流程、缓存原理及相关面试题汇总

    2022-10-15 23:30:28
  • android实现简易计算器

    2023-06-21 04:26:09
  • @valid 无法触发BindingResult的解决

    2023-08-10 09:16:12
  • android 仿微信demo——登录功能实现(服务端)

    2023-10-04 13:40:55
  • java mail使用qq邮箱发邮件的配置方法

    2023-07-02 07:58:56
  • Java简单实现定时器

    2023-07-16 18:10:58
  • Scala方法与函数使用和定义详解

    2021-10-05 06:34:12
  • Http学习之组装报文

    2021-12-30 07:23:18
  • Java中stream处理中map与flatMap的比较和使用案例

    2023-11-21 02:27:53
  • Java 在游戏中探索数组二维数组

    2023-07-01 10:04:04
  • Flutter Android端启动白屏问题的解决

    2023-10-14 12:03:27
  • C#字符串和Acsii码相互转换

    2022-09-24 00:12:07
  • Android 自定义View实现单击和双击事件的方法

    2022-03-23 19:21:18
  • asp之家 软件编程 m.aspxhome.com