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
属性,以及一些其他有用的成员函数:
-
arrayOf():使用库函数 arrayOf() 来创建一个数组并传递元素值给它,如:
arrayOf(1, 2, 3)
就创建了[1, 2, 3]
。 -
arrayOfNulls:库函数
arrayOfNulls()
可以用于创建一个指定大小的、初始化所有元素都为空的数组。 - 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
集合与可空性
集合的可空性可以分为三种:
- 可以包含为 null 的集合元素
- 集合本身可以为 null
- 集合本身可以为 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完成;可空类型和不可空类型是不同的类型,不能转换,比如:String
和String?
是不同的类型。
安全转换操作符
为了避免抛出异常,可以使用安全转换操作符 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 表达式的的接收着。run
、with
以及 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
返回上下文对象。 -
let
、run
及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()
返回表达式结果
let
、run
及 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")
}
函数选择
前文提到了几个函数,那么怎么选择合适的作用域函数呢?,下面是列举了它们之间的主要区别表
函数 | 对象引用 | 返回值 | 是否是扩展函数 |
---|---|---|---|
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)
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
}