Kotlin语法手册(一)

Kotlin语法手册(一)

在使用kotlin时,由于掌握的不够牢靠,好多时候也还是Java编程的习惯,浪费了kotlin提供的语言特性,方便性,间接性,在阅读一些Android开源库的时候,由于好多都是kotlin语法编写的,看的比较费劲,还得去查阅kotlin的语法,比较不方便,故把kotlin的语法记录下来,方便查阅温故,巩固自己的基础知识。

变量

kotlin 中,变量分为 可变变量(var)不可变变量(val) 两类。

  • val:不可变引用,对应的是 Java 中的 final 变量;使用 val 声明的变量不能在初始化之后再次赋值。
  • var:可变引用,对应的是 Java 中的非 final 变量;使用 var 声明的变量的值可以被改变。

不可变变量在赋值之后就不能再去改变它的状态了,因此不可变变量可以说是线程安全的,因为它们无法改变,所有线程访问到的对象都是同一个,因此也不需要去做访问控制。开发者应当尽可能地使用不可变变量,这样可以让代码更加接近函数式编程风格。

fun main() {
   
    //声明一个整数类型的不可变变量,初始化后intValue的值不能再改变了
    val intValue: Int = 100

    //声明一个双精度类型的可变变量
    var doubleValue: Double = 100.0
}

在声明变量时,通常不需要显式指明变量的类型,可以由编译器根据上下文自动推导出来。如果只读变量在声明时没有初始的默认值,则必须指明变量类型,且在使用前必须确保在各个分支条件下变量可以被初始化,否则编译器就会报异常。

数据类型

基本数据类型

在 kotlin中,一切都是对象,可以在任何变量上调用其成员函数和属性,并不区分基本数据类型和它的包装类。也就是说kotlin 没有像 Java 中那样的原始基本类型,但 byte、char、integer、float 或者 boolean 等类型仍然有保留,但是全部都作为对象存在。

    //在 kotlin 中,int、long、float 等类型仍然存在,但是是作为对象存在的

    val intIndex: Int = 100
    //等价于,编译器会自动进行类型推导
    val intIndex = 100

    //数字类型不会自动转型,必须要进行明确的类型转换
    val doubleIndex: Double = intIndex.toDouble()
    //以下代码会提示错误,需要进行明确的类型转换
    //val doubleIndex: Double = intIndex

    val intValue: Int = 1
    val longValue: Long = 1
    //以下代码会提示错误,因为两者的数据类型不一致,需要转换为同一类型后才能进行比较
    //println(intValue == longValue)

    //Char 不能直接作为数字来处理,需要主动转换
    val ch: Char = 'c'
    val charValue: Int = ch.toInt()
    //以下代码会提示错误
    //val charValue: Int = ch

    //不支持八进制
    //二进制
    val value1 = 0b00101
    //十六进制
    val value2 = 0x123

字符串

字符串用 String 类型表示。字符串是不可变的。字符串的元素:使用索引运算符访问: s[i];可以用 for 循环迭代字符串,也可以用 + 来连接字符串。

    val str = "hello"
    println(str[1])
    for (c in str) {
        println(c)
    }
    val str1 = str + " world"

kotlin 支持在字符串字面值中引用局部变量,只需要在变量名前加上字符 $ 即可,此外还可以包含用花括号{}括起来的表达式,此时会自动求值并把结果合并到字符串中。

    val intValue = 100
    //可以直接包含变量
    println("intValue value is $intValue") //intValue value is 100
    //也可以包含表达式
    println("(intValue + 100) value is ${intValue + 100}")   //(intValue + 100) value is 200

如果你需要在原始字符串中表示字面值($)字符(它不支持反斜杠转义),可以用下列语法:

    val price = "${'$'}100.99"
    println(price)  //$100.99

数组

数组在 Kotlin 中使用 Array 类来表示,它定义了 getset 函数(按照运算符重载约定这会转变为 [])以及 size 属性,以及一些其他有用的成员函数:

  1. arrayOf():使用库函数 arrayOf() 来创建一个数组并传递元素值给它,如: arrayOf(1, 2, 3) 就创建了 [1, 2, 3]
  2. arrayOfNulls:库函数 arrayOfNulls() 可以用于创建一个指定大小的、初始化所有元素都为空的数组。
  3. Array:Array是接受数组大小以及一个函数参数的构造函数,用作参数的函数能够返回给定索引的每个元素初始值,如下所示:
// 创建一个 Array<String> 数组大小为5,函数为(i * i).toString()的字符串数组
val asc = Array(5) { i -> (i * i).toString() }//["0", "1", "4", "9", "16"]
asc.forEach { println(it) }

基本数据类型数组

数组类型的类型参数如上面的Array<String>,始终会变成对象类型,因此声明 Array< Int > 将是一个包含装箱类型(java.lang.Integer)的数组,如果想要创建没有装箱的基本数据类型的数组,必须使用一个基本数据类型数组的特殊类IntArray、ByteArray、BooleanArray 等,这些类与 Array 并没有继承关系,但是它们有同样的方法属性集,它们也都有相应的工厂方法。

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// 大小为 5、值为 [0, 0, 0, 0, 0] 的整型数组
val arr = IntArray(5)

// 例如:用常量初始化数组中的值
// 大小为 5、值为 [42, 42, 42, 42, 42] 的整型数组
val arr = IntArray(5) { 42 }

// 例如:使用 lambda 表达式初始化数组中的值
// 大小为 5、值为 [0, 1, 2, 3, 4] 的整型数组(值初始化为其索引值)
var arr = IntArray(5) { it * 1 }

集合

kotlin中集合分为只读集合与可变集合,如下所示:

集合元素 只读 可变
List listOf mutableListOf、arrayListOf
Set setOf mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
Map mapOf mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf
  • List是一个有序集合,可通过索引访问元素。元素可以在 list 中出现多次。
  • Set是唯一元素的集合,一组无重复的对象。一般来说 set 中元素的顺序并不重要。例如,字母表是字母的集合(set)。
  • Map是一组键值对。键是唯一的,每个键都刚好映射到一个值。值可以重复

只读集合的可变性

只读集合不一定就是不可变的。例如,假设存在一个拥有只读类型接口的对象,该对象存在两个不同的引用,一个只读,一个可变,当可变引用修改了该对象后,这对只读引用来说就相当于“只读集合被修改了”,因此只读集合并不总是线程安全的。如果需要在多线程环境下处理数据,需要保证正确地同步了对数据的访问,或者使用支持并发访问的数据结构

例如,list1 和 list1 引用到同一个集合对象, list3 对集合的修改同时会影响到 list1

    val list1: List<String> = JavaMain.names
    val list3: MutableList<String> = JavaMain.names
    list1.forEach { it -> println(it) } //leavesC Ye
    list3.forEach { it -> println(it) } //leavesC Ye
    for (index in list3.indices) {
        list3[index] = list3[index].toUpperCase()
    }
    list1.forEach { it -> println(it) } //LEAVESC YE

集合与可空性

集合的可空性可以分为三种:

  1. 可以包含为 null 的集合元素
  2. 集合本身可以为 null
  3. 集合本身可以为 null,且可以包含为 null 的集合元素

例如,intList1 可以包含为 null 的集合元素,但集合本身不能指向 null;intList2 不可以包含为 null 的集合元素,但集合本身可以指向 null;intList3 可以包含为 null 的集合元素,且集合本身能指向 null

    //List<Int?> 是能持有 Int? 类型值的列表
    val intList1: List<Int?> = listOf(10, 20, 30, 40, null)
    //List<Int>? 是可以为 null 的列表
    var intList2: List<Int>? = listOf(10, 20, 30, 40)
    intList2 = null
    //List<Int?>? 是可以为 null 的列表,且能持有 Int? 类型值
    var intList3: List<Int?>? = listOf(10, 20, 30, 40, null)
    intList3 = null

其他数据类型

  • Any:Any 类型是 kotlin 所有非空类型的超类型,包括像 Int 这样的基本数据类型,如果把基本数据类型的值赋给 Any 类型的变量,则会自动装箱为java.lang.Integer的。
  • Any?:Any?类型是 kotlin 所有可空类型的超类型,如果想要使变量可以存储包括 null 在内的所有可能的值,则需要使用 Any?
  • Unit:Unit 类型类似于 Java 中的 void,可以用于函数没有返回值时的情况,如果函数返回值为 Unit,则可以省略该声明,Unit也可以作为类型参数来声明变量。
  • Nothing:Nothing 类型没有任何值,只有被当做函数返回值使用,或者被当做泛型函数返回值的类型参数使用时才会有意义

函数

kotlin 中的函数以关键字 fun 作为开头,函数名称紧随其后,再之后是用括号包裹起来的参数列表,如果函数有返回值,则再加上返回值类型,用一个冒号与参数列表隔开。

//fun 用于表示声明一个函数,double 是函数名,x表示传入参数,Int 表示函数的返回值类型是int型
fun double(x: Int): Int {
    return 2 * x
}

还有一种是表达式函数体,它是用单行表达式与等号定义的函数,表达式函数体的返回值类型可以省略,返回值类型可以自动推断的。如:fun double(x: Int) = x * 2

如果函数没有有意义的返回值,则可以声明为 Unit ,也可以省略 Unit,下面的三种写法是等价的:

        fun test(str: String, int: Int): Unit {
            println(str.length + int)
        }

        fun test(str: String, int: Int) {
            println(str.length + int)
        }

        fun test(str: String, int: Int) = println(str.length + int)

函数的参数

命名参数

kotlin 允许我们使用命名参数,即在调用某函数的时候,可以将函数参数名一起标明,从而明确地表达该参数的含义与作用,但是在指定了一个参数的名称后,之后的所有参数都需要标明名称。如下所示:

fun main() {
    //该写法是错误的,在指定了一个参数的名称后,之后的所有参数都需要标明名称
    compute(index = 110, "hello")
    
    compute(index = 120, value = "hello")
    compute(130, value = "hello")
}

fun compute(index: Int, value: String) {

}

默认参数

函数参数可以有默认值,当省略相应的参数时使用默认值。与其他语言相比,这可以减少重载数量。

fun main() {
    compute(24)
    compute(24, "world")
}

fun compute(age: Int, name: String = "hello") {

}

有默认参数时,可以省略的只有排在末尾的参数,其他位置的是不能省略的,如下所示:

fun main() {
    //错误,不能省略参数 name
    // compute(24)
    // compute(24,100)
    
    //可以省略参数 value
    compute("hello", 24)
}

fun compute(name: String = "hello", age: Int, value: Int = 100) {}

但是,如果使用命名参数,可以省略任何有默认值的参数,而且也可以按照任意顺序传入需要的参数。

fun main() {
    compute(age = 24)
    compute(age = 24, name = "hello")
    compute(age = 24, value = 90, name = "hello")
    compute(value = 90, age = 24, name = "hello")
}

fun compute(name: String = "hello", age: Int, value: Int = 100) {

}

可变参数

kotlin 的语法与 Java 有所不同,可变参数改为通过使用 varage 关键字声明

fun main() {
    compute()
    compute("hello")
    compute("hello", "world")
    compute("hello", "world", "kotlin")
}

fun compute(vararg name: String) {
    name.forEach { println(it) }
}

在 Java 中,可以直接将数组传递给可变参数,而 kotlin 要求显式地解包数组,以便每个数组元素在函数中能够作为单独的参数来调用,这个功能被称为展开运算符,使用方式就是在数组参数前加一个 *

fun main() {
    val names = arrayOf("hello", "world", "kotlin")
    compute(* names)
}

fun compute(vararg name: String) {
    name.forEach { println(it) }
}

局部函数

在Kotlin中,支持在函数中嵌套函数,被嵌套的函数称为局部函数

fun compute(name: String, country: String) {
    fun check(string: String) {
        if (string.isEmpty()) {
            throw IllegalArgumentException("参数错误")
        }
    }
    check(name)
    check(country)
}

check方法体是放在compute方法体中,check方法被称为局部方法或局部函数;check只能在compute中方法调用,在compute方法外调用,会引起编译错误。

控制流

IF表达式

在 Kotlin 中,if是一个表达式,即它会返回一个值。 因此就不需要三元运算符(条件 ? 然后 : 否则),因为普通的 if 就能胜任这个角色,故kotlin中没有三元运算符。

// 传统用法
var max = a 
if (a < b) max = b

// With else 
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}
 
// 作为表达式
val max = if (a > b) a else b

if 的分支可以是代码块,最后的表达式作为该块的值

//a,b就是该块儿最后返回的值
val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

如果你使用 if 作为表达式而不是语句(例如:返回它的值或者把它赋给变量),该表达式需要有 else 分支

when表达式

when 表达式与 Java 中的 switch/case 类似,但功能更为强大。when 既可以被当做表达式使用也可以被当做语句使用,when 将参数和所有的分支条件顺序比较直到某个分支满足条件,然后它会运行右边的表达式。

如果 when 被当做表达式来使用,符合条件的分支的值就是整个表达式的值。与 Java 的 switch/case 不同之处是 when 表达式的参数可以是任何类型,并且分支也可以是一个条件。

和 if 一样,when 表达式每一个分支可以是一个代码块,它的值是代码块中最后的表达式的值,如果其它分支都不满足条件将会求值于 else 分支。

如果 when 作为一个表达式使用,则必须有 else 分支, 除非编译器能够检测出所有的可能情况都已经覆盖了。如果很多分支需要用相同的方式处理,则可以把多个分支条件放在一起,用逗号分隔。

    val value = 2
    when (value) {
        in 4..9 -> println("in 4..9") //区间判断
        3 -> println("value is 3")    //相等性判断
        2, 6 -> println("value is 2 or 6")    //多值相等性判断
        is Int -> println("is Int")   //类型判断
        else -> println("else")       //如果以上条件都不满足,则执行 else
    }

fun main() {
    //返回 when 表达式
    fun parser(obj: Any): String =
            when (obj) {
                1 -> "value is 1"
                "4" -> "value is string 4"
                is Long -> "value type is long"
                else -> "unknown"
            }
}

when 语句也可以不带参数来使用

    when {
        1 > 5 -> println("1 > 5")
        3 > 1 -> println("3 > 1")
    }

For循坏

    //和java中使用方式很类似
    val list = listOf(1, 4, 10, 34, 10)
    for (value in list) {
        println(value)
    }

通过索引来遍历

    val items = listOf("H", "e", "l", "l", "o")
    //通过索引来遍历List
    for (index in items.indices) {
        println("${index}对应的值是:${items[index]}")
    }

也可以在每次循环中获取当前索引和相应的值

    val list = listOf(1, 4, 10, 34, 10)
    for ((index, value) in list.withIndex()) {
        println("index : $index , value :$value")
    }

也可以自定义循环区间

    for (index in 2..10) {
        println(index)
    }

while循环和do/while循环

两者的使用和Java中的使用相似。

    val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
    var index = 0
    while (index < list.size) {
        println(list[index])
        index++
    }

    val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
    var index = 0
    do {
        println(list[index])
        index++
    } while (index < list.size)

返回与跳转

Kotlin 有三种结构化跳转表达式(作用和java语言中的类似):

  • return 默认从最直接包围它的函数或者匿名函数返回
  • break 终止最直接包围它的循环
  • continue 继续下一次最直接包围它的循环
    在 kotlin 中任何表达式都可以用标签(label)来标记,标签的格式为标识符后跟 @ 符号,例如:abc@ 、fooBar@ 都是有效的标签
fun main() {
    fun1()
}

fun fun1() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    loop@ for (it in list) {
        if (it == 8) {
            continue
        }
        //当值是23的时候,退出标记的loop循环
        if (it == 23) {
            break@loop
        }
        println("value is $it")
    }
    println("function end")
}

通常情况下使用隐式标签更方便, 隐式标签与接受该 lambda 的函数同名,return也可添加标签限制(如下:)

fun fun3() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
        if (it == 8) {
            //这是就是在return上加了隐式标签forEach的限制,使之只在forEach当前循环中终止返回,效果同continue
            return@forEach
        }
        println("value is $it")
    }
    println("function end")
 //运行fun3方法的话,会输出以下结果:
//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun4() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach loop@{
        if (it == 8) {
            return@loop//同fun3一样,这里是添加了loop的标签显式标签
        }
        println("value is $it")
    }
    println("function end")
//运行fun4方法的话,会输出以下结果:
//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun5() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) {
            //局部返回到匿名函数的调用者,即 forEach 循环
            return
        }
        println("value is $value")
    })
    println("function end")
}
//运行fun5方法的话,会输出以下结果:
//value is 1
//value is 2
//value is 4
//value is 5
//function end

区间

Kotlin 可通过调用 kotlin.ranges 包中的 [rangeTo()] 函数及其操作符 .. 形式的轻松地创建两个值的区间。 通常,rangeTo() 会辅以 in!in 函数

if (i in 1..4) {  // 等同于 i>=1 && i <= 4
    print(i)
}

if (i in 1.rangeTo(4)) {  // 和上面是相同的
    print(i)
}

数字类型的 ranges 在被迭代时,等同于java中带索引的fori的循环的效果

for (i in 1..4) {
    print(i)
}

要反向迭代数字,请使用 [downTo]函数而不是 ..rangeTo()。

for (i in 4 downTo 1) print(i)

这是通过 [step]函数完成任意步长(不一定为 1 )迭代数字

for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)

以上声明的都是闭区间,如果想声明的是开区间),可以使用 until 函数

for (i in 1 until 4) {
    println(i)
}

空安全

在 kotlin 中,类型系统将一个引用分为可以容纳 null (可空引用)或者不能容纳 null(非空引用)两种类型。 常规的变量不能指向null,如果希望一个变量可以储存 null 引用,需要显式地在类型名称后面加上问号?

    //name变量不能被赋值为null
    var name: String = "hello"
    
    //name变量可以被赋值为null
    var name: String? = "hello"

kotlin 对可空类型的显式支持有助于防止 NullPointerException 导致的异常问题,编译器不允许不对可空变量做 null 检查就直接调用其属性。

var name: String? = "hello"

val l = name.length // 编译器会报错,因为name变量可能为null

在写代码的时候,我们可以对可空的变量做判空判断,kotlin也为我们提供了安全的调用的运算符

安全的调用

?.:如果变量值非空,则变量的方法或属性会被调用,否则直接返回 null。

//b变量声明的是可空类型的,当b!=null的时候,就返回的b.length的值
//当b=null时,b?.length就自动返回的null,可以看出这个表达式返回的类型是Int?
println(b?.length)

安全调用在链式调用中也很有用,例如,如果一个员工 Bob 可能会(或者不会)分配给一个部门, 并且可能有另外一个员工是该部门的负责人,那么获取 Bob 所在部门负责人(如果有的话)的名字,我们写作:bob?.department?.head?.name

如果任意一个属性(环节)为空,这个链式调用就会返回 null

如果要只对非空值执行某个操作,安全调用操作符可以与 [let] 一起使用:

val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
    item?.let { println(it) } // 输出 Kotlin 并忽略 null
}

安全调用也可以出现在赋值的左侧。这样,如果调用链中的任何一个接收者为空都会跳过赋值,而右侧的表达式根本不会求值:

// 如果 `person` 或者 `person.department` 其中之一为空,都不会调用该函数:
person?.department?.head = managersPool.getManager()

Elvis 操作符

当我们有一个可空的引用 b 时,如果 b 非空,我使用它;如果使用 b 是空的话,我们想使用某个非空的值,如下所示:

val l: Int = if (b != null) b.length else -1

除了可以用if表达式,这还可以通过 Elvis 操作符表达,写作 ?:

//当表达式b?.length为空(即b=null)的时候赋值为-1
val l = b?.length ?: -1

如果 ?: 左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。 请注意,当且仅当左侧为空时,才会对右侧表达式求值。

安全的类型转换

如果对象不是目标类型,那么常规类型转换可能会导致 ClassCastException。 另一个选择是使用安全的类型转换as?,如果尝试转换不成功则返回 null

//如果a不是Int类型的话,会返回null
val aInt: Int? = a as? Int

非空断言运算符!!

!!是将任何值转换为非空类型,若该值为空则抛出异常

//如果b=null,会抛出空指针NPE异常
val l = b!!.length

可空类型的扩展

为可空类型定义扩展函数是一种更强大的处理 null 值的方式,可以允许接收者为 null 的调用,并在该函数中处理 null ,而不是在确保变量不为 null 之后再调用它的方法

    //可以被正常调用而不会发生空指针异常
    val name: String? = null
    println(name.isNullOrEmpty()) //true

类型检测与类型转换

类型检查is!is 操作符

在运行时通过使用 is 操作符或其否定形式 !is 来检测对象是否符合给定类型

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // 与 !(obj is String) 相同
    print("Not a String")
}
else {
    print(obj.length)
}

智能转换

在许多情况下,不需要在 Kotlin 中使用显式转换操作符,因为编译器跟踪不可变值的 is-检测以及[显式转换],在需要时自动插入(安全的)转换:

fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x 自动转换为字符串
    }
}

if (x !is String) return
print(x.length) // x 自动转换为字符串

// `||` 右侧的 x 自动转换为字符串
if (x !is String || x.length == 0) return

// `&&` 右侧的 x 自动转换为字符串
if (x is String && x.length > 0) {
    print(x.length) // x 自动转换为字符串
}

//用在when表达式和while循环也是一样
when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

不安全转换操作符

如果转换是不可能的,转换操作符会抛出一个异常,因此,它是不安全的。 Kotlin 中的不安全转换由操作符 as完成;可空类型和不可空类型是不同的类型,不能转换,比如:StringString?是不同的类型。

安全转换操作符

为了避免抛出异常,可以使用安全转换操作符 as? ,它可以在失败时返回 null

//as?右边是非空类型的,声明是可空类型的,若用as就会抛出异常,这里使用as?可以返回null,所以结果是可空的
val x: String? = y as? String

作用域函数

kotlin标准库包含几个函数,它们的唯一目的是在对象的上下文中执行代码块。当对一个对象调用这样的函数并提供一个lambda表达式时,他会形成一个临时作用域。在此作用域中,可以访问该对象而无需其名称,这些函数称为作用域函数

这些函数基本上做了同样的事情:在一个对象上执行一个代码块。不同的是这个对象在块中如何使用,以及整个表达式的结果是什么。

下面是作用域函数的典型用法:

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

如果不使用 let 来写这段代码,就必须引入一个新变量,并在每次使用它时重复其名称。

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

作用域函数没有引入任何新的技术,但是它们可以使你的代码更加简洁易读。

区别

标准库中提供了几个作用域函数,他们本质上都非常相似,每个作用域函数之间有两个主要区别:

  • 引用上下文对象的方式
  • 返回值

上下文对象:this还是it

this

this关键字可看作为lambda 表达式的的接收着。runwith 以及 apply 通过关键字 this 引用上下文对象,在它们的 lambda 表达式中可以像在普通的类函数中一样访问上下文对象。在大多数场景,当你访问接收者对象时你可以省略 this,来让你的代码更简短。

val adam = Person("Adam").apply { 
    age = 20                       // 和 this.age = 20 或者 adam.age = 20 一样
    city = "London"
}
println(adam)
it

it关键字可看作为作为 lambda 表达式的参数。例如letalso 将上下文对象作为 lambda 表达式参数,如果没有指定参数名,对象可以用隐式默认名称 it 访问

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()

此外,当将上下文对象作为参数传递时,可以为上下文对象指定在作用域内的自定义名称。

fun getRandomInt(): Int {
    //自定义名称value
    return Random.nextInt(100).also { value ->
        writeToLog("getRandomInt() generated value $value")
    }
}

val i = getRandomInt()

返回值

根据返回结果,作用域函数可以分为以下两类:

  • applyalso 返回上下文对象。
  • letrunwith 返回 lambda 表达式结果.
返回上下文对象

applyalso 的返回值是上下文对象本身。

val numberList = mutableListOf<Double>()

//返回的是对象,可以链式调用
numberList.also { println("Populating the list") }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println("Sorting the list") }
    .sort()

当然,也还可以用在返回上下文对象的函数的 return 语句中

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()
返回表达式结果

letrunwith 返回 lambda 表达式的结果

val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run { 
    add("four")
    add("five")
    count { it.endsWith("e") }//返回的结果
}
//There are 3 elements that end with e.
println("There are $countEndsWithE elements that end with e.")

此外,还可以忽略返回值,仅使用作用域函数为变量创建一个临时作用域。

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    val firstItem = first()
    val lastItem = last()        
    println("First item: $firstItem, last item: $lastItem")
}

函数选择

前文提到了几个函数,那么怎么选择合适的作用域函数呢?,下面是列举了它们之间的主要区别表

函数 对象引用 返回值 是否是扩展函数
let it Lambda 表达式结果
run this Lambda 表达式结果
run - Lambda 表达式结果 不是:调用无需上下文对象
with this Lambda 表达式结果 不是:把上下文对象当做参数
apply this 上下文对象
also it 上下文对象

一下是对作用域函数的简短总结:

  • 对一个非空(non-null)对象执行 lambda 表达式:let
  • 将表达式作为变量引入为局部作用域中:let
fun main() {
    val nickName = "leavesC"
    val also = nickName.let {
        it.length
    }
    println(also) //7
}
  • 对象配置:apply
val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)
  • 对象配置并且计算结果:run
val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}
  • 在需要表达式的地方运行语句:非扩展的 run
val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
    println(match.value)
}
  • 附加效果:also
val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")
  • 一个对象的一组函数调用:with
val result = with(StringBuilder()) {
        append("leavesC")
        append("\n")
        for (letter in 'A'..'Z') {
            append(letter)
        }
        toString()
    }
    println(result)

takeIftakeUnless

除了作用域函数外,标准库还包含函数 takeIftakeUnless。这俩函数使你可以将对象状态检查嵌入到调用链中。
takeIf 接收一个返回值类型为 bool 的函数,当该参数返回值为 true 时返回接受者对象本身,否则返回 null。
takeUnless 的判断条件与 takeIf 相反。

fun main() {
    println(check("leavesC")) //7
    println(check(null)) //0
}

fun check(name: String?): Int {
    return name.takeIf { !it.isNullOrBlank() }?.length ?: 0
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容