Kotlin | 7.运算符重载及其他约定

2021-03-02 15:56:14 浏览数 (1)

本章内容包括:

  • 运算符重载
  • 约定:支持各种运算的特殊命名函数
  • 委托属性

7.1 运算符

代码语言:javascript复制
          /**-------------------- 7.1.1 重载二元算术运算 ----------------------*/
        // 代码清单7.1 定义一个plus运算符
        data class Point(val x: Int, val y: Int) {
            // 定义一个名为 plus 的方法
            operator fun plus(other: Point): Point {
                return Point(x   other.x, y   other.y)
            }
        }
        println(Point(10, 10).plus(Point(12, 13)).x)// 22
        val point = Point(10, 10)
        val point2 = Point(12, 13)
        // 通过使用   号来调用plus方法
        println(point   point2)// Point(x=22, y=23)

        /*
        * 在使用了operator修饰符声明了plus函数后,你就可以直接使用_好来求和了。
        * 除了这个运算符声明为一个 成员函数 外,也可以把它定义为一个扩展函数
        */
        // 代码清单7.2 把运算符定义为扩展函数
        operator fun Point.plus(other: Point): Point {
            return Point(x   other.x, y   other.y)
        }

        /*
        * 可重载的二元算术运算符
        *  表达式     函数名
        *  a * b     times
        *  a / b     div
        *  a % b     mod
        *  a   b     plus
        *  a - b     minus
        */

        // 代码清单7.3 定义一个运算数类型不同的运算符
        operator fun Point.times(scale: Double): Point {
            return Point((x * scale).toInt(), (y * scale).toInt())
        }

        val point3 = Point(10, 20)
        println(point3 * 2.0)// Point(x=20,y=40)

        // 注意不能使用 2.0 * point3,如果需要使用需要另定义
        operator fun Double.times(point: Point): Point {
            return Point((this * point.x).toInt(), (this * point.y).toInt())
        }
        println(3.0 * point3)// Point(x=30,y=60)

        // 代码清单7.4 定义一个返回结果不同的运算符
        operator fun Char.times(count: Int): String {
            return toString().repeat(count)
        }
        println('a' * 3)// aaa

        /*
        * 没有用于位运算的特殊运算符
        * 以下,用于执行位运算的完整函数列表
        * shl  --  带符号左移
        * shr  --  带符号右移
        * ushr --  无符号右移
        * and  --  按位与
        * or   --  按位或
        * xor  --  按位异或
        * inv  --  按位取反
        */
        // 使用方法:
        println(0x0F and 0x0F)// 0
        println(0x0F or 0x0F)// 255
        println(0x1 shl 4)// 16

        /**-------------------- 7.1.2 重载复合赋值运算符 ----------------------*/
        // 对可变变量var有效,例子:
        var point1 = Point(1, 2)
        point1  = Point(1, 1)
        println(point1)// Point(x=2,y=3)

        // 将元素添加到可变集合,例子:
        val numbers = ArrayList<Int>()
        numbers  = 42
        println(numbers[0])// 42

        /*
        * 如果你定义了一个返回值 Unit,名为plusAssign函数,Kotlin将会在用到 =运算符的地方调用它。
        * 其他如 minusAssign、timeAssign
        */
        operator fun <T> MutableCollection<T>.plusAssign(element: T) {
            this.add(element)
        }

        /*
        * a  = b
        * a = a.plus(b)
        * a.plusAssign(b)
        * 运算符  = 可以被转换为plus或者plusAssign函数的调用
        *   和 - 运算符总是返回一个新的集合。
        *  =和-=运算符用于可变集合时,始终就地修改它们,用于只读集合时,会返回一个修改过的副本。
        */
        val list = arrayListOf(1, 2)
        list  = 3

        val newList = list   listOf(4, 5)
        println(list)// 就地修改 [1,2,3]
        println(newList)// 新的集合 [1,2,3,4,5]


        /**-------------------- 7.1.3 重载一元运算符 ----------------------*/
        // 代码清单7.5 定义一个一元运算符
        operator fun Point.unaryMinus(): Point {
            // 一元运算符无参数
            return Point(-x, -y)
        }

        val point4 = Point(10, 20)
        println(-point4)// Point(x=-10,y=-20)

        /*
        *  a  --   a.unaryPlus()
        * 一元运算符   被转换为unaryPlus函数的调用
        * 可重载的一元算法的运算符
        * 表达式   函数名
        *  a      unaryPlus
        * -a      unaryMinus
        * !a      not
        *   a,a   inc
        * --a,a-- dec
        */

        // 代码清单7.6 定义一个自增运算符
        operator fun BigDecimal.inc() = this   BigDecimal.ONE

        var db = BigDecimal.ZERO
        println(db  )// 0  在执行后添加
        println(  db)// 2  在执行前添加

7.2 重载比较运算符

代码语言:javascript复制
        /**-------------------- 7.2.1 等号运算符 equals ----------------------*/
        /*
        * == != 可以用于可空运算数
        * a == b  -> a? equals(b) ?: (b==null)
        */

        // 代码清单7.7 实现equals函数
        class Point(val x: Int, val y: Int) {

            // 重写在Any中定义的方法
            override fun equals(other: Any?): Boolean {
                // 优化:检查参数是否与this是统一对象
                if (other === this) return true
                // 检查参数类型
                if (other !is Point) return false
                // 智能转换为Point来访问x、y属性
                return other.x == x && other.y == y
            }
        }
        println(Point(10, 20) == Point(10, 20))// true
        println(Point(10, 20) != Point(5, 5))// true
        println(null == Point(10, 20))// false

        // 恒等运算符(===):来检查参数与调用equals的对象是否相同。
        // Any中的基本方法已经将equals方法标记为 operator 了


        /**-------------------- 7.2.2 排序运算符 compareTo ----------------------*/
        /*
        * a >= b  -> a.compareTo(b) >= 0
        * 两个对象的比较被转换为compareTo的函数调用,然后结果与零比较
        */

        // 代码清单7.8 实现 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)
            }
        }

        val person = Person("Alice", "Smith")
        val person2 = Person("Bob", "Johnson")
        println(person < person2)// false

        /*
        * compareValuesBy 这个函数接收用来计算比较值的一系列回调,按顺序依次调用回调方法,两两一组分别做比较,
        * 并返回结果。如果值不同,则返回比较结果;如果它们相同,则继续调用下一个;如果没有更多的回调来调用,则返回0。
        * */
        println("abc" < "bac")// true

7.3 集合与区间的约定

代码语言:javascript复制
        /**-------------------- 7.3.1 通过下标来访问元素 get 和 set ----------------------*/
        // kotlin中可以使用类似java中数组中的方式来访问map中的元素---使用方括号
//        val value = map[key]
        // 也可以用同样的运算符来改变一个可变map的元素
//        mutableMap[key] = newValue

        // 使用方括号来引用点的坐标:p[0]访问x坐标,p[1]访问y坐标。
        // 代码清单7.9 实现 get 约定
        // 定义一个名为 get 的运算符函数
        operator fun Point.get(index: Int): Int {
            // 根据给出的index返回对应的坐标
            return when (index) {
                0 -> x
                1 -> y
                else -> throw IndexOutOfBoundsException("Invalid coordinate $index")
            }
        }

        val point = Point(1, 2)
        println(point[1])// 2

        /*
        * x[a,b] -> x.get(a,b)
        *
        * get的参数可以是任何类型,而不只是Int,还可以是多个参数。例如 matrix[row,rol]
        */
        // 代码清单7.10 实现 set 的约定方法
        data class MutablePoint(var x: Int, var y: Int)

        // 定义一个名为 set 的运算符函数
        operator fun MutablePoint.set(index: Int, value: Int) {
            when (index) {
                // 根据给出的index参数修改对应的坐标
                0 -> x = value
                1 -> y = value
                else -> throw java.lang.IndexOutOfBoundsException("Invalid coordinate $index")
            }
        }

        val mutablePoint = MutablePoint(10, 20)
        mutablePoint[1] = 42
        println(mutablePoint)// MutablePoint(x=10,y=42)

        // set 最后一个参数用来接收赋值语句中等号右边的值。
        // x(a,b) = c  ->  x.set(a,b,c)


        /**-------------------- 7.3.2 in 的约定 ----------------------*/
        // 代码清单7.11 实现in的约定
        data class Rectangle(val upperLeft: Point, val lowerRight: Point)

        operator fun Rectangle.contains(p: Point): Boolean {
            // 构建一个区间,检查坐标x是否属于这个区间,使用 until 函数来构建一个开区间
            return p.x in upperLeft.x until lowerRight.x &&
                    p.y in upperLeft.y until lowerRight.y
        }

        /*
        * in 右边的对象将会调用 contains 函数,in 左边的对象将会作为函数入参。
        * a in c -> c.contains(a)
        * in 10..20        [10,20] 用 10..20构建一个普通的闭区间。
        * in 10 until 20   [10,20) 开区间是不包括最后一个点的区间。
        */

        /**-------------------- 7.3.3 rangeTo 的约定 ----------------------*/
        /*
        * start..end  ->  start.rangeTo(end)
        * .. 运算符将被转换为 rangeTo函数的调用
        */

        // 这个函数返回一个区间,可以用来检测其他一些元素是否属性它。
//        operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T>

        // 代码清单7.12 处理日期的区间

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val now = LocalDate.now()
            // 创建一个从今天开始的10天的区间
            val closedRange = now..now.plusDays(10)
            // 检测一个特定的日期是否属于这个区间
            println(now.plusWeeks(1) in closedRange) // true

            // rangeTo 运算符的优先级低于算术运算符,但是最好把参数括起来
            val n = 9
            println(0..(n   1))// 0..10

            (0..9).forEach {
                // 把区间括起来,来调用它的方法
                println(it)// 0123456789
            }


            /**-------------------- 7.3.4 在 for 循环中使用 iterator 的约定 ----------------------*/
            // in 在for循环中使用被执行迭代,转换成 list.iterator()
            // 这个库函数让迭代字符串成为可能
//         operator fun CharSequence.iterator():CharIterator
            for (c in "abc") {
                println(c)
            }

            // 代码清单7.13 实现日期区间的迭代器
            operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
                    // 这个对象实现了遍历LocalDate元素的Iterator
                    object : Iterator<LocalDate> {
                        var current = start

                        // 注意,这里日期用到了compareTo约定
                        override fun hasNext() = current <= endInclusive

                        // 在修改前返回当前日期作为结果
                        override fun next() = current.apply {
                            // 把当前日期增加一天
                            current = plusDays(1)
                        }
                    }

            val ofYearDay = LocalDate.ofYearDay(2017, 1)
//            val dayOff = ofYearDay.minusDays(1)..newYear()
            // 对应的 iterator函数实现后,遍历daysOff
//            for (dayOff in daysOff) {
//                println(dayOff)
//            }
        }

7.4 解构声明和组件函数

代码语言:javascript复制
        class Point(val x: Int, val y: Int) {
            operator fun component1() = x
            operator fun component2() = y
        }


        // 解构声明:允许你展开单个复合值,并使用它来初始化多个单独变量
        val point = Point(10, 20)
        val (x, y) = point
        println(x)// 10
        println(y)// 20
        /*
        * 一个解构声明看起来像一个普通的变量声明,但它在括号中有多个变量。约定原理。
        * val (x,y) = point   :
        * val a = p.component1()
        * val b = p.component2()
        *
        * 主要场景之一:从一个函数返回多个值。
        */

        // 代码清单7.14 使用解构声明来返回多个值
        // 声明一个数据类来持有值
        data class NameComponents(val name: String, val extension: String)

        fun splitFileName(fullName: String): NameComponents {
            val result = fullName.split(".", limit = 2)
            // 返回也该数据类型的示例
            return NameComponents(result[0], result[1])
        }
        // 使用解构声明来展开这个类
        val (name, ext) = splitFileName("example.kt")
        println(name)// example
        println(ext)// ext

        // 代码清单7.15 使用解构声明来处理集合
        fun splitFilename(fullName: String): NameComponents {
            val (name, extension) = fullName.split(".", limit = 2)
            return NameComponents(name, extension)
        }
        // 标准库只允许使用此语法来访问一个对象的前五个元素,让一个函数能返回多个值有更简单的方法,使用标准库中的Pair和Triple类。


        /**-------------------- 7.4.1 解构声明和循环 ----------------------*/
        // 代码清单7.16 用解构声明来遍历 map
        // 使用这个语法来打印给定map中的所有条目
        fun printEntries(map: Map<String, String>) {
            for ((key, value) in map) {
                // 在 in 循环中用解构声明
                println("$key -> $value")
            }
            // 等同于这个
            for (entry in map.entries) {
                val key = entry.component1()
                val value = entry.component2()
                println("$key -> $value")
            }
        }

        val mapOf = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
        printEntries(mapOf)
        // 使用了两个Kotlin约定:一个是迭代一个对象 一个是用于解构声明

7.5 重用属性访问的逻辑:委托属性

代码语言:javascript复制
       /*
        * Kotlin中最独特和最强大的功能:委托属性
        * 委托是一种设计模式,操作的对象不用自己执行,而是把工作微委托给另一个辅助的对象。我们把辅助对象称为委托。
        */


        /**-------------------- 7.5.1 委托属性的基本操作 ----------------------*/
        // 基础语法:
//        class Foo {
        // 关键字 by 把属性关联上委托对象
//            var p: Type by Delegate()
//        }

        class Foo {
            // 编译器会自动生成一个辅助属性
//            private val delegate = Delegate()
            // p 的访问都会调用对应的 delegate 的getValue和setValue方法
//            var p: Type
//                set(value: Type) = delegate.setValue(..., value)
//                get() = delegate.getValue(...)
        }

        class Delegate {

            // getValue 包含了实现setter的逻辑
//        fun getValue(...): Type {
//            ...
//        }

            // setValue 包含了实现setter的逻辑
//        fun setValue(x: Int, any: Type) {
//            ...
//        }
//
        }

//        val foo = Foo()
        // 通过调用 delegate.getValue(...)来实现属性的修改
//        val oldValue = foo.p
        // 通过调用 delegate.setValue(...,newValue)来实现属性的修改
//        foo.p = newValue


        /**-------------------- 7.5.2 使用委托属性:惰性初始化和 by lazy() ----------------------*/
        // 惰性初始化时一种常见的模式,知道在第一次访问该属性的时候,才根据需要创建对象的一部分。

        class Email


        // 使用额外的 _emails 属性来实现惰性加载,在没有加载之前为null,然后加载为邮件列表

        // 代码清单7.17 使用支持属性来实现惰性初始化
        class Person(val name: String) {
            // _emails 属性用来保存数据,关联委托
            private var _emails: List<Email>? = null
            val emails: List<Email>
                get() {
                    if (_emails == null) {
                        // 访问时加载邮件
                        _emails = loadEmails(this)
                    }
                    //  如果已经加载,就直接返回
                    return _emails!!
                }

            fun loadEmails(person: Person): List<Email> {
                println("Load email for ${person.name}")
                return listOf()
            }
        }

        val p = Person("jingbin")
        p.emails// 第一次访问会加载邮件: Load email for jingbin
        p.emails
        /*
        * 这里使用了所谓的 支持属性技术。有一个属性 _emails 用来存储这个属性,而另一个email用来提供对属性的读取访问。
        * 这样代码有点啰嗦,而且线程不安全,kotlin有更好的方案,使用标准库函数 lazy 返回的委托。
        */


        // 代码清单7.18 用委托属性来实现惰性初始化
        class Person2(val name: String) {
            // lazy 的参数是一个Lambda,可以调用它来初始化这个值,且默认是线程安全的。
            val emails by lazy { loadEmail(this) }

            private fun loadEmail(person: Person2): List<Email> {
                println("Load email2 for ${person.name}")
                return listOf()
            }
        }


        /**-------------------- 7.5.3 实现委托属性 ----------------------*/
        // 代码清单7.19 使用 PropertyChangeSupport 的工具类
        open class PropertyChangeAware {

            protected val changeSupport = PropertyChangeSupport(this)

            fun addPropertyChangeListener(listener: PropertyChangeListener) {
                changeSupport.addPropertyChangeListener(listener)
            }

            fun removePropertyChangeListener(listener: PropertyChangeListener) {
                changeSupport.removePropertyChangeListener(listener)
            }
        }

        // 代码清单7.20 手工实现属性修改的通知
        class Person3(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
            var age: Int = age
                set(newValue) {
                    // field 标识符允许你访问属性背后的支持字段
                    val oldValue = field
                    field = newValue
                    changeSupport.firePropertyChange("age", oldValue, newValue)
                }

            var salary: Int = salary
                set(newValue) {
                    val oldValue = field
                    field = newValue
                    changeSupport.firePropertyChange("salary", oldValue, newValue)
                }
        }

        val p3 = Person3("jingbin", 30, 40000)
        // 关联监听器,用于监听属性修改
        p3.addPropertyChangeListener(PropertyChangeListener { it ->
            println("Property ${it.propertyName} changed"   "from ${it.oldValue} to ${it.newValue}")
        })
        p3.age = 35 // Property age changed form 30 to 35
        p3.salary = 60000 // Property salary changed form 40000 to 60000


        // 代码清单7.21 提过辅助类来实现属性变化的通知
        class ObservableProperty(val propName: String, var propValue: Int, val changeSupport: PropertyChangeSupport) {
            fun getValue(): Int = propValue
            fun setValue(newValue: Int) {
                val oldValue = propValue
                propValue = newValue
                changeSupport.firePropertyChange(propName, oldValue, newValue)
            }
        }

        class Person4(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
            val _age = ObservableProperty("age", age, changeSupport)
            var age: Int
                get() = _age.getValue()
                set(value) {
                    _age.setValue(value)
                }

            val _salary = ObservableProperty("salary", salary, changeSupport)
            var salary: Int
                get() = _salary.getValue()
                set(value) {
                    _salary.setValue(value)
                }
        }
        /*
        * 你创建了一个保存属性值的类,并在修改属性时自动触发更改通知。你删除了重复的逻辑代码,但是需要相当多的样板代码来为每个属性创建
        * ObservableProperty 实例,并把getter和setter委托给它。Kotlin的委托属性可以让你摆脱这些样板代码。
        */

        // 代码清单7.22 ObservableProperty 作为属性委托
//        class ObservableProperty2(var propValue: Int, val changeSupport: PropertyChangeSupport) {
//            operator fun getValue(p: Person5, prop: KProperty<*>): Int = propValue
//            operator fun setValue(p: Person5, prop: KProperty<*>, newValue: Int) {
//                val oldValue = propValue
//                propValue = newValue
//                changeSupport.firePropertyChange(prop.name, oldValue, newValue)
//            }
//        }

        // 代码清单7.23 使用委托属性来绑定更改通知
//        class Person5(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
//            var age: Int by ObservableProperty2(age, changeSupport)
//            var salary: Int by ObservableProperty2(salary, changeSupport)
//        }

        // 右边的对象被称为委托,Kotlin会自动将委托存储在隐藏的属性中,并在访问或修改属性时调用委托的geyValue,和setValue

        // 代码清单7.24 使用Delegates.observable来实现属性修改的通知
        class Person5(val name: String, age: Int, salary: Int) : PropertyChangeAware() {
            private val observer = { prop: KProperty<*>, oldValue: Int, newValue: Int ->
                changeSupport.firePropertyChange(prop.name, oldValue, newValue)
            }
            var age: Int by Delegates.observable(age, observer)
            var salary: Int by Delegates.observable(salary, observer)
        }

        /*
        * by 右边的表达式不一定是新创建的实例,也可以是函数调用、另一个属性、或任何其他表达式,
        * 只要这个表达式的值,是能够被编译器用正确的参数类型来调用getValue和setValue的对象。
        */


        /**-------------------- 7.5.4 委托属性的变换规则 ----------------------*/
//        class C{
//            var prop: Type by MyDelegate()
//        }
//        val c = C()

        /*
        * MyDelegate 实例会被保存到一个隐藏的属性中,它被称为<delegate>。编译器也会将用一个KProperty类型的对象来代表这个属性,它被称为<property>。
        * 编译器生成的代码如下:
        */
//        class C {
//            private val <delegate> = MyDelegate()
//            var prop: Type
//                get() = <delegate>.getValue(this, <property>)
//                set(value:Type) = <delegate>.setValue(this,<property>,value)
//        }

        /*
        * val x = c.prop  -> val x = <delegate>.getValue(c, <property>)
        * c.prop = x      -> <delegate>.setValue(c, <property>, x)
        */


        /**-------------------- 7.5.5 在 map 中保存属性值 ----------------------*/
        // 代码清单7.25 定义一个属性,把值存在map
        class Person6 {
            private val _attributes = hashMapOf<String, String>()
            fun setAttribute(attrName: String, value: String) {
                _attributes[attrName] = value
            }

            // 从map手动检索属性
            val name: String
                get() = _attributes["name"]!!
        }

        val p6 = Person6()
        val data = mapOf("name" to "jingbin", "company" to "ali")
        for ((attrName, value) in data) {
            p6.setAttribute(attrName, value)
        }
        println(p.name) // jingbin

        // 代码清单7.26 使用委托属性把值存到map中
        class Person7 {
            private val _attributes = hashMapOf<String, String>()
            fun setAttribute(attrName: String, value: String) {
                _attributes[attrName] = value
            }

            // 把 map 作为委托属性
            val name: String by _attributes
        }

        /**-------------------- 7.5.6 框架中的委托属性 ----------------------*/
        // 代码清单7.27 使用委托属性来访问数据库列
        // user 对应数据库中的表
//        object Users : IdTable() {
        // name 和 age 对应数据库表的列
//            val name: varchar("name", length = 50).index()
//            val age = ingeter("age")
//        }

        // 每一个User示例对应表中的一个实体
//        class User(id: EntityID) : Entity(id) {
        // name 的值是数据库中对应那个用户的值
//            var name: String by Users.name
//            var age: Int by Users.age
//        }

总结

  • Kotlin 允许使用对应名称的函数来重载一些标准的数学运算,但是不能定义自己的运算符。
  • 比较运算符映射为 equals和 compareTo 方法的调用。
  • 通过定义名为 get set contains 的函数,就可以让你自己的类与Kotlin 的集合一样,使用[]和 in 运算符。
  • 可以通过约定来创建区间,以及迭代集合和数组。
  • 解构声明可以展开单个对象用来初始化多个变量,这可以方便地用来从函数返回多个值。它们可以自动处理数据类,可以通过给自己的类定义名为 componentN 的函数来支持。
  • 委托属性可以用来重用逻辑,这些逻辑控制如何存储、初始化、访问和修改属性值,这是用来构建框架的一个强大的工具。
  • lazy 标准库函数提供了一种实现惰性初始化属性的简单方法。
  • Delegates.observable 函数可以用来添加属性更改的观察者。委托属性可以使用任意 map 来作为属性委托,来灵活来处理具有可变属性集的对象。

0 人点赞