Kotlin知识概括(九) —— 约定

前序

      Java在标准库中,有一些与特定的类相关联的语言特性。好比,实现 java.lang.Iterable 接口的对象能够在forEach循环中使用。Kotlin也提供不少相似原理的特性,可是是经过调用特定的函数,来实现特定的语言特性,这种技术称之为约定。(例如,实现名为plus特殊方法的类,能够在该类的对象上使用 + 运算符)java

      由于类实现的接口集是固定的,Kotlin不能为了实现某个语言特性,而修改现有的Java类。但也能够经过把任意约定的方法定义为Java类的扩展方法,使其具有Kotlin约定的能力。android

      Kotlin不容许开发者自定义本身的运算符,由于Kotlin限制了你能重载的运算符,以及运算符对应的函数名称。bash

算术运算符重载

      在Java中,只有基本数据类型才可使用算术运算符,String类型也仅局限于使用 + 运算符,对于其余类不能使用算术运算符。ide

      Kotlin中使用约定最直接的例子就是算术运算符,意味着只要实现约定对应的方法,就能够对任意类型使用算数运算符。约定对应的方法都须要使用operator关键字修饰的,表示你将该方法做为相应的约定的实现。函数

二元算术运算符

运算符 函数名 表达式 转换
*(乘法运算符) times a * b a.times(b)
/(除法运算符) div a / b a.div(b)
%(取模运算符) rem a % b a.rem(b)
+(加法运算符) plus a + b a.plus(b)
-(减法运算符) minus a - b a.minus(b)

对于自定义类型的算术运算符,与基本数据类型的算术运算符具备相同的优先级。post

      operator函数不要求两边运算数类型相同。但不可将两边运算数进行交换运算,由于Kotlin不自动支持交换性。想要支持交换性,须要在两边的运算类型中定义相应的算术运算符的函数。ui

      Kotlin不要求返回值类型必须和运算数类型相同。也容许对约定的函数进行重载,即定义多个参数类型不一样operator函数。this

data class Point(var x:Int,var y:Int)

operator fun Point.plus(point: Point):Point{
    return Point(x + point.x,y + point.y)
}

//定义另类的operator函数
operator fun Point.plus(value: Int){
    println("x = ${x + value} y = ${y + value}")
}

fun main(args:Array<String>){
    val point1 = Point(3,4)
    val point2 = Point(3,4)
    println(point1 + point2)
    println(point1 + 1)
}
复制代码

运算符函数与Java

      Java中调用Kotlin的运算符很是简单,只须要像普通函数同样调用运算符对应的函数。但因为Java中没有operator关键字,因此Java中定义约定的具体函数时,惟一的约束是须要参数的 类型 和 数量 匹配。spa

在Java中定义两个加法运算符的plus方法:.net

#daqi.java
public class Point {
    public int x;
    public int y;

    public Point(int x ,int y){
        this.x = x;
        this.y = y;
    }

    public Point plus(Point p){
        return  new Point(x + p.x, y + p.y);
    }

    public Point plus(int p){
        return  new Point(x + p, y + p);
    }

    @Override
    public String toString() {
        return "x = " + x + " , y = " + y;
    }
}
复制代码

在Kotlin中为Java类声明约定的扩展函数,并使用加法运算符:

#daqiKotlin.kt

//将约定的函数声明为Java类的扩展函数
operator fun Point.plus(longNum:Long):Point{
    return Point(this.x + longNum.toInt(), this.y + longNum.toInt())
}

fun main(args:Array<String>){
    var point1 = Point(3,4)
    var point2 = Point(4,5)
    //使用Java定义的运算符函数
    println(point1 + point2)
    println(point1 + 1)
    println(point2 + 1L)
}


复制代码

      扩展函数能够很好的对现有的Java类添加Kotlin运算符的能力,但仍是要听从扩展函数不能访问privateprotected修饰的属性或方法的特性。

复合辅助运算符

      Kotlin除了支持简单的算术运算符重载,还支持复合赋值运算符重载,即 += 、-=等复合赋值运算符。

运算符 函数名 表达式 转换
*= timesAssign a *= b a.timesAssign(b)
/= divAssign a /= b a.divAssign(b)
%= remAssign a %= b a.remAssign(b)
+= plusAssign a += b a.plusAssign(b)
-= minusAssign a -= b a.minusAssign(b)

      当在某类型中定义了返回该类型的基本算术运算符的operator函数,且右侧运算数的类型符合该operator函数的参数的状况下,可使用复合辅助运算符。例如,定义不一样参数类型的plus函数:

operator fun Point.plus(point: Point):Point{
    x += point.x
    y += point.y
    return this
}

operator fun Point.plus(value: Int):Point{
    x += value
    y += value
    return this
}
复制代码

借助plus函数使用 复合赋值运算符+= :

fun main(args: Array<String>) {
    var point1 = Point(3,4)
    var point2 = Point(4,5)
    point2 += point1
    point2 += 1
}
复制代码

      这意味着,使用复合辅助运算符时,基本算术运算符的方法和复合赋值运算符的方法均可能被调用。当存在符合两侧运算数类型的基本算术运算符的operator方法和复合赋值运算符的operator方法时,编译器会报错。解决办法是:

  • 将运算符转换为对应的operator方法,直接调用方法。
  • 用val替代var,使编译器调用复合赋值运算符的该operator方法(例如:plusAssign)

运算符与集合

      Kotlin标准库中支持集合的使用 + 、- 、+= 和 -= 来对元素进行增减。+ 和 - 运算符老是返回一个新的集合,+= 和 -= 运算符始终就地修改集合

一元运算符和位运算符

运算符 函数名 表达式 转换
+ unaryPlus +a a.unaryPlus()
- unaryMinus -a a.unaryMinus()
! not !a a.not()
++ inc a++、++a a.inc()
-- dec a--、--a a.dec()

      当定义incdec函数来重载自增和自减运算符时,编译器会自动支持与普通数字类型的前缀和后缀自增运算符相同的语义。例如,调用前缀形式 ++a,其步骤是:

  • 把 a.inc() 结果赋值给 a
  • 把 a 的新值做为表达式结果返回。

比较运算符

      与算术运算符同样,Kotlin容许对任意类型重载比较运算符(==、!=、>、<等)。能够直接使用运算符进行比较,不用像Java调用equalscompareTo函数。

等号运算符

      若是在Kotlin中使用 == 运算符,它将被转换成equals方法的调用。!=运算符也会被转换为equals方法的调用,但结果会取反。

      与其余运算符不一样,== 和 != 能够用于可空运算数,由于这些运算符会检查运算数是否为null。null == null 老是为 true。

表达式 转换
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null))

      当自定义重载equals函数时,能够参考data类自动生成的equals函数:

public boolean equals(@Nullable Object var1) {
  if (this != var1) {
     if (var1 instanceof Point) {
        Point var2 = (Point)var1;
        if (this.x == var2.x && this.y == var2.y) {
           return true;
        }
     }
     return false;
  } else {
     return true;
  }
}
复制代码
  • 当比较自身对象时,直接返回true。
  • 类型不一样,则直接返回false。
  • 依据关键字段进行判断,条件符合就返回true。

      Kotlin提供恒等运算符(===)来检查两个参数是不是同一个对象的引用,与Java的==运算符相同。但===!==(同一性检查)不可重载,所以不存在对他们的约定。

      == 运算符和 != 运算符只使用函数 equals(other: Any?): Boolean,能够覆盖它来提供自定义的相等性检测实现。不会调用任何其余同名函数(如 equals(other: Point))或 扩展函数,由于继承自Any类的实现始终优先于扩展函数和其余同名函数

排序运算符

      在Java中,类能够实现Comparable接口,并在compareTo方法中判断一个对象是否大于另外一个对象。但只有基本数据类型可使用 <>来比较,全部其余类型没有简明的语法调用compareTo方法,须要显式调用。

      Kotlin支持相同的Comparable接口(不管是Java的仍是Kotlin的Comparable接口),比较运算符将会被转换为compareTo方法。全部在Java中实现Comparable接口的类,均可以在Kotlin中使用比较运算符。

表达式 转换
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

      Kotlin标准库中提供compareValuesBy函数来简洁地实现compareTo方法。该方法接收两个进行比较的对象,和用于比较的数值的方法引用:

data class Point(var x:Int,var y:Int):Comparable<Point>{
    override fun compareTo(other: Point): Int {
        return compareValuesBy(this,other,Point::x,Point::y)
    }
}

fun main(args: Array<String>) {
    val point1 = Point(3,4)
    var point2 = Point(4,5)

    println("result = ${point1 < point2}")
}
复制代码

equals方法和compareTo方法,在父类中已经添加operator,重载时无需添加。

集合与区间的约定

      处理结合最多见的是经过下标获取和设置元素,以及检查元素是否属于当前集合。而这些操做在Kotlin中都提供相应的运算符语法支持:

  • 使用下标运算符a[b],获取或设置元素。
  • 使用in运算符,检查元素是否在集合或区间内,也能够用于迭代。

下标运算符

      使用下标运算符读取元素会被转换成get运算符方法的调用。当写入元素时,将调用set

表达式 转换
a[i] a.get(i)
a[i_1, ……, i_n] a.get(i_1, ……, i_n)
a[i] = b a.set(i, b)
a[i_1, ……, i_n] = b a.set(i_1, ……, i_n, b)

      Map也可使用下标运算符,将键做为下标传入到下标运算符中获取对应的value。对于可变的map,一样可使用下标运算符修改对应键的value值。

注:get的参数能够是任意类型,因此当对map使用下标运算符时,参数类型时键的类型。

in运算符

      in运算符用于检查某个对象是否属于集合。它是一种约定,相应的函数为contains

表达式 转换
a in c c.contains(a)

rangTo 约定

      当须要建立区间时,都是使用..运算符。..运算符是调用rangeTo函数的一种约定。

表达式 转换
start..end start.rangeTo(end)

能够为任何类定义rangeTo函数。可是,若是该类实现了Comparable接口,那么能够直接使用Kotlin标准库为Comparable接口提供的rangeTo函数来建立一个区间。

public operator fun <T : Comparable<T>> T.rangeTo(that: T): ClosedRange<T> = ComparableRange(this, that)
复制代码

使用Java8的LocalDate来构建一个日期的区间:

fun main(args: Array<String>) {
    val now = LocalDate.now()
    val vacation = now .. now.plusDays(10)
    println(now.plusWeeks(1) in vacation)
}
复制代码

..运算符注意点:

  • ..运算符的优先级低于算术运算符,但最好仍是把参数括起来以免混淆:
0 .. (n + 1)
复制代码
  • 区间表达式调用函数式Api时,必须先将区间表达式括起来,不然编译将不经过:
(0..10).filter { 
    it % 2 == 0
}.map { 
    it * it
}.forEach { 
    println(it)
}
复制代码

iterator 约定

      for循环中可使用in运算符来表示执行迭代。这意味着Kotlin的for循环将被转换成list.iterator()的调用,而后反复调用hasNextnext 方法。

iterator方法也是Kotlin中的一种约定,这意味iterator()能够被定义为扩展函数。例如:Kotlin标准库中为Java的CharSequence定义了一个扩展函数iterator,使咱们能遍历一个常规的Java字符串。

for(s in "daqi"){
    
}
复制代码

解构声明

      Kotlin提供解构声明,容许你展开单个复合值,并使用它来初始化多个单独的变量。

fun main(args: Array<String>) {
    val point = Point(3,4)
    val(x,y) = point
}   
复制代码

      解构声明看起来像普通的变量声明,但他的括号中存在多个变量。但其实解构声明也是使用了约定的原理,要在解构声明中初始化每一个变量,须要提供对应的componentN函数(其中N是声明中变量的位置)。

val point = Point(3,4)
val x = point.component1()
val y = point.component2()
复制代码

数据类

      Kotlin中提供一种很方便生成数据容器的方法,那就是将类声明为数据类,也就是data类。

编译器自动从数据类的主构造函数中声明的全部属性生成如下方法:

  • equals()/hashCode()
  • toString()
  • componentN() 按声明顺序对应于全部属性
  • copy()

同时数据类必须知足如下要求:

  • 主构造函数须要至少有一个参数(可使用默认参数来实现无参主构造函数)
  • 主构造函数的全部参数须要标记为 val 或 var
  • 数据类不能是抽象、开放、密封或者内部的

      equals方法会检查主构造函数中声明的全部属性是否相等;hashCode()会根据主构造函数中声明的全部属性生成一个哈希值;componentN()会按照主构造函数中声明的全部属性的顺序生成;toString()会按照如下格式"Point(x=3, y=4)"生成字符串。

      数据类体中有显式实现 equals()hashCode() 或者 toString(),或者这些函数在父类中有 final 实现,会使用现有函数;数据类不容许为 componentN() 以及 copy() 函数提供显式实现。

      若是该类不是数据类,要想该类的对象也能够应用于解构声明,须要手动声明对应的operator修饰的componentN()函数(成员函数和扩展函数均可以):

fun main() {
    val(x,y) = Piont(1,2)

}

class Piont(val x:Int,val y:Int){
    operator fun component1():Int{
        return x
    }

    operator fun component2():Int{
        return y
    }
}
复制代码

使用场景

  • 遍历map

      使用解构声明快速获取mapentry 的键和值,快速遍历。

for ((key, value) in map) {
   // 直接使用该 key、value
   
}
复制代码
  • 从函数中返回多个变量

      建立请求存储返回信息的数据类,在调用方法获取返回信息时,使用解构声明将其分红不一样的值:

data class Result(val resultCode: Int, val status: Int,val body:String)
fun getHttpResult(……): Result {
    // 各类计算

    return Result(resultCode, status,josnBody)
}

------------------------------------------------------------------
//获取返回值
val(resultCode, status,josnBody) = getHttpResult()
复制代码

      注意:咱们也可使用标准库中的 Pair 类做为返回值,来实现返回两个变量。

  • 在 lambda 表达式中解构

      和map遍历类似,就是将lambda中的Map.Entry参数进行解构声明:

val map = mapOf(1 to 1)
map.mapValues { (key, value) -> 
    "key = $key ,value = $value "
}
复制代码

注意

      因为数据类中componentN()是按照主构造函数中声明的全部属性的顺序对应生成的。也就是说component1()返回的是主构造函数中声明的第一个值,component2()返回的是主构造函数中声明的第二个值,以此类推。

对于解构声明中不须要的变量,能够用下划线取代其名称,Kotlin将不会调用相应的 componentN()

fun main(args: Array<String>) {
    val point = Point(3,4)
    val(_,y) = point
    println(y)
}   
复制代码

      不然,你想要的值在主构造函数中声明在第二个位置,而你不是使用下划线取代其名称取代第一个变量的位置时,解构声明将使用 component1()对值进行赋值,你将得不到你想要的值。

fun main(args: Array<String>) {
    val point = Point(3,4)
    //y轴坐标应该是第二个位置,但因为没有使用_占位,将使用component1()对其进行赋值,也就是使用x轴坐标对y坐标进行赋值。
    val(y) = point
    println(y)
}   
复制代码

中辍调用

      在提到解构声明的地方,每每伴随着中辍调用的出现。但中辍调用并非什么约定,是让含有infix 关键字修饰的方法,能够像基本算术运算符同样被调用。即忽略该调用函数的点与圆括号,将函数名放在目标对象和参数之间

//中辍调用
1 to "one"

//普通调用
1.to("one")
复制代码

中缀函数必须知足如下要求:

  • 成员函数或扩展函数
  • 只有一个参数
  • 参数不得接受可变参数且不能有默认值

使用场景

  • 区间

使用..运算符建立的区间是一个闭区间,当咱们须要建立倒序区间或者半闭区间,甚至是设置区间步长时,所使用到的downTountilstep 其实都不是关键字,而是一个个使用infix 关键字修饰的方法,只是使用中辍调用来进行呈现。

  • map

在建立map时,对key和vlaue使用中辍调用来添加元素,提升可读性。

val map = mapOf("one" to 1,"two" to 2)
复制代码

中辍调用优先级

      中缀函数调用的优先级低于算术操做符、类型转换以及 rangeTo 操做符。因此0 until n * 20 until (n * 2)等价。

      但中缀函数调用的优先级高于布尔操做符&& 与 ||、is 与 in 检测以及其余一些操做符。因此7 in 0 until 107 in (0 until 10)等价。

参考资料:

android Kotlin系列:

Kotlin知识概括(一) —— 基础语法

Kotlin知识概括(二) —— 让函数更好调用

Kotlin知识概括(三) —— 顶层成员与扩展

Kotlin知识概括(四) —— 接口和类

Kotlin知识概括(五) —— Lambda

Kotlin知识概括(六) —— 类型系统

Kotlin知识概括(七) —— 集合

Kotlin知识概括(八) —— 序列

Kotlin知识概括(九) —— 约定

Kotlin知识概括(十) —— 委托

Kotlin知识概括(十一) —— 高阶函数

Kotlin知识概括(十二) —— 泛型

Kotlin知识概括(十三) —— 注解

Kotlin知识概括(十四) —— 反射

相关文章
相关标签/搜索