1 条件与循环
1.1 If 表达式
if else同Java,区别有两个:
1.使用if else代替Java的三目运算(a > b? a:b)
2.if
表达式的分支可以是代码块,这种情况最后的表达式作为该块的值
var max = a
if (a < b) max = b
// With else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// 作为表达式,Kotlin使用if else代替Java的三目运算(a > b? a:b)
val max = if (a > b) a else b
// if 表达式的分支可以是代码块,这种情况最后的表达式作为该块的值,
// 例如,如果将If用作表达式,用于返回其值或将其赋值给变量,则else分支是必需的。
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
1.2 When 表达式
类比Java的switch,但key值不局限int类型,也不需要break。注意:Kotlin版本1.7之后必须写else或者穷举全部类型,否则报错无法编译
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> {
print("x is neither 1 nor 2")
}
}
when
将它的参数与所有的分支条件顺序比较,直到某个分支满足条件。
when
既可以作为表达式使用也可以作为语句使用。如果它被当做表达式, 第一个符合条件的分支的值就是整个表达式的值,如果当做语句使用, 则忽略个别分支的值。 类似于 if
,每一个分支可以是一个代码块,它的值是块中最后的表达式的值。
如果其他分支都不满足条件将会求值 else
分支。 如果 when
作为一个表达式使用,那么必须有 else
分支, 除非编译器能够检测出所有的可能情况都已经覆盖了, 例如,对于 枚举(enum)类条目与密封(sealed)类子类型]。
enum class Bit {
ZERO, ONE
}
val numericValue = when (getRandomBit()) {
Bit.ZERO -> 0
Bit.ONE -> 1
// 'else' is not required because all cases are covered
}
// 1.在when语句中,else分支在以下条件下是必需的:
// 当具有布尔、枚举或密封类型的case或其可为null的对应项时
// when的分支并没有涵盖这个case的所有可能案例
enum class Color {
RED, GREEN, BLUE
}
when (getColor()) {
Color.RED -> println("red")
Color.GREEN -> println("green")
Color.BLUE -> println("blue")
// 'else' is not required because all cases are covered
}
when (getColor()) {
Color.RED -> println("red") // no branches for GREEN and BLUE
else -> println("not red") // 'else' is required
}
// 2.要定义多个case的常见行为,请使用逗号在一行中组合它们的条件:
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
// 3.可以用任意表达式(而不只是常量)作为分支条件
when (x) {
s.toInt() -> print("s encodes x")
else -> print("s does not encode x")
}
// 4.还可以检测一个值在(in)或者不在(!in)一个区间或者集合中:
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
//5.另一种选择是检测一个值是(is)或者不是(!is)一个特定类型的值。
//注意: 由于智能转换,你可以访问该类型的方法与属性而无需任何额外的检测:
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
// 6.when 也可以用来取代 if-else if 链。
//如果不提供参数,所有的分支条件都是简单的布尔表达式,而当一个分支的条件为真时则执行该分支:
when {
x.isOdd() -> print("x is odd")
y.isEven() -> print("y is even")
else -> print("x+y is odd")
}
// 7.可以使用以下语法将 when 的主语(subject,译注:指 when 所判断的表达式)捕获到变量中:
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
// 在 when 主语中引入的变量的作用域仅限于 when 主体。
1.3 For 循环
for
循环可以对任何提供迭代器(iterator)的对象进行遍历,这相当于像 C# 这样的语言中的 foreach
循环。 for
的语法如下所示:
for (item in collection) print(item)
for (item: Int in ints) {
// ……
}
如上所述,for
可以循环遍历任何提供了迭代器的对象。这意味着:
- 有一个成员函数或者扩展函数
iterator()
返回Iterator<>
:- 有一个成员函数或者扩展函数
next()
- 有一个成员函数或者扩展函数
hasNext()
返回Boolean
。
- 有一个成员函数或者扩展函数
这三个函数都需要标记为 operator
。
如需在数字区间上迭代,请使用区间表达式:
fun main() {
for (i in 1..3) {
// 输出1-3
println(i)
}
for (i in 6 downTo 0 step 2) {
// 输出6、4、2、0
println(i)
}
val array = arrayOf("a", "b", "c")
// 对区间或者数组的 for 循环会被编译为并不创建迭代器的基于索引的循环。
// 如果你想要通过索引遍历一个数组或者一个 list,你可以这么做:
for (i in array.indices) {
println(array[i])
}
// 或者你可以用库函数 withIndex:
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
}
1.4 while 循环
while和do-while循环在满足条件时连续执行其身体。它们之间的区别在于条件检查时间:
while检查条件,如果条件满足,则执行body,然后返回条件检查。
dowhile执行主体,然后检查条件。如果满足,循环将重复。因此,不管条件如何,do-while的主体至少执行一次。
while (x > 0) {
x--
}
do {
val y = retrieveData()
} while (y != null) // y 在此处可见
1.5 循环中的 break 与 continue
在循环中 Kotlin 支持传统的 break
与 continue
操作符。
2 返回与跳转
Kotlin 有三种结构化跳转表达式(基本同Java):
return
默认从最直接包围它的函数或者匿名函数返回。break
终止最直接包围它的循环。continue
继续下一次最直接包围它的循环。
所有这些表达式都可以用作更大表达式的一部分:
val s = person.name ?: return
这些表达式的类型是 Nothing 类型。
2.1 Break 与 Continue 标签
在 Kotlin 中任何表达式都可以用标签来标记。 标签的格式为标识符后跟 @
符号,例如:abc@
、fooBar@
。 要为一个表达式加标签,我们只要在其前加标签即可。
// 用标签限定 break 或者 continue
// 标签限定的 break 跳转到刚好位于该标签指定的循环后面的执行点。
// continue 继续标签指定的循环的下一次迭代。
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}
2.2 返回到标签
Kotlin 中函数可以使用函数字面量、局部函数与对象表达式实现嵌套。 标签限定的 return
允许我们从外层函数返回。 最重要的一个用途就是从 lambda 表达式中返回。回想一下我们这么写的时候, 这个 return
表达式从最直接包围它的函数——foo
中返回:
fun foo() {
listOf(1, 2, 3, 4, 5).forEach {
// 非局部直接返回到 foo() 的调用者
if (it == 3) return
print(it)
}
println("this point is unreachable")
}
// 注意,这种非局部的返回只支持传给内联函数的 lambda 表达式。
// 如需从 lambda 表达式中返回,可给它加标签并用以限定 return。
fun foo1() {
listOf(1, 2, 3, 4, 5).forEach lit@{
// 局部返回到该 lambda 表达式的调用者——forEach 循环
if (it == 3) return@lit
print(it)
}
print(" done with explicit label")
}
// 通常情况下使用隐式标签更方便,因为该标签与接受该 lambda 的函数同名。
fun foo2() {
listOf(1, 2, 3, 4, 5).forEach {
// 局部返回到该 lambda 表达式的调用者——forEach 循环
if (it == 3) return@forEach
print(it)
}
print(" done with implicit label")
}
// 或者,我们用一个匿名函数替代 lambda 表达式。
// 匿名函数内部的 return 语句将从该匿名函数自身返回
fun foo3() {
listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
// 局部返回到匿名函数的调用者——forEach 循环
if (value == 3) return
print(value)
})
print(" done with anonymous function")
}
// 请注意,前面三个示例中使用的局部返回类似于在常规循环中使用 continue。
// 并没有 break 的直接等价形式,不过可通过增加
// 另一层嵌套 lambda 表达式并从其中非局部返回来模拟:
fun foo4() {
run loop@{
listOf(1, 2, 3, 4, 5).forEach {
// 从传入 run 的 lambda 表达式非局部返回
if (it == 3) return@loop
print(it)
}
}
print(" done with nested loop")
}
fun main() {
foo()
foo1()
foo2()
foo3()
foo4()
}
当要返一个回值的时候,解析器优先选用标签限定的返回:
return@a 1
这意味着“返回 1
到 @a
”,而不是“返回一个标签标注的表达式 (@a 1)
”。
3 异常
3.1 异常类
Kotlin 中所有异常类继承自 Throwable
类。 每个异常都有消息、堆栈回溯信息以及可选的原因。
// 1.使用 throw 表达式来抛出异常:
fun main() {
throw Exception("Error!")
}
// 2.使用 try……catch 表达式来捕获异常:
try {
// 一些代码
} catch (e: SomeException) {
// 处理程序
} finally {
// 可选的 finally 块
}
可以有零到多个 catch
块,finally
块可以省略。 但是 catch
与 finally
块至少需有一个。
3.2 Try 是一个表达式
try
是一个表达式,意味着它可以有一个返回值:
val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }
try
-表达式的返回值是 try
块中的最后一个表达式或者是(所有)catch
块中的最后一个表达式。 finally
块中的内容不会影响表达式的结果。
3.3 受检异常
Kotlin 没有受检异常。这其中有很多原因,但我们会提供一个简单的示例 that illustrates why it is the case。
以下是 JDK 中 StringBuilder
类实现的一个示例接口:
Appendable append(CharSequence csq) throws IOException;
这个签名是说,每次我追加一个字符串到一些东西(一个 StringBuilder
、某种日志、一个控制台等)上时,我就必须捕获 IOException
。 为什么?因为相应实现可能正在执行 IO 操作(Writer
也实现了 Appendable
)。 其结果是这种代码随处可见:
try {
log.append(message)
} catch (IOException e) {
// 必须要安全
}
3.4 Nothing 类型
在 Kotlin 中 throw
是表达式,所以你可以使用它(比如)作为 Elvis 表达式的一部分:
val s = person.name ?: throw IllegalArgumentException("Name required")
throw
表达式的类型是 Nothing
类型。 这个类型没有值,而是用于标记永远不能达到的代码位置。 在你自己的代码中,你可以使用 Nothing
来标记一个永远不会返回的函数:
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
当你调用该函数时,编译器会知道在该调用后就不再继续执行了:
val s = person.name ?: fail("Name required")
println(s) // 在此已知“s”已初始化
当处理类型推断时还可能会遇到这个类型。这个类型的可空变体 Nothing?
有一个可能的值是 null
。如果用 null
来初始化一个要推断类型的值,而又没有其他信息可用于确定更具体的类型时,编译器会推断出 Nothing?
类型:
val x = null // “x”具有类型 `Nothing?`
val l = listOf(null) // “l”具有类型 `List<Nothing?>