美文网首页
Kotlin运算符重载及其他约定

Kotlin运算符重载及其他约定

作者: 谷哥得小弟 | 来源:发表于2021-07-15 16:21 被阅读0次

    一、重载算术运算符

    1.1 重载二元算术运算

    kotlin 允许我们重载常用的二元算术运算:+ - * / ,这样我们这些基本运算就不只是能运用于基本数据结构(int,string 等)了,我们还可以用这些符号操作对象,集合等。比如对象A+对象B,往集合C添加元素等。

    重载对象相加运算符写法如下:

    data class Point(val x: Int, val y: Int) {
        //operator关键字就是重载运算符标识 plus 对应+ 不可随意更名
        operator fun plus(p: Point): Point {
            return Point(x + p.x, y + p.y)
        }
    }
    

    我们建立一个Point类重载plus 方法,这样我们可以在外部调用类的方法让两个对象相加:

    @Test
        fun testSecondPoint() {
            val p1 = Point(10, 20)
            val p2 = Point(30, 40)
    
            print(p1 + p2)
            print("\n" + p1.plus(p2))
        }
    

    打印结果如下:

    Point(x=40, y=60)
    Point(x=40, y=60)
    
    
    Process finished with exit code 0
    

    除了把Point类声明为成员函数外,还可以声明为扩展函数,对上面的类进行如下更改即可:

    data class Point(val x: Int, val y: Int) {
        //operator关键字就是重载运算符标识 plus 对应+ 不可随意更名
    //    operator fun plus(p: Point): Point {
    //        return Point(x + p.x, y + p.y)
    //    }
    }
    
    //把方法写到类的外面去,等同于java的静态函数
    operator fun Point.plus(p: Point): Point {
        return Point(x + p.x, y + p.y)
    }
    

    这种写法和第一种写法是等价的,这种写法用的更多一些。在Kotlin 中你不能定义自己的运算符,只能重载系统已经有的运算符,可重载的二元运算符如下:

     a*b  times //对应的方法
     a/b  div
     a%b  rem
     a+b  plus
     a-b  minus
    

    移位运算:
    kotlin没有为标准数字类型定义任何位运算符,但提供了一些函数供我们使用:

    shl -------带符号左移
    shr -------带符号右移
    ushr -------无符号右移
    and --------按位与
    or  ----------按位或
    xor  ----------按位异或
    inv  ----------按位取反
    

    下面举个例子:

    @Test
        fun testWei() {
            print(0x01 and 0x10)
            print("\n")
            print(0x02 or 0x10)
            print("\n")
            print(0x12)
            print("\n")
            print(0x02 or 0x10)
            print("\n")
            print(4 shl 1)
            print("\n")
            print(4 shr 2)
            print("\n")
            print(0x01 ushr 0x10)
        }
    
    0
    18
    18
    18
    8
    1
    0
    
    
    Process finished with exit code 0
    
    
    print(4 shl 1)  代表 100左移1位---1000 = 8
    print(4 shr 2)  代表 100右移2位---001 = 1
    
    

    1.2 重载复合赋值运算符

    上面的二元运算符对于合并步骤的操作也是有效的,比如 += -=,看如下例子:

    var point = Point(10, 20)  //这里point 需要改为变量,常量就不能相加了
            point += Point(2, 3)
    
            print(point)
    
    打印如下:
    Point(x=12, y=23)
    
    
    Process finished with exit code 0
    

    kotlin系统为这种复合运算也定义了运算符,就是plusAssign,minusAssign等。
    我们重载一个试试:

    operator fun Point.plusAssign(p: Point) {
        this.x += p.x
        this.y += p.y
    }
    
    
    data class Point(var x: Int, var y: Int)
    
    @Test
        fun testValue() {
            val point = Point(10, 20)
            val pointB = Point(5, 5)
            point.plusAssign(pointB)
    
            print(point)
        }
        
        
        结果:
        Point(x=15, y=25)
    
    
    Process finished with exit code 0
        
    

    注意这里的Point类的x,y必须声明为变量,否则会报错。

    1.3 重载一元运算符

    常见的一运算符如下:

    表达式 函数名
    +a unaryPlus
    -a unaryMinus
    !a not
    ++ a, a ++ inc
    -- a, a -- dec

    现在让我们拿inc来试一下:

    operator fun Point.inc(): Point {
        return Point(++x, ++y)
    }
    
    fun testInc(){
            var point = Point(10, 20)
            point.inc()
    
            print(point)
        }
        
        输出:
        Point(x=11, y=21)
    
    
    Process finished with exit code 0
    

    二、重载比较运算符

    与算术运算符一样,我们也可以对任何对象使用比较运算符,而不仅仅限于基本数据类型。

    2.1 等号运算符 'equals'

    在kotlin中,所有== , !=运算符,都会转换成equals方法的调用。如下:

    a==b  ------------------>a?.equals(b)?:(b==null)
    

    比较 a 和 b 会检查 a是否非空,如果非空,调用a.equals(b) ,如果两个都为空,则返回true,equal函数是系统帮我们写好的,我们直接调用就行。如果我们需要手动去更改里面的比较逻辑,我们重写一下equals这个函数看一下:

    data class Point(var x: Int, var y: Int) {
        override fun equals(obj: Any?): Boolean {
            if (obj === this) return true
            if (obj !is Point) return false
            return obj.x == x && obj.y == y
        }
    }
    
    @Test
        fun testequals() {
            print(Point(10, 15) == Point(10, 15))
            print("\n")
            print(Point(10, 15) != Point(20, 30))
            print("\n")
            print(null == Point(10, 15))
        }
    
    输出:
    true
    true
    false
    
    
    Process finished with exit code 0
    

    注意:Kotlin 中所有对象都支持等式比较,在Any类中已经标记了operator,我们不需要再次标记,我们需要重写,添加override关键字;还需要注意这里的equals方法不能实现为扩展函数,因为继承自Any类的实现始终优先于扩展函数。

    2.2 排序运算符 'compareTo'

    Kotlin中基础数据比较大小可以直接进行比较,比如:int、string ;但是对象的比较的话和java类似,都需要实现 Comparable接口,重写compareTo 方法,如下:

    data class Point(var x: Int, var y: Int) : Comparable<Point> {
        override fun compareTo(obj: Point): Int {
            return compareValuesBy(this, obj, Point::x, Point::y)
        }
    }
    
    tip:a>=b-----------> a.compareTo(b)>=0 (两个对象的比较转换为compareTo的函数调用,然后结果与零进行比较)

    我们调用一下看下效果:

    @Test
        fun testcompareTo() {
            print(Point(10, 15) < Point(1, 2))
            print("\n")
            print(Point(10, 15) <= Point(20, 3))
            print("\n")
            print(Point(10, 15) <= Point(10, 3))
            print("\n")
        }
    
    输出:
    false
    true
    false
    
    Process finished with exit code 0
    
    

    可以看到我们先比较x,如果x值相等的情况,才会去比较y,有一个先后顺序。

    三、集合与区间的约定

    处理集合常见的操作是通过下标来获取和设置元素,以及检查元素是否属于当前集合,所有这些操作都支持运算符语法。比如下标运算符(a[b]),是否在集合中 in 运算符。

    3.1 通过下标来访问元素 'get' 'set'

    对于基础类型,我们可以直接访问,如下
    1、访问map中的元素:

    @Test
        fun testGetSet() {
            val map = mutableMapOf(0 to "a", 1 to "b", 2 to "c", 3 to "d")
            val value=map[2]
            print("\nvalue:"+value)
    
            map[1]="bbb"
            print("\nmap:"+map)
        }
    
    输出:
    value:c
    map:{0=a, 1=bbb, 2=c, 3=d}
    
    
    Process finished with exit code 0
    
    

    这种下标获取在kotlin中会被转换成对get,set运算符方法的调用;map和MutableMap已经实现了这些方法,我们可以直接用,现在让我们来实现一个对象的get set运算符吧:

    operator fun Point.get(index: Int): Int {
        return when (index) {
            0 -> x
            1 -> y
            else -> throw IndexOutOfBoundsException("IndexOutOfBoundsException")
        }
    }
    
    operator fun Point.set(index: Int, value: Int) {
        return when (index) {
            0 -> x = value
            1 -> y = value
            else -> throw IndexOutOfBoundsException("IndexOutOfBoundsException")
        }
    }
    

    然后在进行调用:

    @Test
        fun testGetSet() {
            val map = mutableMapOf(0 to "a", 1 to "b", 2 to "c", 3 to "d")
            val value = map[2]
            print("\nvalue:" + value)
    
            map[1] = "bbb"
            print("\nmap:" + map)
    
            val p = Point(10, 20)
            print("\nmap:" + p[0])
    
            p[1] = 23
            print("\nmap:${p}")
    
            p.set(0, 5)
            print("\nmap:${p}")
        }
    
    输出:
    value:c
    map:{0=a, 1=bbb, 2=c, 3=d}
    map:10
    map:Point(x=10, y=23)
    map:Point(x=5, y=23)
    
    
    Process finished with exit code 0
    

    3.2 'in' 的约定

    in 运算符用来检测某个对象是否属于集合内,对应的函数是contains,我们用某个点是否属于某个矩形来做测试:

    data class Rectangle(val upLeft: Point, val lowRight: Point)
    
    operator fun Rectangle.contains(p: Point): Boolean {
        return p.x in upLeft.x until lowRight.x &&
                p.y in upLeft.y until lowRight.y
    }
    
    @Test
        fun testIn() {
            val rect = Rectangle(Point(10, 20), Point(50, 50))
            val p = Point(20, 30)
            val p1 = Point(5, 5)
    
            print("\nmap:${p in rect}")
            print("\nmap:${p1 in rect}")
            print("\nmap:${rect.contains(p)}")
    
        }
    
    输出:
    map:true
    map:false
    map:true
    
    Process finished with exit code 0
    

    in 运算符会转化为集合类对contains的调用,所以两者等价

    3.3 rangeTo 的约定

    start .. end ------->start.rangeTo(end)
    

    这个约定主要用来表示一个区间,优先级要低于算术运算符
    这个约定一般用在基础类型中,如下:

    @Test
        fun testRangeTo() {
            val a = 5
            print("\nmap:${0 .. (a+1)}")
    
            print("\nmap:${0.rangeTo(a+1)}")
        }
    
    输出:
    map:0..6
    map:0..6
    
    
    Process finished with exit code 0
    

    四、结构声明和组件函数

    4.1 结构声明和循环

    结构声明:允许你展开单个复合值,并使用它来初始化多个单独的变量

    @Test
        fun testXIgou() {
            val p=Point(10,20)
            //同时声明两个变量,然后用对象p给它赋值
            val (x,y)=p
            print("\n ${x}")
            print("\n ${y}")
        }
    
    输出:
    10
     20
    
    
    Process finished with exit code 0
    

    我们可以把上诉赋值过程看成:

    val (a,b)=p ------------>val  a=p.component1()  val b=p.component2()
    

    这个流程有点类似于java中split 方法将字符串分割,我们再来看看如何分割:

    data class Name(
        val name: String,
        val ext: String
    )
    
    fun aplit(fullName: String): Name {
        val result = fullName.split(".", limit = 2)
        return Name(result[0], result[1])
    }
    
    fun testXI() {
            val (name,ext) = aplit("hello.txt")
            print("\n ${name}")
            print("\n ${ext}")
        }
        
        输出:
        hello
        txt
    
    
    Process finished with exit code 0
    

    相当于我们先分割字符串,在存入对象,最后在数据类中取值,kotlin只支持取前5元素
    再来看下循环中如何使用:

    @Test
        fun testXunhuan() {
            val map = mapOf("苹果" to "水果", "土豆" to "蔬菜")
            for ((key, value) in map) {
                print("\n$key -> $value")
            }
            val str="a,b,c,d,e,f,g"
            val list=str.split(",")
            for (p in list) {
                print("\n----$p")
            }
        }
    
    苹果 -> 水果
    土豆 -> 蔬菜
    ----a
    ----b
    ----c
    ----d
    ----e
    ----f
    ----g
    
    
    Process finished with exit code 0
    
    

    五、委托属性

    5.1 委托属性基本操作

    基本语法:

    class Foo {
        var p:Type by Delegate()
    }
    

    属性p将它的访问器逻辑委托给了另一个对象:delegate类的一个新对象实例。通过关键字 by 对其后的表达式求值来获取这个对象,关键字 by 可以用于获取任何符合属性委托约定规则的对象。

    5.2 使用委托属性:惰性初始化 和 by lazy()

    惰性初始化是在第一次访问的时候,根据需要创建对象的一部分。在初始化过程需要消耗大量资源的时候非常有用。如下:

    class Person(val name:String) {
        val emails by lazy{
            //懒加载数据内容,对于耗费资源的操作很有用
            // 比如查询数据库,这个是线程安全的
        }
    }
    

    5.3 实现委托属性

    举个例子是对某人的年龄和工资变化进行跟踪:
    首先创建一个类来当作被委托类,重载运算符 getvalue ,setvalue:

    class ObservablePro(
        var oldVaule: Int, val changeVaule: PropertyChangeSupport
    ) {
        operator fun getValue(p: Person, prop: KProperty<*>): Int = oldVaule
    
        operator fun setValue(p: Person, prop: KProperty<*>, newV: Int) {
            val oldV = oldVaule
            oldVaule = newV
            changeVaule.firePropertyChange(prop.name, oldV, newV)
        }
    }
    

    然后,创建一个类,内部实现构造监听器以及委托给上面写的那个类

    class Person(
        val name: String, age: Int, salary: Int
    ) {
        protected val change = PropertyChangeSupport(this)
        fun addListener(listener: PropertyChangeListener) {
            change.addPropertyChangeListener(listener)
        }
    
        var age: Int by ObservablePro(age, change)
        var salary: Int by ObservablePro(salary, change)
    }
    

    最后,外部调用初始化Person类并添加监听:

    @Test
        fun testdelegate() {
            val person = Person("yang", 26, 1000)
            person.addListener(PropertyChangeListener {
                print("\nperson 数据有变更salary:${person.salary}\tage:${person.age}\tname:${person.name}")
            })
            person.age = 27
            person.salary = 1200
            person.age = 28
            person.salary = 1400
            person.age = 29
            person.salary = 1500
        }
    
    输出:
    person 数据有变更salary:1000 age:27  name:yang
    person 数据有变更salary:1200 age:27  name:yang
    person 数据有变更salary:1200 age:28  name:yang
    person 数据有变更salary:1400 age:28  name:yang
    person 数据有变更salary:1400 age:29  name:yang
    person 数据有变更salary:1500 age:29  name:yang
    
    
    Process finished with exit code 0
    
    

    可以看到随着属性更改后,监听器在不断回调

    相关文章

      网友评论

          本文标题:Kotlin运算符重载及其他约定

          本文链接:https://www.haomeiwen.com/subject/jmqmpltx.html