前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kotlin | 7.运算符重载及其他约定

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

作者头像
Jingbin
发布2021-03-02 15:56:14
9260
发布2021-03-02 15:56:14
举报
文章被收录于专栏:Android 技术栈Android 技术栈

本章内容包括:

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

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 来作为属性委托,来灵活来处理具有可变属性集的对象。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 7.1 运算符
  • 7.2 重载比较运算符
  • 7.3 集合与区间的约定
  • 7.4 解构声明和组件函数
  • 7.5 重用属性访问的逻辑:委托属性
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档