MyException - 我的异常网
当前位置:我的异常网» Java相关 » Kotlin 初窥门径[二]:流程控制

Kotlin 初窥门径[二]:流程控制

www.MyException.Cn  网友分享于:2013-08-22  浏览:0次
Kotlin 初窥门径[2]:流程控制

流程控制语句是编程语言中的核心之一。可以分为分支语句、循环语句和跳转语句。本文将详细介绍一下 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 等人的观点。

文章评论

程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
一个程序员的时间管理
一个程序员的时间管理
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
代码女神横空出世
代码女神横空出世
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
编程语言是女人
编程语言是女人
每天工作4小时的程序员
每天工作4小时的程序员
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
10个调试和排错的小建议
10个调试和排错的小建议
中美印日四国程序员比较
中美印日四国程序员比较
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
我是如何打败拖延症的
我是如何打败拖延症的
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
Java程序员必看电影
Java程序员必看电影
那些争议最大的编程观点
那些争议最大的编程观点
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
程序员都该阅读的书
程序员都该阅读的书
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
 程序员的样子
程序员的样子
总结2014中国互联网十大段子
总结2014中国互联网十大段子
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
为什么程序员都是夜猫子
为什么程序员都是夜猫子
鲜为人知的编程真相
鲜为人知的编程真相
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
老程序员的下场
老程序员的下场
程序员应该关注的一些事儿
程序员应该关注的一些事儿
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
旅行,写作,编程
旅行,写作,编程
如何成为一名黑客
如何成为一名黑客
程序员和编码员之间的区别
程序员和编码员之间的区别
程序员的鄙视链
程序员的鄙视链
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有