流程控制语句是编程语言中的核心之一。可以分为分支语句、循环语句和跳转语句。本文将详细介绍一下 Kotlin 中的流程控制语句。

If 表达式

在Kotlin中一切都是表达式,也就是说一切都返回一个值。如果 if 条件不含有一个 exception ,那我们可以像我们平时那样使用它:

if(x > 0) {
toast("x is greater than 0")
} else if(x == 0) {
toast("x equals 0")
} else {
toast("x is smaller than 0")
}

我们也可以把结果赋值给一个变量:

val res = if (x != null && x.size() >= days) x else null

这也说明我也不需要像 C# 那样要有一个三元操作符,因为我们可以使用 if 来简单的实现同样的功能:

val z = if (condition) x else y

所以 if 表达式总是返回一个 value 。如果一个分支返回了 Unit ,那整个表达式也将返回 Unit 。而当使用 if 做表达式时,必须要有 else

When 表达式

when 表达式与C#中的 switch/case 类似,但是要强大得多。when 会去试图匹配所有可能的分支直到找到满意的一项。然后它会运行右边的表达式。与C#的 switch/case 不同之处是参数可以是任何类型,并且分支也可以是一个条件。

对于默认的选项,我们可以增加一个 else 分支,它会在前面没有任何条件匹配时再执行。条件匹配成功后执行的代码也可以是代码块:

when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> {
print("I'm a block")
print("x is neither 1 nor 2")
}
}

因为它是一个表达式,它也可以返回一个值。我们需要考虑什么时候作为一个表达式使用,它必须要覆盖所有分支的可能性或者实现 else 分支。否则它不会被编译成功:

val result = when (x) {
0, 1 -> "binary"
else -> "error"
}

如上所示,条件也可以是一系列被逗号分割的值。但是它可以有更多的匹配方式。比如,我们可以检测参数类型并进行判断:

when(view) {
is TextView -> view.setText("I'm a TextView")
is EditText -> toast("EditText value: ${view.getText()}")
is ViewGroup -> toast("Number of children: ${view.getChildCount()} ")
else -> view.visibility = View.GONE
}

再条件右边的代码中,参数会被自动转型,所以你不需要去明确地做类型转换。

它还让检测参数否在一个数组范围甚至是集合范围:

val cost = when(x) {
in 1..10 -> "cheap"
in 10..100 -> "regular"
in 100..1000 -> "expensive"
in specialValues -> "special value!"
else -> "not rated"
}

你甚至可以从对参数做需要的几乎疯狂的检查摆脱出来:

valres = when {
x in 1..10 -> "cheap"
s.contains("hello") -> "it's a welcome!"
v is ViewGroup -> "child count: ${v.getChildCount()}"
else -> ""
}

For 循环

for 循环可以对所有提供迭代器的变量进行迭代。等同于 C# 等语言中的 foreach,语法形式如下:

for (item in collection) {
print(item)
}

如果你想使用索引的形式来迭代,则可以使用 ranges 模式(后面会详细介绍):

for (index in 0..viewGroup.getChildCount() - 1) {
val view = viewGroup.getChildAt(index)
view.visibility = View.VISIBLE
}

当我们迭代一个 array 或者 list 时,可以通过 indices 属性来更加简单的获取索引,所以上面的方式并不是必要的:

for (i in array.indices)
print(array[i])

或者,您也可以使用 withIndex 函数,会返回索引和对应的值:

for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}

当你使用 for 来迭代 array 或者 list 时,编译器会自动将其转化为索引的形式,并不会有创建迭代器的开销。

Ranges

Range 表达式使用一个 .. 操作符,它是被定义实现了一个 RangTo 方法。Ranges 帮助我们使用很多富有创造性的方式去简化我们的代码。比如我们可以把它:

if(i >= 0 && i <= 10)
println(i)

转化成:

if (i in 0..10)
println(i)

Range 被定义为可以被比较的任意类型,但是对于数字类型(IntRange, LongRange, CharRange),编译器会对其进行优化,将它转换为简单的类似于 C# 中的 for,使用索引的形式来迭代,避免额外的开销。

Ranges 默认情况下是自动增长的:

for (i in 1..4) print(i) // prints "1234"

for (x in 1.0..2.0) print("$x ") // prints "1.0 2.0 "

所以如果像以下的代码:

for (i in 10..0)
println(i) // prints nothing

它就不会做任何事情,但你也可以使用 downTo 函数:

for(i in 10 downTo 0)
println(i) // prints "543210"

我们也可以在 Ranges 中使用 step 来定义一个增长值:

for (i in 1..4 step 2) print(i) // prints "13"

for (i in 4 downTo 1 step 2) print(i) // prints "42"

for (i in 1.0..2.0 step 0.3) print("$i ") // prints "1.0 1.3 1.6 1.9 "

如果你想去创建一个 open range(不包含最后一项,类似数学中的开区间),则可以使用 until 函数:

for (i in 0 until 4) println(i)

这一行会打印从0到3,但是会跳过最后一个值。这也就是说 0 until 4 == 0..3 。在一个list中迭代时,使用 (i in 0 until list.size)(i in 0..list.size - 1) 更加容易理解。

就如之前所提到的,使用 Ranges 确实是富有创造性的方式。比如,一个简单的方式去从一个 ViewGroup 中得到一个 Views 列表可以这么做:

val views = (0..viewGroup.childCount - 1).map { viewGroup.getChildAt(it) }

混合使用 Ranges 和 函数操作符,可以节省很多代码,不用我们显示的去迭代一个集合,以及明确地去创建一个我们用来添加 views 的 list,所有的事情都在一行代码中完成了。

while 和 do/while 循环

你也可以使用 while 循环,尽管它们两个都不是特别常用的。它们通常可以更简单、视觉上更容易理解的方式去解决一个问题,两个例子:

while(x > 0){
x--
} do{
val y = retrieveData()
} while (y != null) // y在这里是可见的!

whiledo...while 和其它语言没什么区别。

返回与跳转

Kotlin 有三个流程跳转表达式:

  • return 从最近的函数或者匿名函数中返回。
  • break 终止最近的循环。
  • continue 跳出本次循环,执行下一次循环。

而它们都可以做为其它表达式的一部分来执行:

val s = person.name ?: return

Break 和 Continue 标签

在 kotlin 中,所有的表达式都可以添加一个标签,标签通过在末尾添加一个 @ 符号来表示,比如:abc@foo@ 等都是有效的标签,然后将标签放在我们需要的表达式前面即可:

loop@ for (i in 1..100) {
// ...
}

现在,我们可以为 break 指定我们标记的表达式:

loop@ for (i in 1..100) {
for (j in 1..100) {
if (...) break@loop
}
}

这样,break 终止的便不再是最近的循环,而是 loop@ 所指定的循环。

Return 标签

在 Kotlin 中,字面函数,局部函数,以及对象表达式中,函数都可以嵌套,而结合 lable 可以让我们从外部函数返回,更重要的是可以从 lambda 表达式中返回,看我们之前下的例子:

fun main(args: Array<String>) {
foo()
} val ints = listOf(0, 1, 2, 3)
fun foo() {
ints.forEach() {
if (it == 1) return
println(it)
}
}

这段代码只会输出一个 0 ,因为 return 表达式返回的是最近的闭合函数,即 foo。如果我们想从 lambda 表达式中返回,可以使用为 return 指定一个标签:

fun foo() {
ints.forEach lit@ {
if (it ==0) return@lit
print(it)
}
}

如上,会输出 123,但这里还有一种更简洁的写法:直接使用以 lambda 表达式的名称做为标签:

fun foo() {
ints.forEach {
if (it == 0) return@forEach
print(it)
}
}

另外,我们也可以用函数表达式替代匿名函数。在函数表达式中使用 return 语句则可以从函数表达式中返回:

fun foo() {
ints.forEach(fun(value: Int){
if (value == 0) return
print(value)
})
}

异常

所有的异常类都是 Exception 的子类,实现了 Throwable,每个异常都有一个消息,栈踪迹和可选的原因。

使用 throw 表达式,抛出异常:

throw MyException("Hi exception!")

使用 try 捕获异常:

try {
// 一些代码
}
catch (e: SomeException) {
// 处理
}
finally {
// 可选的finally块
}

在 Kotlin 中, throwtry 都是表达式,这意味着它们可以被赋值给一个变量。这个在处理一些边界问题的时候确实非常有用:

val s = when(x) {
is Int -> "Int instance"
is String -> "String instance"
else -> throw UnsupportedOperationException("Not valid type")
}

或者:

val s = try { x as String } catch(e: ClassCastException) { null }

try 返回值要么是 try 块的最后一个表达式,要么是 catch 块的最后一个表达式,finally 块的内容不会对表达式有任何影响。

而在 Kotlin 中,并不使用显式异常,这表示不会强迫我们在任何地方使用 try/catch 。这与Java中不太一样,比如在抛出 IOException 的方法,我们必须使用 try-catch 来包围代码块,通过检查 exception 来处理,这并不是一个好的方法。

比如,下面是 JDK StringBuilder 类实现的一个接口:

Appendable append(CharSequence csq) throws IOException;

这个签名说了什么?每次当我把一些字符串附加到 StringBuilder 中或者是 log console 等等,都需要捕获 IOExceptions 。为什么呢?因为可能涉及到 IO 操作(Writer 也实现了 Appendable)... 所以导致所有实现 Appendable 的接口都要捕获 IOException 异常。

try {
log.append(message)
}
catch (IOException e) {
// Must be safe
}

这样显然是不太合理的,更详细的可以参考 Bruce Eckel 和 Rod Waldhoff、以及 Anders Hejlsberg 等人的观点。

Kotlin 初窥门径[2]:流程控制的更多相关文章

  1. 《疯狂Kotlin讲义》读书笔记4——流程控制

    流程控制 与Java类似,Kotlin同样提供了两种基本的流程控制结构:分支结构和循环结构. Kotlin提供了 if 和 when 两种分支语句,其中 when 语句可以代替Java的switch语 ...

  2. 第10章 Shell编程(4)_流程控制

    5. 流程控制 5.1 if语句 (1)格式: 格式1 格式2 多分支if if [ 条件判断式 ];then #程序 else #程序 fi if [ 条件判断式 ] then #程序 else # ...

  3. Shell命令和流程控制

    Shell命令和流程控制 在shell脚本中可以使用三类命令: 1)Unix 命令: 虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令.这些命令通常是用来进行文件和文字 ...

  4. PHP基础知识之流程控制的替代语法

    PHP 提供了一些流程控制的替代语法,包括 if,while,for,foreach 和 switch. 替代语法的基本形式是把左花括号({)换成冒号(:),把右花括号(})分别换成 endif;,e ...

  5. Python黑帽编程2.4 流程控制

    Python黑帽编程2.4  流程控制 本节要介绍的是Python编程中和流程控制有关的关键字和相关内容. 2.4.1 if …..else 先上一段代码: #!/usr/bin/python # - ...

  6. 使用yield进行异步流程控制

    现状 目前我们对异步回调的解决方案有这么几种:回调,deferred/promise和事件触发.回调的方式自不必说,需要硬编码调用,而且有可能会出现复杂的嵌套关系,造成"回调黑洞" ...

  7. [Java入门笔记] Java语言基础(四):流程控制

    流程控制指的是在程序运行的过程中控制程序运行走向的方式.主要分为以下几种: 顺序结构 顺序结构,顾名思义,是指程序从上往下逐步顺序执行.中间没有任何的判断和跳转. 分支结构 Java提供两种分支结构: ...

  8. node基础13:异步流程控制

    1.流程控制 因为在node中大部分的api都是异步的,比如说读取文件,如果采用回调函数的形式,很容易造成地狱回调,代码非常不容易进行维护. 因此,为了解决这个问题,有大神写了async这个中间件.极 ...

  9. Shell入门教程:流程控制(1)命令的结束状态

    在Bash Shell中,流程控制命令有2大类:“条件”.“循环”.属于“条件”的有:if.case:属于“循环”的有:for.while.until:命令 select 既属于“条件”,也属于“循环 ...

随机推荐

  1. 【恢复】 Redo文件丢失的恢复

    第一章 Redo文件丢失的恢复 1.1  online redolog file 丢失 联机Redo日志是Oracle数据库中比较核心的文件,当Redo日志文件异常之后,数据库就无法正常启动,而且有丢 ...

  2. 使用hexdump追踪FAT32文件系统中的一个文件

    最近在看文件系统基础结构等知识,本来重点是想看EXT4文件系统,但是目前没有找到比较详细说明EXT4文件系统详细结构的,用EXT3的对应着找结果有点出入,在想是不是我用hexdump的参数有问题,于是 ...

  3. Hadoop出现的错误及处理

    1.local host is: (unknown); destination host is: "yun-ubuntu":8031; 原因:yun-ubuntu这个host 并不 ...

  4. Spark Standalone Mode Configuration

    For currently popular distributed framework Spark, here is the intro and step to configure the spark ...

  5. Excel多表合并的宏

    Sub 合并当前目录下所有工作簿的全部工作表() Dim MyPath, MyName, AWbName Dim Wb As Workbook, WbN As String Dim G As Long ...

  6. 利用workbench将excel数据导入到MySQL中

    数据导入的方式(csv,txt之类) 在MySQL中,数据导入的方式有两种方式 通过第三方客户端导入(workbench) 通过mysql client 方式导入 通过mysql clinet的导入方 ...

  7. 由max_allowed_packet引发的mysql攻防大战

    1.原因 程序的sql语句比较长.max_allowed_packet默认是1024.于是就报错了.一开始手动改 global max_allowed_packet ,改完后.莫名奇妙被还原.后来改配 ...

  8. 简单好用用js就可以保存文本文件到本地

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...

  9. Jmeter编写Base64加密函数

    方法一: 使用Beanshell Sampler.BSF Sampler等实现,现已Base64加密为例,脚本如下: import sun.misc.BASE64Decoder; String res ...

  10. 【CSS3】块级元素与行内元素的区别

    一.行内元素与块级函数的三个区别 行内元素的特点: 和其他元素都在一行上: 高,行高及外边距和内边距部分可改变: 宽度只与内容有关: 行内元素只能容纳文本或者其他行内元素. 行内元素设置width无效 ...