logo头像
Snippet 博客主题

Kotlin之基本语法

本文于373天之前发表,文中内容可能已经过时。

在今年Google IO大会上Google已经明确kotlin作为为Android第一官方语言的地位。我相信Google的决意,就像当初毫不犹豫的抛弃eclipse转向as,kotlin已经是不可避免的大势所趋了。再加上Kotlin与java代码完全兼容,所以从Java转向Kotlin是一件很容易的事情。Kotlin语法简单,它融合了当前多种语言的优势,可以是一门明星语言。

下面就Kotlin的一些基本语法做一个讲解。

一个源文件往往以包声明开始:源文件的所有内容(比如类和函数)都被包声明并包括。

1
2
3
4
5
package foo.bar
fun bza() {}
class Goo {}

在上面的例子中, bza() 的全名应该是 foo.bar.bza ,Goo 的全名是 foo.bar.Goo。如果没有指定包名,那这个文件的内容就从属于一个默认的 “default” 包。

Imports

在源文件中,除了模块中默认导入的包,每个文件也可以有它自己的导入指令。比如:

1
import foo.Bar

如果不指定特定的文件,那么可以使用*导入范围内的所有可用的内容 (包,类,对象,等等)。比如:

1
import foo.* //foo 中的所有内容

如果命名有冲突,我们还可以使用 as 关键字局部重命名解决冲突。

1
2
import foo.Bar // Bar 可以使用
import bar.Bar as bBar // bBar 代表 'bar.Bar'

函数

函数声明

在 kotlin 中用关键字 fun 声明函数:

1
2
3
fun double(x: Int): Int {
}

参数

函数参数用 Pascal 符号定义,格式形如:name:type,参数之间用逗号隔开,每个参数必须指明类型。

1
2
3
fun powerOf(number: Int, exponent: Int) {
...
}

函数参数可以设置默认值,默认值可以通过在type类型后使用=号进行赋值,当参数被忽略时会使用默认值,这样做的好处是可以减少重载。

1
2
3
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size() ) {
...
}

函数调用

在函数内部可以直接使用函数,比如:

1
val result = double(2)

如果在其他类需要调用调用成员函数:

1
Sample().foo() // 创建Sample类的实例,调用foo方法

中缀符号

在满足如下条件时:它们是成员函数或者是扩展函数,只有一个参数 使用infix关键词进行标记。函数可以通过中缀符号进行调用。比如:

1
2
3
4
5
6
7
8
//给 Int 定义一个扩展方法
infix fun Int.shl(x: Int): Int {
...
}
1 shl 2 //用中缀注解调用扩展函数
1.shl(2)

Unit类型

如果函数不会返回任何有用值,那么他的返回类型就是 Unit 。Unit 是一个只有唯一值Unit的类型,这个值并不需要被直接返回,相当于Java的Void。

1
2
3
4
5
6
7
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit` or `return` is optional
}

Unit 返回值也可以省略,例如下面这样:

1
2
3
fun printHello(name: String?) {
...
}

单表达式函数

当函数只返回单个表达式时,大括号可以省略,并在 = 后面定义函数体:

1
fun double(x: Int): Int = x*2

如果进一步精简,还可以写成如下的方式。

1
fun double(x: Int) = x * 2

变长参数

函数的参数(通常是最后一个参数)可以用 vararg 修饰符进行标记,标记后,允许给函数传递可变长度的参数:

1
2
3
4
5
6
7
8
9
fun asList<T>(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts)
result.add(t)
return result
}
val list = asList(1, 2, 3)

可变函数只有一个参数可以被标注为 vararg 。加入vararg并不是列表中的最后一个参数,那么后面的参数需要通过命名参数语法进行传值,再或者如果这个参数是函数类型,就需要通过lambda法则。

当调用变长参数的函数时,我们可以一个一个的传递参数,比如:

1
asList(1, 2, 3)

或者我们要传递一个 array 的内容给函数,那么就可以使用 * 前缀操作符:

1
2
val a = array(1, 2, 3)
val list = asList(-1, 0, *a, 4)

函数范围

Kotlin 中可以在文件顶级声明函数,这就意味者你不用像在Java,C#或是Scala一样创建一个类来持有函数。除了顶级函数,Kotlin 函数可以声明为局部的,作为成员函数或扩展函数。

Kotlin 支持局部函数,比如在一个函数包含另一函数。

1
2
3
4
5
6
7
8
9
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}

当然,局部函数可以访问外部函数的局部变量(比如闭包)。

1
2
3
4
5
6
7
8
9
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}

局部函数甚至可以返回到外部函数。

1
2
3
4
5
6
7
8
9
10
11
fun reachable(from: Vertex, to: Vertex): Boolean {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (current == to) return@reachable true
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(from)
return false
}

泛型函数

Kotlin和Java一样,还支持泛型函数。

1
2
3
fun sigletonArray<T>(item: T): Array<T> {
return Array<T>(1, {item})
}

尾递归函数

Kotlin 支持函数式编程的尾递归。这个允许一些算法可以通过循环而不是递归解决问题,从而避免了栈溢出。当函数被标记为 tailrec 时,编译器会优化递归,并用高效迅速的循环代替它。

1
2
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))

这段代码计算的是数学上的余弦不动点。Math.cos 从 1.0 开始不断重复,直到值不变为止,结果是 0.7390851332151607。上面的代码等效于:

1
2
3
4
5
6
7
8
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if ( x == y ) return y
x = y
}
}

注:使用 tailrec 修饰符必须在最后一个操作中调用自己。在递归调用代码后面是不允许有其它代码的,并且也不可以在 try/catch/finall 块中进行使用。当前的尾递归只在 JVM 的后端中可以用。

字符串

1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {
var a = 1
// 使用变量名作为模板:
val s1 = "a is $a"
a = 2
// 使用表达式作为模板:
val s2 = "${s1.replace("is", "was")}, but now is $a"
println(s2)
}

条件表达式

1
2
3
4
5
6
7
8
9
10
11
fun maxOf(a: Int, b: Int): Int {
if (a > b) {
return a
} else {
return b
}
}
fun main(args: Array<String>) {
println("max of 0 and 42 is ${maxOf(0, 42)}")
}

上面的表达式可简写为:

1
2
3
4
5
fun maxOf(a: Int, b: Int) = if (a > b) a else b
fun main(args: Array<String>) {
println("max of 0 and 42 is ${maxOf(0, 42)}")
}

值检查与自动转换

使用 is 操作符检查一个表达式是否是某个类型的实例。如果对不可变的局部变量或属性进行过了类型检查,就没有必要明确转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// obj 将会在这个分支中自动转换为 String 类型
return obj.length
}
// obj 在种类检查外仍然是 Any 类型
return null
}
fun main(args: Array<String>) {
fun printLength(obj: Any) {
println("'$obj' string length is ${getStringLength(obj) ?: "... err, not a string"} ")
}
printLength("Incomprehensibilities")
printLength(1000)
printLength(listOf(Any()))
}

for循环

Kotlin简化了for循环的方式,例如:

1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {
val items = listOf("apple", "banana", "kiwi")
for (item in items) {
println(item)
}
//或者使用下面的方式
//for (index in items.indices) {
// println("item at $index is ${items[index]}")
//}
}

while 循环

1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
val items = listOf("apple", "banana", "kiwi")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
}

ranges

检查 in 操作符检查数值是否在某个范围内:

1
2
3
4
5
6
7
fun main(args: Array<String>) {
val x = 10
val y = 9
if (x in 1..y+1) {
println("fits in range")
}
}

检查数值是否在范围外:

1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {
val list = listOf("a", "b", "c")
if (-1 !in 0..list.lastIndex) {
println("-1 is out of range")
}
if (list.size !in list.indices) {
println("list size is out of valid list indices range too")
}
}

在范围内迭代:

1
2
3
4
5
fun main(args: Array<String>) {
for (x in 1..5) {
print(x)
}
}

或者使用步进:

1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
for (x in 1..10 step 2) {
print(x)
}
for (x in 9 downTo 0 step 3) {
print(x)
}
}

集合

对一个集合进行迭代:

1
2
3
4
5
6
fun main(args: Array<String>) {
val items = listOf("apple", "banana", "kiwi")
for (item in items) {
println(item)
}
}

使用 in 操作符检查集合中是否包含某个对象。

1
2
3
4
5
6
7
fun main(args: Array<String>) {
val items = setOf("apple", "banana", "kiwi")
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}
}

使用lambda表达式过滤和映射集合:

1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
val fruits = listOf("banana", "avocado", "apple", "kiwi")
fruits
.filter { it.startsWith("a") }
.sortedBy { it }
.map { it.toUpperCase() }
.forEach { println(it) }
}

附:高阶函数和lambda表达式

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者

上一篇