写点什么

Kotlin 语法手册(一)

  • 2022 年 2 月 12 日
  • 本文字数:12066 字

    阅读完需:约 40 分钟

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 类来表示,它定义了 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

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 表达式的参数。例如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}
复制代码


用户头像

还未添加个人签名 2020.10.09 加入

Android领域,打工人

评论

发布
暂无评论
Kotlin语法手册(一)