4

Kotlin语法手册(一) - InfoQ 写作平台

 2 years ago
source link: https://xie.infoq.cn/article/33b1a6912775183afb1f0461f
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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 类来表示,它定义了 get 与 set 函数(按照运算符重载约定这会转变为 [])以及 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 是一个有序集合,可通过索引访问元素。元素可以在 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: Intif (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)的时候赋值为-1val l = b?.length ?: -1

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

安全的类型转换

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

//如果a不是Int类型的话,会返回nullval 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) returnprint(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 关键字可看作为 lambda 表达式的的接收着。runwith 以及 apply 通过关键字 this 引用上下文对象,在它们的 lambda 表达式中可以像在普通的类函数中一样访问上下文对象。在大多数场景,当你访问接收者对象时你可以省略 this,来让你的代码更简短。

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

it 关键字可看作为作为 lambda 表达式的参数。例如let 及 also 将上下文对象作为 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()

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

  • apply 及 also 返回上下文对象。

  • letrun 及 with 返回 lambda 表达式结果.

返回上下文对象

apply 及 also 的返回值是上下文对象本身。

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()
返回表达式结果

letrun 及 with 返回 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")}

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

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

  • 对一个非空(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)

takeIf 与 takeUnless

除了作用域函数外,标准库还包含函数 takeIf 及 takeUnless。这俩函数使你可以将对象状态检查嵌入到调用链中。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}

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK