Kotlin基础八

本文主要介绍Kotlin中自定义运算符和委托属性相关知识。包括自定义类型的算数、复合赋值、一元、比较运算符,集合中的约定运算符如[]、in等,还阐述了委托属性的实现原理、懒加载及属性值变化监听的写法,帮助开发者深入掌握Kotlin特性。

内容:

  • 自定义类型算数运算(二元)
  • 自定义类型复合赋值运算(二元)
  • 自定义一元运算符
  • 自定义比较运算符
  • 集合中的约定运算符
  • 委托属性

一  自定义类型算数运算

在Java中,全套的算术运算只能用于基本数据类型,+运算符可以与String值一起拼接字符串。但在Kotlin中我们可以自定义类型运算符,来实现对象之间的算术运算。

这里我们定义个了点的数据类:

data class Points(val x: Double, val y: Double) {

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

}复制代码

这个数据类里边定义一个方法,方法是用poerator关键字修饰,方法的名字是plus,方法的逻辑是两个点的坐标是相加返回一个新的点。

我们在Kotlin中就可以使用+代替调用这个方法,如:

val point1 = Points(10.0, 10.0)
val point2 = Points(5.0, 5.0)
Log.e("rrrrrrrrrr", (point1 + point2).toString())复制代码

这就是自定义类型运算符。

自定义类型运算符的写法:使用关键字poerator去修饰,方法名字是Kotlin约定好的。

注意:运算符不仅仅能声明为一个成员函数,也可以把它定义为一个扩展函数。自定义类型的运算符,基本上和与标准数字类型的运算符有着相同的优先级

那么对应不同的运算方法名字都有什么呢?


注意,Kotlin运算符不会自动支持交换性(交换运算符的左右两边),如定义一个点缩放之后新的点的坐标运算符
data class Points(val x: Double, val y: Double) {
    //自定义加号运算符
    operator fun plus(other: Points): Points {
        return Points(x + other.x, y + other.y)
    }
    //自定义*号运算符
    operator fun times(scale: Double): Points {
        return Points(x * scale, y * scale)
    }

}复制代码
//正确
point1*2.0
//错误,因为double类里边没有定义这个方法,除非你扩展这个方法
2.0*point1复制代码
注意:和普通的函数一样,可以重载operator函数:可以定义多个同名的,但参数类型不同的方法。在java中调用需要直接调用方法。

二 自定义类型复合赋值运算

像+=、-=等这些运算符被称为复合赋值运算符。如果你定义了一个返回值为Unit,名为plusAssign的函数,Kotiin将会在用到+=运算符的地方调用它。其他二元算术运算符也有命名相似的对应函数:如minusAssign、timesAssign等。

注意:当你在代码中用到+=的时候,理论上plus和plusAssig口都可能被调用。一般不同时写。

Kotlin标准库支持集合的这两种方法。+和-运算符总是返回一个新的集合。+=和-=运算符用于可变集合时,始终在一个地方修改它们;不可变集合时候不能使用这类的运算符
val arr1 = arrayListOf(1, 2, 3, 4)
//这里返回一个新集合,原集合arr1还是[1, 2, 3, 4]
val arr2 = arr1 + 5
//修改原来的集合 arr1 =[1, 2, 3, 4, 6]
arr1 += 6复制代码
val arr1 = arrayListOf(1, 2, 3, 4)
val arr2: Collection<Int> = arrayListOf(5, 6, 7)
arr1 + arr2 //创建一个新集合,原集合不改变

arr1+=arr2//改变原来的集合复制代码

三自定义一元运算符

重载一元运算符的过程与你在前面看到的方式相同:用预先定义的一个名称来声明函数(成员函数或扩展函数),并用修饰符operator标记。我们来看一个例子

坐标取反:

operator fun unaryMinus() =Points(-x, -y)复制代码
用于重载一元运算符的函数,没有任何参数。所有的一元运算符函数名字如下


四自定义比较运算符

与算术运算符一样,在Kotlin中,可以对任何对象使用比较运算符(==、!=、>、<等),而不仅仅限于基本数据类型。我们在之前就曾谈到过等式比较的话题,也己经看到,如果在Kotiin中使用==运算符,它将被转换成equals方法的调用。这只是我们要讨论的约定原
则中的一个。需要注意:===运算符不能被重载,它是比较对象是否是同一个地址。equals函数之所以被标记为override,那是因为与其他约定不同的是,这个方法的实现是在Any类中定义的(Kotlin中的所有对象都支持等式比较)。这也解释了为什么你不需要将它标记operator:
Any中的基本方法就己经标记。

五 集合中约定运算符

5.1[] 运算符

我们知道在java中获取或设置集合的值,我们是通过get或者set方法进行的。而对应的kotlin代码们我们是用运算符[]去获取或者设置的。如下代码:

val arr1 = arrayListOf(1, 2, 3, 4, 5)
//获取值
arr1[1]
//设置值
arr1[1]=10
val arr2 = hashMapOf(1 to "one", 2 to "two")
//获取值
arr2[1]
//设置值
arr2[1] = "ONE"复制代码

其实他们的本质就是[]运算符会调用get或者set方法。按照这样的逻辑,我们可以为我们自定义的Point类去实现一个这个运算符的方法。

data class Points(var x: Double, var y: Double) {

    operator fun get(index: Int): Double {
        return when (index) {
            0 -> x
            1 -> y
            else -> 0.0
        }
    }

    operator fun set(index: Int, value: Double) {
        return when (index) {
            0 -> x = value
            1 -> y = value
            else -> { }
        }
    }

}复制代码

这里就可以通过[] 运算符去获取或者设置值

val point1 = Points(10.0, 10.0)
Log.e("rrrrrrrr", point1[0].toString())
point1[0] = 11.0
Log.e("rrrrrrrr", point1[0].toString())复制代码

5.2 in运算符

在java中我们使用contains去判断一个集合中是否包含一个元素,而在kotlin中我们使用in关键字去判断一个对象是否在集合中。

val arr = arrayListOf(1, 12, 3, 4, 54, 5, 2)
1 in arr复制代码

in运算符对应调用的方法就是contains , in右边的对象将会调用contains函数,in左边的对象将会作为函数入参。

5.3rangeTo的约定

在java中表示一个区间我也不会,在kotlin中去表示一个区间使用..去表示,..运算符调用rangeTo函数的一个简洁方法

5.4for的约定

在Kotlin中,for循环中也可以使用in运算符,和做区间检查一样。但是在这种情况下它的含义是不同的:它被用来执行迭代。这意味着一个诸如for(x  In list){...}将被转换成list.iterator()的调用,然后就像在Java中一样,在它上面重复调用hasNext和next方法

5.5解构声明

val point1 = Points(10.0, 10.0)

val (x, y) = point1复制代码

上边的代码就是解构声明,一个解构声明看起来像一个普通的变量声明,但它在括号中有多个变量。它也是用了约定的原理。要在解构声明中初始化每个变量,将调用名为componentN的函数,其中N是声明中变量的位置。

val x1 = point1.component1()
val y1 = point1.component2()复制代码
对于数据类,编译器为每个在主构造方法中声明的属性生成一个componentN函数,

对于非数据类我们可以自己写下这样的约定函数:

class Client(val name: String,val address: String) {
    operator fun component1() = name
    operator fun component2() = address
}
复制代码

5.6 解构声明和循环

val map = TreeMap<String,String>()  map.put("1","1.1")
 for ((key,value) in map) { 
 Log.e("rrrr","key${key}===value${value}")
 }复制代码

这个就是解构声明和循环的配合达到的效果。一个是迭代一个对象,另一个是用于解构声明。Kotiin标准库给map增加了一个扩展的iterator函数,用来返回map条目的法代器。

六委托属性

在第四篇的时候讲解了委托类的一些语法和一些优势,知道了kotlin对委托有着原生的支持,现在我们再去了解一下委托属性

6.1委托属性的实现原理

类的属性的get和set方法是调用别的类的get和set方法获取到的,属性的值没有储存到本类的特点叫做属性的委托。

委托需要储存值的类必须有对应的getValue 和setValue方法方法。

如下代码:

class Client(name: String) {
    var name by Provider(name)

}复制代码

这里的name是委托属性,它把值储存到Provider类中,在编译的时候上边的代码会被编译成如下代码,都是自动生成的代码:

class Client(name: String) {

    private val delegate = Provider(name)

    var name: String 
     get() = delegate.getValue(....)
     set(value) = delegate.setValue(....)
}复制代码

这里会生成一个新的变量,初始化值是by后边的对象,而被委托的属性调用生成新变量值得get和set方法,这就是委托属性。

对应的provider代码如下:

class Provider(var name: String) 

     //一个用于接收属性的实例,用来设置或读取属性,另一个用于表示属性本身
    operator fun getValue(client: Client, property: KProperty<*>): String {

        return name
    }
     //一个用于接收属性的实例,用来设置或读取属性,另一个用于表示属性本身    operator fun setValue(client: Client, property: KProperty<*>, name: String) {
        this.name = name
    }


}复制代码

这就是委托属性,但是每次都要写类似Provider这样的类,很麻烦,Kotlin给我们提供了更加简便的方法:

class Client(name: String) {

    val address  by lazy {getAll()}
    private fun getAll(): String {
       return "1000"
    }

}复制代码
这里使用标准库函数lazy返回你本来要写的委托类。返回的委托类对象只有一个getValue且方法,所以只能对val的属性进行委托。lazy的参数是一个lambda,可以调用它来初始化值。这个lambda只有在第一次调用的时候才会去调用,之后就会直接返回,有此实现懒加载。

到此你已经了解了属性的委托原理,以及属性的懒委托,接下来还有一个去实现属性值变化的监听的代码:

open class PropertyChangeAware {

    protected val changeSupport = PropertyChangeSupport(this)

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

    fun removePropertyChangeListener(listener: PropertyChangeListener){
        changeSupport.removePropertyChangeListener(listener)
    }
}
复制代码
class Person(val name: String, arg: Int, gongzi: Int) : PropertyChangeAware() {

    private val observer = { prop: KProperty<*>, oldValue: Int, newValue: Int ->
        changeSupport.firePropertyChange(prop.name, oldValue, newValue)
    }


    var arg: Int by  Delegates.observable(arg, observer)

    var gongzi: Int by Delegates.observable(gongzi, observer)

}复制代码

使用:

val person = Person("张三", 20, 10000)
//监听属性值的变化,然后进行回调
person.addPropertyChangeListener(PropertyChangeListener { evt -> Log.e("rrrrrrr",evt.propertyName+evt.newValue) })复制代码

小结:

  • 引用类型加,减,乘,除,取模的运算逻辑自定义
  • 引用类型的+= 运算符的使用,以及在集合中的使用
  • 一元运算符的自定义
  • 比较运算符equal方法的讲解
  • 集合中[] 和 in 对用的方法 
  • 区间对应的方法
  • 委托属性,委托属性懒加载,委托属性加上监听属性值变化回掉写法


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值