Kotlin
Table of Contents
- 1. 简介
- 2. 基本语法
- 2.1. 一次赋值变量(val)和可重赋值变量(var)
- 2.2. 基本类型(Basic types)
- 2.3. 流程控制
- 2.4. 异常处理
- 2.5. 类和对象
- 2.6. 函数
- 2.7. Null safety
- 3. Asynchronous programming (Coroutines)
- 4. 标准库
- 5. 参考
1. 简介
Kotlin 是一个由 JetBrains 开发的多平台的静态语言。Kotlin 可以编译成 Java 字节码/JavaScript 代码/Native 代码,跨平台开发(Kotlin Multiplatform)是 Kotlin 的主要使用场景之一。
安装 IntelliJ IDEA 可以获得 Kotlin 的开发编译环境;如果只是想简单试试,则可以使用在线环境:https://play.kotlinlang.org/ 。
1.1. Hello world
下面是 Kotlin 版本的 Hello world 程序:
fun main() { println("Hello, world!!!") }
2. 基本语法
2.1. 一次赋值变量(val)和可重赋值变量(var)
Kotlin 中, 使用 val
(来源于单词 value 前 3 个字母)声明的变量只能赋值一次;而使用 var
(来源于单词 variable 前 3 个字母)声明的变量可以赋值多次。
下面是 val
和 var
的例子:
val a = 1 // a 使用 val 声明,不能被重赋值 a = 2 // 这一行会报错。因为 a 是使用 val 声明,所以 a 在上一行被赋值后不能再赋值 val b: Int // 声明 b 为 Int 类型 b = 3 // 这一行不会报错。因为 b 在上一行只使用 val 进行了声明,并没有赋值 var c = 3 c += 4 // c 使用 var 声明,可以被重赋值
2.1.1. 常量(constant)
如果一个用 val 声明的 top-level 变量在编译时就已经知道它的值,那么我们可以为它增加修饰符 constant,这样编译器会把它进行内联以提高性能。
const val CONSTANT_A: String = "This is constant A" // 这是正确用法 fun main() { println(CONSTANT_A) // const val CONSTANT_B: String = "This is constant B" // 这行会报错,因为它是函数内的局部变量,不是 top-level 变量 // println(CONSTANT_B) }
关于 constant 的其它约束可以参考:https://kotlinlang.org/docs/properties.html#compile-time-constants
2.2. 基本类型(Basic types)
Kotlin 中,什么东西都是对象,基本类型也是对象,我们可以调用对象的方法。例如,可以调用 Int 的 toChar() 方法把整数 65 转换为字符 A:
val x: Int = 65 println(x.toChar()) // Print: A
2.2.1. 整数类型
Kotlin 中的整数类型如表 1 所示。
Type | Size (bits) | Min value | Max value | Literal |
---|---|---|---|---|
Byte | 8 | -128 | 127 | N/A |
Short | 16 | -32768 | 32767 | N/A |
Int | 32 | -2,147,483,648 ( |
2,147,483,647 ( |
10, -12, 0xA3B4, 0xA3_B4, 0b11, 1_000_000 |
Long | 64 | -9,223,372,036,854,775,808 ( |
9,223,372,036,854,775,807 ( |
10L, -12L, 0xA3B4, 0xA3_B4L, 0b11L, 1_000_000L |
UByte | 8 | 0 | 255 | N/A |
UShort | 16 | 0 | 65,535 | N/A |
UInt | 32 | 0 | 4,294,967,295 ( |
10u, 10U, 0xA3u, 0xA3U, 0b11u, 0b11U |
ULong | 64 | 0 | 18,446,744,073,709,551,615 ( |
10uL, 10UL, 0xA3uL, 0xA3UL, 0b11uL, 0b11UL |
如果没有明确指定类型,编译器会根据 Literal 的形式自动推导为 Int/UInt
或者 Long/ULong
类型。
val one = 1 // Int val threeBillion = 3000000000 // Long,由于 Int 类型不足以保存这么大的数字,所以会自动推导为 Long val oneLong = 1L // Long val one = 1u // UInt val threeBillion = 3000000000uL // ULong val oneLong = 1uL // ULong
2.2.2. 浮点数类型
2.2.3. 布尔类型
Kotlin 中的 Boolean 类型有两个字面量 true
和 false
,它们支持逻辑或 ||
、逻辑与 &&
和逻辑取反 !
运算。比如:
val myTrue: Boolean = true val myFalse: Boolean = false println(myTrue || myFalse) // true println(myTrue && myFalse) // false println(!myTrue) // false
2.2.4. 字符类型
Kotlin 中的 Char
类型的字面量使用单引号表示。单引号中的反斜线有特殊含义,会进行转义。比如:
val a = 'h' // a 类型为 Char val b = '我' // b 类型为 Char,表示一个汉字 var c = '\n' // c 类型为 Char,表示换行符 println(a) println(b) println(c) // Prints an extra newline character
2.2.5. 字符串
Kotlin 中的 String 类型的字面量使用双引号表示。比如:
val s = "Hello, world!\n"
多行字符串使用 3 个双引号表示。比如:
val text = """ for (c in "foo") print(c) """
如果多行字符串有 margin prefix,则可以使用 trimMargin() 删除它们。
2.2.5.1. String templates
下面是 String templates 的例子:
val s = "abc" println("s is $s") // s is abc println("s is ${s}") // s is abc, same as above println("$s.length is ${s.length}") // abc.length is 3 println("${s}.length is ${s.length}") // abc.length is 3, same as above println("uppercase is ${s.uppercase()}") // uppercase is ABC
2.2.5.2. String format
Kotlin 中的 String format 只支持 JVM 平台,所以更推荐使用上一节介绍的 String templates。
下面是 String format 的例子:
// Formats two strings to uppercase, each taking one placeholder val helloString = String.format("%S %S", "hello", "world") println(helloString) // HELLO WORLD
2.2.6. 数组
数组(Array)中的元素必须是相同的类型,而且元素个数不能动态改变(即:当我们增加或删除数组中元素时,会创建新的数组然后进行复制)。在 Kotlin 中,更常使用的是标准库中的 Collections,仅当一些特别需求(比如 low-level 操作)情况下才会使用 Array。
通过下面方式可以创建数组:
- arrayOf(), arrayOfNulls()
- emptyArray()
- Array 构造函数
下面是创建 Array 的例子:
// Creates an array with values [1, 2, 3] val simpleArray = arrayOf(1, 2, 3) // 创建数组的方式:使用 arrayOf println(simpleArray.joinToString()) // Creates an array with values [null, null, null] val nullArray: Array<Int?> = arrayOfNulls(3) // 创建数组的方式:使用 arrayOfNulls println(nullArray.joinToString()) // Create an empty array with the emptyArray() function: var exampleArray1 = emptyArray<String>() // 创建数组的方式:使用 emptyArray var exampleArray2: Array<String> = emptyArray() // same as above // 创建数组的方式:使用 emptyArray // Creates an Array<String> with values ["10", "10", "10", "10", "10"] val initArray = Array<Int>(5) { 10 } // 创建数组的方式:使用 Array 构造函数 println(initArray.joinToString()) // 10, 10, 10, 10, 10 // Creates an Array<String> with values ["0", "1", "4", "9", "16"] val asc = Array(5) { i -> (i * i).toString() } // i starts from 0 // 创建数组的方式:使用 Array 构造函数 asc.forEach { print(it) } // it 是隐式参数 https://kotlinlang.org/docs/lambdas.html#it-implicit-name-of-a-single-parameter
2.2.6.1. Primitive-type arrays
如果对 Primitive 值使用 Array 类,这些值将被装箱到对象中。如果你想追求更好的性能,则不要使用 Array 类,而应该直接使用表 3 中的类。
Primitive-type array | Equivalent in Java |
---|---|
BooleanArray | boolean[] |
ByteArray | byte[] |
CharArray | char[] |
DoubleArray | double[] |
FloatArray | float[] |
IntArray | int[] |
LongArray | long[] |
ShortArray | short[] |
2.2.7. Collections
List 实例:
fun main() { val numbers = listOf("one", "two", "three", "four") println("Number of elements: ${numbers.size}") println("Third element: ${numbers.get(2)}") println("Fourth element: ${numbers[3]}") println("Index of element \"two\" ${numbers.indexOf("two")}") }
Set 实例:
fun main() { val numbers = setOf(1, 2, 3, 4) println("Number of elements: ${numbers.size}") if (numbers.contains(1)) println("1 is in the set") val numbersBackwards = setOf(4, 3, 2, 1) println("The sets are equal: ${numbers == numbersBackwards}") }
Map(Dictionary)实例:
fun main() { val numbersMap = mapOf( "key1" to 1, "key2" to 2, "key3" to 3, "key4" to 4, ) println("All keys: ${numbersMap.keys}") println("All values: ${numbersMap.values}") if ("key2" in numbersMap) println("Value by key \"key2\": ${numbersMap["key2"]}") if (1 in numbersMap.values) println("The value 1 is in the map") if (numbersMap.containsValue(1)) println("The value 1 is in the map") // same as previous }
2.3. 流程控制
2.3.1. if
Kotlin 中 if
是表示式,它返回一个值。这样,我们不再需要三元操作符了。
下面是 if
的使用例子:
fun main() { val a = 2 val b = 3 var max = a if (a < b) max = b // if 可省略大括号,可以没有 else // With else if (a > b) { max = a } else { max = b } // As expression max = if (a > b) a else b // 把 if 表示式的值赋值给 max,相当于三元操作符 // You can also use `else if` in expressions: val maxLimit = 1 val maxOrLimit = if (maxLimit > a) maxLimit else if (a > b) a else b println("max is $max") println("maxOrLimit is $maxOrLimit") }
2.3.2. when
Kotlin 中 when
相当于其它语言中的 switch 语句。
下面是 when
的使用例子:
fun main() { val x = 4 when (x) { 0, 1 -> print("x == 0 or x == 1") 2 -> print("x == 2") in 3..10 -> print("x is in the range [3, 10]") else -> print("otherwise") } }
2.3.3. for
下面是 for
的使用例子:
fun main() { for (i in 1..4) { // 会输入 1,2,3,4 println(i) } val a = arrayOf(1, 3, 9) for (i in a) { println(i) } }
2.3.4. while
Kotlin 中支持 while
和 do...while
,它们的区别在于 do...while
中的语句至少会执行一次。
下面是 while
的使用例子:
fun main() { var i = 1 while (i <= 5) { println("i = $i") ++i } }
下面是 do...while
的使用例子:
fun main() { var sum: Int = 0 var input: String do { print("Enter an integer (enter 0 to end): ") input = readLine()!! sum += input.toInt() } while (input != "0") println("sum = $sum") }
2.3.5. break, continue, return
break
可以跳出最内层的循环,如:
fun main() { for (i in 1..5) { // 输出 1,2 if (i == 3) { break } println(i) } }
continue
可以中止最内层循环的当前迭代,继续下一次迭代,如:
fun main() { for (i in 1..5) { // 输出 1,2,4,5 if (i == 3) { break } println(i) } }
2.3.5.1. labels(@)
如果有嵌套的循环,想使用 break/continue 跳到外层循环,则可以使用 label。
下面是 label 的使用例子:
fun main() { outer@ for (i in 1..4) { // 这里在外层循环外定义了 label,名为 outer,名称最后要接个符号 @ for (j in 1..2) { if (i == 3) break@outer // 指定 label 名 break。注意:break 和 label 名 outer 之间只有一个符号 @,没有空格 println("i = $i; j = $j") } } println("------------") outer@ for (i in 1..3) { for (j in 1..2) { if (i == 2) continue@outer // 指定 label 名 continue。注意:continue 和 label 名 outer 之间只有一个符号 @,没有空格 println("i = $i; j = $j") } } }
运行上面例子,会输出:
i = 1; j = 1 i = 1; j = 2 ------------ i = 1; j = 1 i = 1; j = 2 i = 3; j = 1 i = 3; j = 2
2.3.5.2. Return to labels
关键字 return
一般用于从函数返回。比如:
fun foo() { listOf(1, 2, 3, 4, 5).forEach { if (it == 3) return // non-local return directly to the caller of foo() print(it) } print("this point is unreachable") }
上面函数会输出 12。但如果我只想跳出 lambda 表达式(比如跳过这次 forEach 迭代,接着执行下一个 forEach 迭代,即想让 foo 函数输出 1245),怎么办呢?答案是使用 label,比如:
// 模拟 continue 办法 1:明确指定 label 名称 fun foo() { listOf(1, 2, 3, 4, 5).forEach lit@{ // 定义了一个名为 lit 的 label if (it == 3) return@lit // 跳过这次 forEach 迭代,接着执行下一个 forEach 迭代。有点像 continue print(it) } print(" done with explicit label") }
你可能有疑问为什么不直接使用 continue
呢?这是 因为 continue/break
都只能用于常规的循环(如 for/while/do...while
)。
我们也可以直接使用隐含的 label 名,比如:
// 模拟 continue 办法 2:使用隐含的 label 名 fun foo() { listOf(1, 2, 3, 4, 5).forEach { if (it == 3) return@forEach // lambda 表达式名称 forEach 是一个隐含的 label 名 print(it) } print(" done with implicit label") }
最后,介绍第 3 种方法:使用匿名函数替换 lambda 表达式,比如:
// 模拟 continue 办法 3:使用匿名函数替换 lambda 表达式,在匿名函数中直接使用 return 语句 fun foo() { listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) { if (value == 3) return // local return to the caller of the anonymous function - the forEach loop print(value) }) print(" done with anonymous function") }
上面 3 个例子的语义都和 continue
类似,如何实现 break
语义呢?方法如下:
// 模拟 break fun foo() { run label1@{ // 关于 run 可参考 https://kotlinlang.org/docs/scope-functions.html#run listOf(1, 2, 3, 4, 5).forEach { if (it == 3) return@label1 // non-local return from the lambda passed to run print(it) } } print(" done with nested loop") // 前面的 print 会输出 12,这一行 print 内容也会被输出 }
2.4. 异常处理
Kotlin 中所有异常都继承于 Throwable 类。Kotlin 没有 Java 中的 Checked exceptions(即必须抛出的异常)。
使用 throw
可以抛出异常,比如 throw IllegalArgumentException("Invalid arg")
。
使用 try...catch...finally
可以捕获异常,如:
fun main() { try { var num = 10 / 0 // throws exception println(num) } catch (e: Exception) { println("$e") } }
可以有零个或者多个 catch
语句,可以省略 finally
语句,但是至少有一个 catch
或者 finally
语句。
2.4.1. Try 是表达式
Kotlin 中, try...catch...finally
是表达式,也就是说它会返回某个值。比如:
val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }
2.4.2. Nothing 类型
throw
表达式的类型是 Nothing
。这个类型没有值,它一般用于标记一个不会到达的代码位置。你可以声明一个函数的返回类型为 Nothing
,表示它不会返回。比如:
fun fail(message: String): Nothing { // 也可以不明确指定它的返回类型,因为编译器有类型推导系统 throw IllegalArgumentException(message) }
你也可能在其它地方看到过 Nothing
类型。比如:
val x = null // 'x' has type `Nothing?` val l = listOf(null) // 'l' has type `List<Nothing?>
2.5. 类和对象
2.5.1. 类构造函数
Kotlin 中使用关键字 class
来定义类。它支持两种构造函数:
- Primary Constructor,一个类只能有一个 Primary Constructor;
- Secondary Constructors,一个类可以没有,也可以有多个 Secondary Constructors。而且 Secondary Constructors 必须调用 Primary Constructor。
Primary Constructor 是在类声明头中的构造函数,下面是 Primary Constructor 的例子:
class Person constructor(val name: String, var age: Int) { // 这是 Primary Constructor。可省掉 constructor 关键字,即写为 class Person (val name: String, var age: Int) { fun getInfo(): String { return "Name: $name\nAge: $age" } init { // Primary Constructor 中不能有复杂逻辑,可以把初始化逻辑代码放入 init 块中 println("First initializer block that prints $name") } } fun main() { val person1 = Person("Alice", 25) // Kotlin 不使用 new 创建对象 println(person1.name) println(person1.age) person1.age += 1 println(person1.getInfo()) }
Secondary Constructors 是名为 constructor
的成员函数,可以有多个,比如:
class Person(val name: String, var age: Int) { // 这是 primary constructor val children: MutableList<Person> = mutableListOf() constructor(name: String, age: Int, parent: Person) : this(name, age) { // 第一个 secondary constructor,它调用了 primary constructor parent.children.add(this) } constructor(name: String, parent: Person) : this(name, 0) { // 第二个 secondary constructor,它调用了 primary constructor parent.children.add(this) } fun getInfo(): String { return "Name: $name\nAge: $age\nChildren number: ${children.size}" } } fun main() { val parent = Person("Alice", 32) // Kotlin 不使用 new 创建对象 val child1 = Person("Joe", 3, parent) // Kotlin 不使用 new 创建对象 val child2 = Person("Winny", parent) // Kotlin 不使用 new 创建对象 println(parent.getInfo()) println(child1.getInfo()) println(child2.getInfo()) }
Kotlin 不支持析构函数,如果需要在对象被回收时执行一些操作,可以使用 kotlin.native.internal.createCleaner。
2.5.2. 类的属性
类的属性的语法为:
var <propertyName>[: <PropertyType>] [= <property_initializer>] [<getter>] [<setter>]
下面是类属性的例子:
class Rectangle(val width: Int, val height: Int) { // 这个类有三个属性 width,height,area val area: Int get() = this.width * this.height } fun main() { val r = Rectangle(10, 20) println("width = ${r.width}") // width = 10 println("height = ${r.height}") // height = 20 println("area = ${r.area}") // area = 200 }
2.5.2.1. 后备字段(field)
在介绍后备字段(Backing fields)前,先看一段代码:
// 这是一段有问题的代码,不要使用 class Person { var name: String = "" var age: Int = 0 set(value) { if (value >= 0) { age = value // 这里有问题!不能在 setter 中对当前属性赋值,这会无限调用 setter,导致程序崩溃 } } } fun main() { var p = Person() p.age = 1 println(p.age) }
上面代码中,属性 age
的 setter 中有语句 age = value
,这会造成无限调用 age
的 setter,从而导致程序崩溃。
解决办法是: 把 setter 中对当前属性的引用改为 field
, 即:
class Person { var name: String = "" var age: Int = 0 set(value) { if (value >= 0) { field = value // setter 中如果要对当前属性赋值,请使用 field } } } fun main() { var p = Person() p.age = 1 println(p.age) }
同样的道理, 不能在 getter 中读取当前属性,如果有这样的需求,则应该修改为 field
。
2.5.3. 类的继承
Kotlin 中所有类都有一个共同的父类: Any
, Any
有三个方法 equals()
, hashCode()
, toString()
。也就是说,Kotlin 所有类都有这三个方法。
默认,Kotlin 中的类都是 final 的,不能被继承。要让一个类可以被其它类继承,则需要使用 open
关键字,比如:
open class Base(p: Int) // 使用了 open 关键字,这样其它类可以继承 Base 了 class Derived(p: Int) : Base(p) // 类 Derived 继承于类 Base
2.5.3.1. 重载(Overriding)
类中的方法和属性可以被重载(Overriding)。但需要两个操作:
- 父类中的方法或属性必须增加
open
关键字; - 子类中的方法或属性必须增加
override
关键字。
下面是重载类中方法的例子:
open class Shape { open fun draw() { /*...*/ } // open 修饰父类的 draw fun fill() { /*...*/ } } class Circle() : Shape() { override fun draw() { /*...*/ } // override 修饰子类的 draw } open class Rectangle() : Shape() { final override fun draw() { /*...*/ } // 如果有 final,则表示 draw 不能被 Rectangle 的子类重载了 }
2.5.4. 可见性
Classes, objects, interfaces, and functions 等都可以控制其可见性。Kotlin 有表 4 所示机制来控制可见性。
Modifier | 说明 |
---|---|
public(默认) | 任何位置都可见。不用明确指定,这是默认值 |
internal | 同一个 module(module 由多个文件组成)内部可见 |
protected | 修改类成员表示当前类及其子类可见 |
private | 修改类成员表示当前类可见。修饰其它时表示当前文件可见 |
下面是使用 private 修饰函数,internal 修饰全局变量的例子:
// file name: example.kt package foo private fun foo() { ... } // visible inside example.kt internal val baz = 6 // visible inside the same module
下面是修饰类成员的例子:
open class Outer { private val a = 1 protected open val b = 2 internal open val c = 3 val d = 4 // public by default protected class Nested { public val e: Int = 5 } } class Subclass : Outer() { // a is not visible // b, c and d are visible // Nested and e are visible override val b = 5 // 'b' is protected override val c = 7 // 'c' is internal }
2.5.5. 抽象类(Abstract classes)
一个类可以被声明为 abstract
,这就是抽象类。不能直接实例化抽象类。
下面是抽象类的例子:
abstract class Person(name: String) { // Person 是抽象类,不能直接实例化 init { println("My name is $name.") } fun displaySSN(ssn: Int) { println("My SSN is $ssn.") } abstract fun displayJob(description: String) // 这个方法是 abstract 的,它没有实现。Person 子类中必须实现这个方法 } class Teacher(name: String): Person(name) { override fun displayJob(description: String) { println("I'm a $description teacher.") } } fun main() { val teacher = Teacher("Alice") teacher.displayJob("mathematics") teacher.displaySSN(12345) }
2.5.6. 接口(Interfaces)
使用 interface
关键字可以声明接口。和抽象类类似,我们不能直接实例化接口。
下面是接口的例子:
interface Person { // Person 是接口,不能直接实例化 val name: String // abstract property fun displaySSN(ssn: Int) { // 这个方法有默认实现 println("My SSN is $ssn.") } fun displayJob(description: String) // abstract method } class Teacher(override val name: String) : Person { // override 了接口的抽象属性 name override fun displayJob(description: String) { // override 了接口的抽象方法 displayJob println("I'm a $description teacher.") } } fun main() { val teacher = Teacher("Alice") teacher.displayJob("mathematics") teacher.displaySSN(12345) }
抽象类和接口的主要区别在于: 抽象类可以有状态,而接口不保存状态。类只能继承一个抽象类,但可以实现多个接口。
2.5.7. Extension
Kotlin 提供了一种在不修改类的情况下,可以增强类功能的机制,即 Extension。它包含 Extension functions 和 Extension properties 两种机制。
下面是 Extension functions 的例子:
fun String.removeFirstLastChar(): String = this.substring(1, this.length - 1) fun main() { val myString= "abcde" val result = myString.removeFirstLastChar() println("Result is: $result") // bcd }
如果 Extension functions 是类中已经存在的函数,那么类中的函数总是有更高优先级。比如:
class Example { fun printFunctionType() { println("Class method") } } fun Example.printFunctionType() { println("Extension function") } fun main() { Example().printFunctionType() // Class method }
2.5.8. Data classes
如果类仅仅是用于保存数据,则可以使用 Data classes,编译器会自动为它生成一些函数。
比如有 Data class:
data class User(val name: String, val age: Int)
则编译器自动生成:
equals()/hashCode()
pair.toString()
of the form "User(name=John, age=42)".componentN()
functions corresponding to the properties in their order of declaration.copy()
function
其中生成的 copy()
是很有用的,比如:
data class User(val name: String, val age: Int) fun main() { val jack = User(name = "Jack", age = 1) println(jack) // User(name=Jack, age=1) val olderJack = jack.copy(age = 2) println(olderJack) // User(name=Jack, age=2) }
2.5.9. Enum classes
在类前使用 enum
可以声明为枚举,比如:
enum class RGB { RED, GREEN, BLUE } fun main() { for (color in RGB.entries) println(color.toString()) // prints RED, GREEN, BLUE val color = RGB.valueOf("RED") println("The first color is: $color") // prints "The first color is: RED" }
2.5.10. Sealed classes
在讨论 Sealed classes 前,先看看 Sealed classes 能解决什么问题。
考虑下面程序:
open class Expr class Const(val value: Int) : Expr() class Sum(val left: Expr, val right: Expr) : Expr() fun eval(e: Expr): Int = when (e) { is Const -> e.value is Sum -> eval(e.right) + eval(e.left) else -> throw IllegalArgumentException("Unknown expression") } fun main() { val expr = Const(1) println(eval(expr)) }
上面程序运行良好。Expr 有两个子类 Const 和 Sum, 但如果哪一天我们给它增加了另外一个子类,那么 eval 函数中的 when
表达式也需要增加相应的分支,但这很容易忘记。由于 when
表达式有 else
语句,当我们忘记为新子类增加 when
分支时编译器并不能帮我们发现问题。
Sealed classes 就是为了解决这个问题的:
sealed class Expr // 把 class 设置为 sealed 可以解决上面提到的问题 class Const(val value: Int) : Expr() class Sum(val left: Expr, val right: Expr) : Expr() fun eval(e: Expr): Int = when (e) { is Const -> e.value is Sum -> eval(e.right) + eval(e.left) } fun main() { val expr = Const(1) println(eval(expr)) }
eval 函数中的 when
表达式中不再需要 else
语句了,这样一旦我们给 Expr 增加了新子类,编译会强制要求我们在 when
表达式中增加相应的分支,否则编译器报错。
2.5.11. 泛型(Generics)
下面是类泛型的例子:
class ParameterizedClass<T>(private val value: T) { fun getValue(): T { return value } } fun main() { val parameterizedClass1 = ParameterizedClass<String>("string-value") val res1 = parameterizedClass1.getValue() println(res1 is String) // true val parameterizedClass2 = ParameterizedClass<Int>(100) val res2 = parameterizedClass2.getValue() println(res2 is Int) // true }
下面是函数泛型的例子:
fun <T> middle(list: List<T>): T { return list[list.size / 2] } fun main() { val middleString: String = middle(listOf("ab", "cd", "ef")) println(middleString) val middleInteger: Int = middle(listOf(1, 2, 3, 4, 5)) println(middleInteger) }
当我们不关心泛型类型时,可以使用 *
,比如:
fun printArray(array: Array<*>) { array.forEach { println(it) } } fun main() { val a = arrayOf(1,2,3) printArray(a) val b = arrayOf("ab", "cd", "ef") printArray(b) }
2.5.11.1. in, out
可以使用 in
声明类的泛型参数,它表示对象可以赋值给子类。比如:
class InClass<in T> { // in 表示可以赋值给 subtype fun toString(value: T): String { return value.toString() } } fun main() { val obj: InClass<Number> = InClass() val ref: InClass<Int> = obj // Int 是 Number 的 subtype,没有 in 关键字这行会报错 println(obj.toString(1.2)) println(ref.toString(100)) }
可以使用 out
声明类的泛型参数,它表示对象可以赋值给超类。比如:
class OutClass<out T>(val value: T) { // out 表示可以赋值给 supertype fun get(): T { return value } } fun main() { val obj = OutClass("ABC") val ref: OutClass<Any> = obj // Any 是 String 的 supertype,没有 out 关键字这行会报错 println(obj.value) // ABC println(ref.value) // ABC }
2.5.12. Object declarations(单例模式)
单例模式是指一个类只有一个实例(对象)。在 Kotlin 中使用 Object declarations 来表达单例模式。
下面是 Object declaration 的例子:
object MySingleton { fun doSomething() { // ... } } fun main() { MySingleton.doSomething() }
2.5.12.1. Companion objects
一个类可以有多个 Object declarations,我们可以把其中的一个标记为 companion
(一个类最多有一个 Companion objects),这样在调用 Companion object 中的方法时可以直接省略 Companion object 名称了。
下面是 Companion object 的例子:
class Person(val name: String) { // A member function of the class fun sayHello() { println("Hello, my name is $name") } companion object MyFactory1 { // 名称 MyFactory1 可以简略,即可以直接写为 companion object { // A property inside the companion object val defaultName = "DefaultName1" // A function inside the companion object fun createWithDefaultName(): Person { return Person(defaultName) } } object MyFactory2 { // A property inside the companion object val defaultName = "DefaultName2" // A function inside the companion object fun createWithDefaultName(): Person { return Person(defaultName) } } } fun main() { // Accessing a property and function from the companion object val instance1 = Person.createWithDefaultName() // 类中 companion object 只能有一个,调用时可以省略名称 MyFactory1 val instance2 = Person.MyFactory2.createWithDefaultName() // 类中普通的 Object declaration,必须指定名称(即 MyFactory2) val instance3 = Person("Alice") instance1.sayHello() // Output: Hello, my name is DefaultName1 instance2.sayHello() // Output: Hello, my name is DefaultName2 instance3.sayHello() // Output: Hello, my name is Alice }
2.5.13. Object expressions(对类进行小修改但不声明子类)
有时我们想对类进行小修改,但懒得声明子类。这时可以使用 Object expressions。
下面是 Object expression 的例子:
window.addMouseListener(object : MouseAdapter() { // object 是关键字,这里表示 Object expression override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } })
上面例子相当于创建了 MouseAdapter
的子类,而且重载了方法 mouseClicked/mouseEntered
,再把这个子类的对象传递给 addMouseListener
函数。也可以写为:
val obj = object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... } } window.addMouseListener(obj)
下面是 Object expression 的完整例子:
// From https://www.programiz.com/kotlin-programming/object-singleton open class Person(name: String, age: Int) { init { println("name: $name, age: $age") } fun eat() = println("Eating food.") fun talk() = println("Talking with people.") open fun pray() = println("Praying god.") } fun main() { val atheist = object : Person("Jack", 29) { override fun pray() = println("I don't pray. I am an atheist.") } atheist.eat() atheist.talk() atheist.pray() }
2.5.14. Type aliases
使用 typealias
可以给类型创建别名。比如:
typealias Predicate<T> = (T) -> Boolean // Predicate<T> 是函数类型 (T) -> Boolean 的别名 fun foo(p: Predicate<Int>) = p(42) fun main() { val f: (Int) -> Boolean = { it > 0 } println(foo(f)) // prints "true" val p: Predicate<Int> = { it > 0 } println(listOf(1, -2).filter(p)) // prints "[1]" }
2.6. 函数
Kotlin 中使用关键字 fun
来定义函数,参数定义使用风格 name: type
,比如:
fun sum(a: Int, b: Int): Int { return a + b } fun main() { sum(1, 2) // 调用函数 sum(a = 1, b = 2) // 调用函数时,可以指定参数名称,这时参数的顺序就不重要了 sum(b = 2, a = 1) // 调用函数时,可以指定参数名称,这时参数的顺序就不重要了 }
2.6.1. 默认参数
Kotlin 函数支持默认参数,比如:
fun foo( bar: Int, baz: Int = 200, // 这是默认参数 qux: Int = 300, // 这也是默认参数。这里有个多余的逗号是没有关系的 ) { println("bar is $bar") println("baz is $baz") println("qux is $qux") } fun main() { foo(10, 20, 30) // 指定了所有参数 foo(10, 20) // 省略了一个默认参数 foo(10) // 省略了两个默认参数 }
默认参数后面还可以有普通的参数,比如:
fun foo( bar: Int = 100, baz: Int, // 默认参数 bar 后面还有普通参数 baz ) { println("bar is $bar") println("baz is $baz") } fun main() { foo(10, 20) // 指定了所有参数 foo(baz = 200) // 省略了默认参数,普通参数则必须指定名称 }
2.6.2. 变长参数(vararg)
变长参数使用 vararg
修饰,如:
fun <T> asList(vararg ts: T): List<T> { // 参数 ts 是变长参数 val result = ArrayList<T>() for (t in ts) // ts is an Array result.add(t) return result } fun main() { val list1 = asList(1, 2, 3) println("$list1") // [1, 2, 3] val a = arrayOf(1, 2, 3) val list2 = asList(-1, 0, *a, 4) // 数组前面的星号 * 是 spread operator,表示把内容展开作为变长参数的一部分 println("$list2") // [-1, 0, 1, 2, 3, 4] }
2.6.3. 返回 Unit
当函数不需要返回值时,可以声明返回类型是 Unit。这种类型的值,只有一个就是 Uint。如:
fun printHello(name: String?): Unit { if (name != null) println("Hello $name") else println("Hi there!") // `return Unit` or `return` is optional }
当然,也可以直接不声明返回类型,上面代码等价于:
fun printHello(name: String?) { if (name != null) println("Hello $name") else println("Hi there!") }
2.6.4. Single-expression 函数
如果函数体只有一个表达式,则可以省略函数体的大括号,把表达式直接写在等号后面。如:
fun double(x: Int): Int = x * 2 // Single-expression 函数 fun double(x: Int) = x * 2 // 同上。由于编译器可以自动推导出类型,所以返回类型也可以省略
2.6.5. Infix 函数
用 infix
关键字标记的函数也可以使用中缀表示法调用(即省略调用的点和圆括号)。Infix functions 必须满足以下要求:
- 必须是成员函数(member functions)或扩展函数(extension functions);
- 它们必须有一个参数;
- 形参不能接受可变数量的参数,也不能有默认值。
下面是中缀函数的例子:
infix fun Int.add(number: Int): Int { // 使用 infix 声明中缀函数 return this + number // 中缀函数总是有 receiver,关键字 this 表示 receiver } fun main() { val num = 10 val newNum1 = num.add(5) println("$newNum1") val newNum2 = num add 5 // 使用中缀函数 println("$newNum2") }
2.6.6. Lambda 表达式
如果某个函数的参数是函数,或者返回值是函数;那么这个函数就是高阶函数(Higher-order functions)。
Lambda 表达式就是“函数字面量”。比如:
fun main() { val square = { number: Int -> number * number } // Lambda 表达式 val nine = square(3) println("$nine") }
2.6.6.1. 隐式的单参数名(it)
如果 Lambda 表达式只有单个参数,则可以使用 it 进行简化。比如:
fun main() { val array = arrayOf(1,2,3) array.forEach { item -> println(item * 4) } array.forEach { println(it * 4) } // 是上一行的简写形式 }
2.7. Null safety
Kotlin 中严格区分“可为 null”和“不可为 null”的类型。对于“可以为 null”的类型,在类型名后面有个问号 ?
,比如:
fun main() { var a: String = "abc" // String 类型不能为 null // a = null // 这行会报错,不能给 a 赋值为 null println(a) var b: String? = "abc" // String? 类型可以为 null b = null // ok println(b) }
不能直接在“可为 null”的类型上调用方法或属性,要使用 safe call ?.
调用其方法或属性,比如:
fun main() { var b: String? = "abc" // String? 类型可以为 null // val len: Int = b.length // 这是错误的,因为 b 是可为 null 类型,不能直接调用其方法或属性 val len: Int? = b?.length // 要使用 safe call ?. 调用其方法或属性,如果 b 为 null,则 b?.length 返回 null;如果 b 不为 null,则 b?.length 返回其长度 println(len) }
2.7.1. Elvis operator
考虑下面例子:
val l: Int = if (b != null) b.length else -1
代码很简单:当 b
不为 null
就返回 b.length
,当 b
为 null
就返回 -1
。
对于上面代码,如果使用 Elvis operator( ?:
)的话,可以简化为:
val l = b?.length ?: -1
2.7.2. Not-null assertion operator
如果你确定 b
一定不为 null
,则可以使用 !!
操作符:
val l = b!!.length // 返回 b 的长度。这里是断言 b 不为 null;但如果 b 为 null,则会抛异常
2.7.3. Automatic casts
编译器会进行 Automatic casts,以方便使用,下面是例子:
fun parseInt(str: String): Int? { return str.toIntOrNull() } fun printProduct(arg1: String, arg2: String) { val x = parseInt(arg1) val y = parseInt(arg2) if (x == null) { // if 中 x 为 null 会退出,编译器会把 if 外部后续代码中的 x 自动会变为 Int 类型,方便使用 println("Wrong number format in arg1: '$arg1'") return } if (y == null) { // if 中 y 为 null 会退出,编译器会把 if 外部后续代码中的 y 自动会变为 Int 类型,方便使用 println("Wrong number format in arg2: '$arg2'") return } // x and y are automatically cast to non-nullable after null check println(x * y) } fun main() { printProduct("6", "7") printProduct("a", "7") printProduct("99", "b") }
上面的 printProduct
也可以为:
fun printProduct(arg1: String, arg2: String) { val x = parseInt(arg1) val y = parseInt(arg2) // Using `x * y` yields error because they may hold nulls. if (x != null && y != null) { // 由于 if 中检查了 x,y 的值,在这个 if 分支内,x 和 y 会自动变为 Int 类型,方便使用 // x and y are automatically cast to non-nullable after null check println(x * y) } else { println("'$arg1' or '$arg2' is not a number") } }
2.7.4. Safe type casts
关键字 as
是 type cast 操作符,当类型转换失败时会抛出异常;关键字 as?
也是 type cast 操作符,当类型转换失败时不会抛出异常,而是返回 null
,所以 as?
也称为 Safe type cast 操作符。
下面是 as?
的使用例子:
interface Person { // Person 是接口,不能直接实例化 val name: String // abstract property fun displayJob(description: String) // abstract method } class Teacher(override val name: String) : Person { override fun displayJob(description: String) { println("I'm a $description teacher.") } } class Student(override val name: String) : Person { override fun displayJob(description: String) { println("I'm a $description student.") } } fun testSafeCast(person: Person) { val p1: Teacher? = person as? Teacher // 使用 as? 时,如果 person 不是 Teacher,则结果是 null,不会报错。 println("person cast to Teacher, $p1") // 使用 as 时,如果 person 不是 Teacher,则会抛异常 ClassCastException。 val p2: Student? = person as? Student println("person cast to Student, $p2") } fun main(args: Array<String>) { val teacher = Teacher("Alice") val student = Student("Bob") testSafeCast(teacher) testSafeCast(student) }
3. Asynchronous programming (Coroutines)
各种编程语言中,有很多异步编程的方案,比如:
- Threading
- Callbacks
- Futures, promises, and others
- Reactive Extensions
- Coroutines
在 Kotlin 中采用的方案是 Coroutines,不过它没有直接在语言层面进行全面的支持,而通过库 kotlinx.coroutines 来实现大部分逻辑。
3.1. 第一个 Coroutine 程序
下面是一个简单的 Coroutine 程序(在运行下面程序前,你需要把 kotlinx-coroutines-core 加入到你项目的依赖中,具体可参考:https://github.com/Kotlin/kotlinx.coroutines/blob/master/README.md#using-in-your-projects ):
import kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope launch { // launch a new coroutine and continue delay(1000L) // non-blocking delay for 1 second (default time unit is ms) println("World!") // print after delay } println("Hello") // main coroutine continues while a previous one is delayed }
运行上面程序会得到下面输出:
Hello World!
下面是对上面程序的一些简单说明:
- launch 是一个 Coroutine Builder,它用于启动一个新 Coroutine。
- delay 用于中止当前 Coroutine 运行,直到指定的时间到达;不过
delay
并不会阻塞底层的线程执行,底层线程可以执行其它的 Coroutine。 - runBlocking 也是一个 Coroutine Builder,不过它往往用于“连接”常规的非协程世界代码(如 main 函数)和协程世界代码(如
runBlocking
大括号内的代码),而且 阻塞当前线程直到大括号内的所有协程都结束, 所以上面的例子在输出 Hello 后,程序不会直接退出而是等待 World! 也输出后才退出。如果省略runBlocking
,则会报编译错误,因为launch
只能在协程世界代码中执行。
3.1.1. Structured concurrency
Kotlin 中的 Coroutine 遵守了 Structured concurrency 的原则,也就是说 新的 Coroutine 只能在特定的 CoroutineScope 中启动。 前一节的例子中,runBlocking 就是建立了这样的 Scope。
在现实的应用程序中,往往会启动大量的 Coroutines,使用 Structured concurrency 可以确保 Coroutines 不会丢失、不会泄漏。
3.2. 第一个 Suspending 函数
我们也可以把节 3.1 中 launch
块中代码封装到一个独立函数 doWorld
中,即:
import kotlinx.coroutines.* fun main() = runBlocking { // this: CoroutineScope launch { doWorld() } // launch 可启动新 Coroutine。如果去掉 launch,则会先输出 World! 再输出 Hello println("Hello") } // this is your first suspending function suspend fun doWorld() { delay(1000L) println("World!") }
为什么函数 doWorld
前面需要增加 suspend
关键字呢?这是因为它调用的 delay
是一个 suspend
函数。 我们要在 doWorld
函数中要调用另一个 suspend
函数(即这个例子中的 deplay
)的话,它自己(即这个例子中的 doWorld
)必须是 suspend
函数。
3.3. runBlocking VS. coroutineScope
除了使用 runBlocking
创建 Coroutine scope 外,还可以使用 coroutineScope
创建 Coroutine scope。
它们的相同点是都会等待它们的代码及其子 Coroutine 执行结束;而它们的主要区别是 runBlocking
会阻塞当前线程;而 coroutineScope
不会阻塞当前线程,当 Coroutine 暂停时会把线程资源给其它的 Coroutine。
下面是 coroutineScope
的例子:
import kotlinx.coroutines.* fun main() = runBlocking { doWorld() } suspend fun doWorld() = coroutineScope { // 如果这里省略 coroutineScope,会报错 Unresolved reference: launch. Suspension functions can be called only within coroutine body launch { delay(1000L) println("World!") } println("Hello") }
下面是用多个 launch
启动多个 Coroutines 的例子:
import kotlinx.coroutines.* // Sequentially executes doWorld followed by "Done" fun main() = runBlocking { doWorld() println("Done") } // Concurrently executes both sections suspend fun doWorld() = coroutineScope { // this: CoroutineScope launch { // 启动 Coroutine delay(2000L) println("World 2") } launch { // 启动 Coroutine delay(1000L) println("World 1") } println("Hello") }
上面代码的执行时间约 2 秒钟,且输出下面内容:
Hello World 1 World 2 Done
3.4. 使用 async/await
除使用 launch
启动协程外,我们也可以使用 async 启动协程,它们的主要区别是:
launch
函数的返回类型是 Job ,它代表一个可以取消的协程任务, 通常用于执行一些不需要返回结果的操作。async
函数的返回类型是 Deferred,它代表一个可以异步获取结果的任务,可以通过await
获取最终结果, 通常用于执行需要返回结果的操作。
在错误处理方面,它们也不同:
- 在使用
launch
启动的任务中,需要在协程内部进行错误处理,否则可能会导致未捕获的异常。 - 在使用
async
启动的任务中,并且可以使用try/catch
来捕获可能的异常。
下面是 async/await
的使用例子:
import kotlinx.coroutines.* import kotlin.system.* fun main() = runBlocking { val time = measureTimeMillis { val one = async { doSomethingUsefulOne() } // async 启动协程 val two = async { doSomethingUsefulTwo() } // async 启动协程 val result1 = one.await() // await 等待协程执行完成,获得结果 val result2 = two.await() // await 等待协程执行完成,获得结果 println("The answer is ${result1 + result2}") } println("Completed in $time ms") } suspend fun doSomethingUsefulOne(): Int { delay(1000L) // pretend we are doing something useful here return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // pretend we are doing something useful here, too return 29 }
运行上面代码,得到下面输出:
The answer is 42 Completed in 1031 ms
4. 标准库
4.1. Scope 函数
标准库中提供了 5 个 Scope 函数 let
, run
, with
, apply
, also
。 这些函数的功能基本上都是执行一个代码块,并返回一个对象。使用它们的目的作用仅仅上让 kotlin 代码更加紧凑和可读性更好。
下面是 let
的例子:
data class Person(var name: String, var age: Int, var city: String) { fun moveTo(newCity: String) { city = newCity } fun incrementAge() { age++ } } fun main() { Person("Alice", 20, "Amsterdam").let { println(it) // 在 let 中,it 代表 context 对象本身 it.moveTo("London") it.incrementAge() println(it) } }
如果不使用 let
的话,上面代码不得不引入一个新变量(如下面代码中的 alice
),可以写为:
data class Person(var name: String, var age: Int, var city: String) { fun moveTo(newCity: String) { city = newCity } fun incrementAge() { age++ } } fun main() { val alice = Person("Alice", 20, "Amsterdam") println(alice) alice.moveTo("London") alice.incrementAge() println(alice) }
4.1.1. Scope 函数总结
Scope 函数的功能是类似的,表 5 是它们的一个对比总结。
Function | Object reference | Return value | Is extension function(Yes 意味着必须在对象上执行) |
---|---|---|---|
let | it | Lambda result | Yes |
run | this | Lambda result | Yes |
run | - | Lambda result | No: called without the context object |
with | this | Lambda result | No: takes the context object as an argument. |
apply | this | Context object | Yes |
also | it | Context object | Yes |
从表 5 中可知 run
有两种用法:一是在某个对象上使用(即作为 Extension function),二是直接使用。
下面是 let
和 run
的使用例子:
class MultiportService(var url: String, var port: Int) { fun prepareRequest(): String = "Default request" fun query(request: String): String = "Result for query '$request'" } fun main() { // 例子:直接使用 run val hexNumberRegex = run { // run the code block and compute the result. val digits = "0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") } for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) { println(match.value) } val service = MultiportService("https://example.kotlinlang.org", 80) // 例子:在对象上使用 run val runResult = service.run { port = 8080 // 也可以写为完整的 this.port = 8080 query(prepareRequest() + " to port $port") // run 返回最后一个表达式 } // 例子:使用 let(只能在对象上使用)。作用和上面的 run 一样 val letResult = service.let { it.port = 8080 // let 和 run 区别较小,只是引用对象时的名称不同,let 使用 this,而 run 使用 it it.query(it.prepareRequest() + " to port ${it.port}") // let 返回最后一个表达式 } println(runResult) // Result for query 'Default request to port 8080' println(letResult) // Result for query 'Default request to port 8080' }
如果 let
语句块中只有一个函数,而且这个函数只接受一个 it
参数,则代码可以简化,比如:
fun main() { val lists = mutableListOf("this", "is", "a", "test", "string") lists.map { it.length }.filter { it > 3 }.let { // let 中只有一个函数,且这个函数只接受一个 it 参数 println(it) // 输出 [4, 4, 6] } lists.map { it.length }.filter { it > 3 }.let(::println) // 上面的简化形式,:: 是 method reference }
with
, apply
, also
常用于初始化对象,下面是它们的使用例子:
data class Person(var name: String, var age: Int = 0, var city: String = "") fun main() { // 例子:使用 with(不能在对象上使用,而是要把对象作为 with 的参数) val adam1 = Person("Adam") with(adam1) { age = 32 // 也可写为 this.age = 32 city = "London" } println(adam1) // 例子:使用 apply val adam2 = Person("Adam").apply { // apply the following assignments to the object age = 32 // 也可写为 this.age = 32 city = "London" } println(adam2) // 例子:使用 also val adam3 = Person("Adam").also { it.age = 32 it.city = "London" } println(adam3) }