一、重载算术运算符
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 {
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
可以看到随着属性更改后,监听器在不断回调