
文章目录
- (三)Kotlin------高级特性
-
- 1.泛型
-
- [1.1 泛型使用](#1.1 泛型使用)
- [1.2 官方高阶扩展函数](#1.2 官方高阶扩展函数)
- [1.3 协变与逆变](#1.3 协变与逆变)
- [1.4 泛型界限](#1.4 泛型界限)
- [1.5 类型擦除](#1.5 类型擦除)
- 2.数组
-
- [2.1 数组的创建](#2.1 数组的创建)
- [2.2 数组的操作](#2.2 数组的操作)
- [2.3 可变长参数](#2.3 可变长参数)
- [2.4 原生类型数组](#2.4 原生类型数组)
- [2.5 多维数组](#2.5 多维数组)
- 3.集合类
-
- [3.1 List 集合](#3.1 List 集合)
- [3.2 Set 集合](#3.2 Set 集合)
- [3.3 Map 集合](#3.3 Map 集合)
- [3.4 迭代器](#3.4 迭代器)
- [3.5 集合与数组扩展操作](#3.5 集合与数组扩展操作)
- [3.6 序列](#3.6 序列)
- 4.特殊类型
-
- [4.1 数据类](#4.1 数据类)
- [4.2 枚举类](#4.2 枚举类)
- [4.3 匿名类](#4.3 匿名类)
- [4.4 单例类](#4.4 单例类)
- [4.5 伴生对象](#4.5 伴生对象)
- [4.6 委托模式](#4.6 委托模式)
- [4.7 密封类](#4.7 密封类)
- 5.异常
-
- [5.1 异常使用](#5.1 异常使用)
- [5.2 异常处理](#5.2 异常处理)
(三)Kotlin------高级特性
1.泛型
泛型用于在定义类、接口或函数时暂时不指定具体类型,而是在使用时再确定类型。它可以提高代码复用性,并减少不必要的类型转换。
1.1 泛型使用
在类名后使用 <T> 定义泛型参数,T 可以理解为一个"待定类型"。
kotlin
class Score<T>(var name: String, var id: String, var value: T)
// 定义类型参数后,T 就可以作为属性、函数参数或返回值的类型使用
fun main() {
val score = Score<String>("数据结构与算法基础", "EP074512", "优秀")
// 使用时通过 <String> 明确 value 的类型
val value: String = score.value
println(value)
}
如果 Kotlin 能根据传入的参数推断类型,泛型类型可以省略:
kotlin
fun main() {
val score = Score("数据结构与算法基础", "EP074512", 95)
// Kotlin 会自动推断 T 为 Int
val value: Int = score.value
println(value)
}
泛型参数可以有多个:
kotlin
class Test<K, V>(val key: K, val value: V)
fun main() {
val mapItem = Test<String, Int>("age", 18)
println(mapItem.key)
println(mapItem.value)
}
接口、抽象类也可以使用泛型:
kotlin
interface Repository<T> {
fun getById(id: String): T
}
子类继承泛型父类时,可以直接指定具体类型,也可以继续保留泛型:
kotlin
abstract class A<T> {
abstract fun test(): T
}
class B : A<String>() {
// 父类泛型明确为 String 后,返回值也必须是 String
override fun test(): String = "Hello World"
}
abstract class C<D> : A<D>() {
// 子类继续保留泛型 D
abstract override fun test(): D
}
fun main() {
val b = B()
println(b.test())
}
函数也可以定义泛型,写在函数名之前:
kotlin
fun <T> test(t: T): T = t
fun main() {
val value: String = test("Hello World")
println(value)
}
泛型也可以和函数类型一起使用:
kotlin
fun <T> test(func: (Int) -> T): T {
// func 接收 Int,返回值类型由 T 决定
return func(10)
}
fun <T> test2(value: T, func: T.() -> Unit) {
// func 是 T 类型的扩展函数
value.func()
}
1.2 官方高阶扩展函数
Kotlin 标准库中有很多常用的高阶扩展函数,例如 apply、let、also、run、takeIf、takeUnless 和 with。它们本质上都是对 Lambda 和泛型的封装。
apply:用于配置对象,最后返回对象本身。常用于对象初始化。
kotlin
public inline fun <T> T.apply(block: T.() -> Unit): T {
block() // 执行传入的 Lambda
return this // 返回当前对象本身
}
原来可能这样写:
kotlin
class Student(var name: String, var age: Int) {
fun hello() {
println("大家好,我叫$name")
}
}
fun main() {
val student: Student = Student("小明", 18)
student.name = "大明"
student.hello()
}
使用 apply 后可以写得更紧凑:
kotlin
fun main() {
Student("小明", 18).apply {
// apply 中的 this 指向当前 Student 对象
this.name = "大明"
}.hello()
}
kotlin
Student("小明", 18).apply {
// 这里编写对 Student 对象的配置逻辑
}
几个比较常用的:
let:把当前对象作为 it 传入 Lambda,并返回 Lambda 的结果。适合做空值判断后的继续处理。
kotlin
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
kotlin
fun main() {
val name = "Kotlin"
val length = name.let {
// it 指向 name
it.length
}
println(length)
}
also:把当前对象作为 it 传入 Lambda,最后返回对象本身。常用于插入日志、调试等附加操作。(跟 apply 功能很像)
kotlin
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
kotlin
fun main() {
val student = Student("小明", 18).also {
// also 中的 it 指向当前 Student 对象
println("创建了学生:${it.name}")
}
student.hello()
}
run:在当前对象作用域内执行 Lambda,并返回 Lambda 的结果。它和 let 类似,但对象通过 this 使用。
kotlin
public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
kotlin
fun main() {
val student = Student("小明", 18)
val text = student.run {
// run 中可以直接访问 name 和 age
"姓名:$name,年龄:$age"
}
println(text)
}
takeIf:如果条件成立,返回对象本身;否则返回 null。
kotlin
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
return if (predicate(this)) this else null
}
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
return if (!predicate(this)) this else null // 跟上面相反
}
kotlin
fun main() {
val str = "Hello World"
// 字符串长度大于 7 时重复一次,否则返回原字符串
val myStr = str.takeIf { it.length > 7 }?.let { it + it } ?: str
println(myStr)
}
with:不是扩展函数,需要手动传入对象,然后在 Lambda 中使用该对象。
kotlin
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
kotlin
fun main() {
val str = "Hello World"
val len = with(str) {
// this 指向 str
this.length
}
println(len)
}
简单记忆:
| 函数 | Lambda 中对象名称 | 返回值 |
|---|---|---|
apply |
this |
对象本身 |
also |
it |
对象本身 |
let |
it |
Lambda 结果 |
run |
this |
Lambda 结果 |
with |
this |
Lambda 结果 |
takeIf |
it |
满足条件返回对象,否则 null |
takeUnless |
it |
不满足条件返回对象,否则 null |
1.3 协变与逆变

Java 中如果 Integer 是 Number 的子类,那么 Integer 可以赋值给 Number。但对于泛型,List<Integer> 默认并不是 List<Number> 的子类。Kotlin 也是类似的,默认情况下泛型是不变的。
假设 Int 是 Number 的子类,泛型类型 Test<T> 可能有三种关系:
- 协变(Covariance):如果
Int是Number的子类,那么Test<Int>也可以看作Test<Number>的子类。 - 逆变(Contravariance):如果
Int是Number的子类,那么Test<Number>可以看作Test<Int>的子类。 - 不变(Invariant):
Test<Int>和Test<Number>没有继承关系,这是默认情况。
Kotlin 提供了两个关键字:
out:表示协变,主要用于"只生产数据"的场景,可以实现子类到父类的转换。in:表示逆变,主要用于"只消费数据"的场景,可以实现父类到子类的转换。
协变示例:
kotlin
class Test<T>(val data: T)
fun main() {
val test1: Test<Int> = Test(888)
// out Number 表示可以接收 Number 及其子类的 Test 对象
val test2: Test<out Number> = test1
val data: Number = test2.data
println(data)
}
逆变示例:
kotlin
class Consumer<T>(private var data: T) {
fun set(value: T) {
data = value
}
}
fun main() {
val test1: Consumer<Any> = Consumer(888)
// in Number 表示可以接收 Number 及其父类的 Consumer 对象
val test2: Consumer<in Number> = test1
test2.set(10)
}
out 修饰后,读取时可以当作上界类型使用:
kotlin
class Test<T>(val data: T)
fun main() {
val test: Test<out Number> = Test(888)
val data: Number = test.data
println(data)
}
in 修饰后,读取时具体类型不确定,只能当作 Any? 使用:
kotlin
class Test<T>(var data: T)
fun main() {
val test: Test<in Number> = Test<Any>(888)
val data: Any? = test.data
println(data)
}
协变如果允许随意写入,会产生类型安全问题:
kotlin
open class A
class B: A()
class C: A()
fun main() {
val test1: Test<B> = Test(B()) //这里存放的都是B类型的数据
val test2: Test<out A> = test1 //此时test2与test1是同一个对象,但是test2是out A
test2.data = C() //由于C是A的子类,按照正常情况来说可以直接用(但实际上这句会报错)
val data: B = test1.data //这下搞笑了,拿到的类型应该是C,结果接收的类型是B
}
因此可以这样理解:
out:使用out修饰的泛型不能用作函数的参数,对应类型的成员变量 setter 也会被限制,适合作为生产者。in:使用in修饰的泛型不能用作函数的返回值,对应类型的成员变量 getter 也会被限制,适合作为消费者。
星号投影 * 表示暂时不关心具体泛型类型,可以接收任意类型的泛型对象:
kotlin
class Test<T>(val data: T)
fun main() {
var test: Test<*> = Test(888)
test = Test("Hello")
// 读取出来的类型只能确定为 Any?
val data: Any? = test.data
println(data)
}
1.4 泛型界限
泛型界限用于限制泛型参数的范围。例如成绩只允许是数字类型,就可以把泛型上界限制为 Number。
kotlin
// T 必须是 Number 或 Number 的子类
class Score<T : Number>(private val name: String, private val id: String, val value: T)
fun main() {
val score1 = Score("数学", "EP001", 95)
val score2 = Score("英语", "EP002", 88.5)
println(score1.value)
println(score2.value)
}
如果需要多个上界,可以使用 where:
kotlin
class Score<T>(private val name: String, private val id: String, val value: T)
where T : Comparable<T>, T : Number
// T 必须同时是 Number 的子类,并且实现 Comparable<T>
fun main() {
val score: Score<Int> = Score("数据结构与算法", "EP710214", 96)
println(score.value)
}
也可以在定义类或接口时直接声明协变或逆变,这叫声明处型变。
kotlin
interface Test<out T> {
fun test(): T
// out T 适合作为返回值
}
interface Consumer<in T> {
fun test(t: T)
// in T 适合作为参数
}
这样在使用时就可以自动适配类型:
kotlin
interface Test<out T> {
fun test(): T
}
fun test(test: Test<Int>) {
val a: Test<Number> = test
// 因为 Test 使用了 out T,所以 Test<Int> 可以赋值给 Test<Number>
}
* 类型投影在不同情况下的默认含义不同:
-
对于
Foo<out T : TUpper>,Foo<*>等价于Foo<out TUpper>,读取时可以当作上界类型。kotlinclass Test<out T : Number>(val data: T) // 因为 T 使用 out,只能作为生产者,所以属性使用 val fun main() { val test: Test<*> = Test(10) val data: Number = test.data println(data) } -
对于
Foo<in T>,Foo<*>等价于Foo<in Nothing>,无法安全传入任何具体值。kotlinclass Test<in T> { fun set(t: T) { } } fun main() { val test: Test<*> = Test<Int>() // 编译错误:参数类型相当于 Nothing,不能传入 10 // test.set(10) } -
对于
Foo<T : TUpper>,读取时Foo<*>可看作Foo<out TUpper>,写入时可看作Foo<in Nothing>。kotlinclass Test<T : Number>(var data: T) fun main() { val test: Test<*> = Test(10) val data: Number = test.data println(data) // 编译错误:星号投影后,setter 被限制 // test.data = 10 }
如果一个泛型类有多个类型参数,每个类型参数都可以单独使用 *:
Function<*, String>等价于Function<in Nothing, String>。Function<Int, *>等价于Function<Int, out Any?>。Function<*, *>等价于Function<in Nothing, out Any?>。
1.5 类型擦除
泛型主要在编译阶段进行类型检查。代码编译后,泛型的具体类型信息通常不会继续保留,这就是类型擦除。它和 Java 泛型的基本思想类似。
例如下面的泛型类:
kotlin
class Test<T>(private var data: T) {
fun test(t: T): T {
val tmp = data
data = t
return tmp
}
}
编译后可以简单理解为类似下面的形式:
kotlin
class Test(private var data: Any?) { // 最后还是全部变成 Any? 类型了
fun test(t: Any?): Any? {
val tmp = data
data = t
return tmp
}
}
如果泛型存在上界,擦除后会变成上界类型:
kotlin
class Test<T : Number>(private var data: T)
// 可以理解为擦除后接近下面的形式
class Test(private var data: Number)
内联函数的泛型稍有不同。因为 inline 函数会在编译时把函数体复制到调用处,所以在某些场景下可以保留更具体的类型信息。
kotlin
inline fun <T> test(value: T): T {
val value2: T = value
return value2
}
fun main() {
val data: String = test("Hello World!")
println(data)
}
编译后可以理解为调用处直接展开:
kotlin
fun main() {
val value: String = "Hello World!"
val value2: String = value
val data: String = value2
println(data)
}
如果希望在函数体内部判断泛型类型,可以使用 inline + reified。reified 表示具化类型参数,只能用于内联函数。
kotlin
inline fun <reified T> isType(value: Any): Boolean {
// 因为 T 被 reified 修饰,所以这里可以使用 is T
return value is T
}
fun main() {
println(isType<String>("666"))
println(isType<Int>("666"))
}
普通泛型函数不能直接这样写:
kotlin
fun <T> isType(value: Any): Boolean {
// 编译错误:普通泛型 T 在运行时已经被擦除
// return value is T
return false
}
2.数组
2.1 数组的创建
Kotlin 中创建数组有两种方式:
-
工具函数:
arrayOf()、arrayOfNulls()、emptyArray() -
Array构造函数创建
kotlin
/**
* size: 数组大小
* init: 初始化函数,循环调用size次,下标作为参数,返回值填充对应位置
*/
public inline constructor(size: Int, init: (Int) -> T)
kotlin
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6) // 直接传入元素
array.size = 10 // 编译错误,数组长度不可修改
val arr: Array<String> = array // 编译错误,类型不匹配
println(array[0]) // 使用[]访问指定下标元素
val array: Array<Int> = emptyArray<Int>() // 创建空数组
val array: Array<Int?> = arrayOfNulls(10) // 创建可空元素数组
kotlin
fun main() {
val array: Array<String> = Array(5) { "我是元素$it" } // 自定义填充
for (s in array) {
println(s)
}
}
val array: Array<Double> = Array(5) { 1.5 } // 全部填充相同值
val array: Array<Int> = Array(10) { it * it } // 按规则生成:0, 1, 4, 9, 16...
遍历方式:
kotlin
fun main() {
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
for (i in 0..<array.size) { // 按下标遍历
println(array[i])
}
}
fun main() {
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
for (element in array) { // 直接遍历元素
println(element)
}
}
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
for (i in array.indices) { // indices返回有效索引范围
println(array[i])
}
for ((index, item) in array.withIndex()) { // 同时获取索引和元素
println("元素$item,位置: $index")
}
array.forEach { println(it) } // forEach遍历元素
array.forEachIndexed { index, item -> // 带索引的forEach
println("元素$item,位置: $index")
}
打印输出:
kotlin
val array: Array<Int> = arrayOf(7, 3, 9, 1, 6)
println(array.joinToString()) // 默认逗号分隔:7, 3, 9, 1, 6
println(array.joinToString(" - ", "> ", " <")) // 自定义分隔符、前后缀: > 7 - 3 - 9 - 1 - 6 <
println(array.joinToString(limit = 1, truncated = "...")) // 限制数量: 7, ...
println(array.joinToString() { (it * it).toString() }) // 自定义元素转换
2.2 数组的操作
内容比较:
kotlin
fun main() {
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val array2: Array<Int> = arrayOf(1, 2, 3, 4, 5)
println(array1 == array2) // == 不比较内容
println(array1.contentEquals(array2)) // 使用contentEquals比较内容
}
数组拷贝:
kotlin
fun main() {
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val array2: Array<Int> = array1.copyOf() // 完整拷贝
println(array2 === array1) // false,不同对象
}
val array2: Array<Int?> = array1.copyOf(10)
// 指定拷贝长度,超出部分填充null
val array2: Array<Int> = array1.copyOfRange(1, 3) // 拷贝指定范围:[1,3)
分割与拼接:
kotlin
val array1 = arrayOf(1, 2, 3, 4, 5)
val array2 = array1.sliceArray(1..3) // 截取指定范围
val array1 = arrayOf(1, 2, 3, 4, 5)
val array2 = arrayOf(6, 7, 8, 9, 10)
val array3 = array1 + array2 // 拼接数组
元素查找:
kotlin
val array = arrayOf(13, 16, 27, 32, 38)
println(array.contains(13)) // 判断是否包含
println(13 in array) // in运算符,等价于contains
println(array.indexOf(26)) // 查找元素下标
println(array.binarySearch(16)) // 二分查找(要求数组有序,效率更高)
快速判断与获取:
kotlin
val array = arrayOf(1, 2, 3, 4, 5)
println(array.any()) // 判断是否非空
println(array.first()) // 获取首个元素
println(array.last()) // 获取最后一个元素
数组翻转:
kotlin
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val array2: Array<Int> = array1.reversedArray() // 翻转并生成新数组
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
array1.reverse() // 原地翻转
array1.reverse(1, 3) // 翻转指定范围
打乱与排序:
kotlin
val array1: Array<Int> = arrayOf(1, 2, 3, 4, 5)
array1.shuffle() // 随机打乱顺序
array1.sort() // 排序(可自定义规则)
array1.sort(1, 3) // 仅排序指定范围
array1.sortDescending() // 降序排序
填充数组:
kotlin
val array1 = arrayOf(1, 2, 3, 4, 5)
array1.fill(10) // 全部填充为指定值
2.3 可变长参数
使用 vararg 关键字声明可变长参数:
kotlin
fun test(vararg strings: String) {
// 可变长参数在函数内可当作Array使用
}
fun main() {
test("AAA", "BBB", "CCC") // 可传入任意数量同类型参数
}
可变长参数在参数列表中只能有一个:
kotlin
fun test(vararg strings: String, a: Int) { ... } // 编译通过
fun test(a: Int, vararg strings: String) { ... } // 编译通过
fun test(vararg a: Int, vararg strings: String) { ... } // 编译错误,多个可变长参数
可变长参数本质是数组,但不能直接传入数组:
kotlin
fun main() {
val array = arrayOf("AAA", "BBB", "CCC")
test(array) // 编译错误,类型不匹配
}
使用扩展运算符(*)将数组展开为单个参数:
kotlin
fun main() {
val array = arrayOf("AAA", "BBB", "CCC")
test(*array) // 展开数组
}
test("111", *array, "DDD", "EEE") // 可与普通参数混合使用
2.4 原生类型数组
为避免装箱开销,Kotlin 为基本类型提供专用数组:
| 原生类型数组 | 对应 Java |
|---|---|
BooleanArray |
boolean[] |
ByteArray |
byte[] |
CharArray |
char[] |
DoubleArray |
double[] |
FloatArray |
float[] |
IntArray |
int[] |
LongArray |
long[] |
ShortArray |
short[] |
kotlin
fun main() {
// 使用 arrayOf 的变种 intArrayOf 快速生成 IntArray(其他同理)
val array: IntArray = intArrayOf(7, 3, 9, 1, 6)
array.forEach { println(it) }
}
2.5 多维数组
以二维数组为例,二维数组创建:
kotlin
val arr: Array<IntArray> = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4), intArrayOf(5, 6))
val arr: Array<Array<Int>> = Array(9) { Array(4) { 0 } } // 9×4矩阵,初始化为0
访问多维数组元素:
kotlin
val arr: Array<IntArray> = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4), intArrayOf(5, 6))
val item: Int = arr[0][0] // 连续使用[]访问
多维数组内容比较:
kotlin
fun main() {
val arr1: Array<IntArray> = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
val arr2: Array<IntArray> = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4))
println(arr1.contentEquals(arr2)) // false 只比较第一层
println(arr1.contentDeepEquals(arr2)) // true 深度比较所有层级
}
多维数组各子数组长度可不同,类型也可混合:
kotlin
val arr: Array<IntArray> = arrayOf(intArrayOf(1, 3, 4, 5), intArrayOf(2, 9))
val arr: Array<Array<out Any>> = arrayOf(arrayOf(1, 3), arrayOf("AAA", "BBB"))
3.集合类
Kotlin 集合分为三大类:
-
List:有序集合,通过索引访问,可包含重复元素
-
Set:无重复元素集合,通常不维护顺序
-
Map:键值对映射,键唯一,值可重复
所有集合(Map 除外)都继承自 Collection 接口:
kotlin
public interface Collection<out E> : Iterable<E> {
public val size: Int // 集合大小
public fun isEmpty(): Boolean // 是否为空
public operator fun contains(element: E): Boolean // 是否包含元素
override fun iterator(): Iterator<E> // 生成迭代器
public fun containsAll(elements: Collection<E>): Boolean // 是否包含另一个集合中所有的内容
}
3.1 List 集合
创建可变 List:
kotlin
fun main() {
val list: MutableList<Int> = mutableListOf(1, 2, 3, 4)
list[0] = 10 // 修改元素
println(list[1]) // 访问元素
println(list) // 输出: [10, 2, 3, 4]
}
添加元素:
kotlin
val list = mutableListOf(1, 2, 3, 4)
list.add(5) // 末尾添加
list.add(2, 666) // 指定位置插入
删除元素:
kotlin
val list = mutableListOf("AAA", "BBB", "CCC", "DDD")
list.removeAt(2) // 按索引删除
list.remove("DDD") // 按元素删除
创建只读 List:
kotlin
val list: List<String> = listOf("AAA", "BBB", "CCC", "DDD") // 不可修改
list[0] = "XXX" // ❌ 编译错误
遍历 List:
kotlin
val list: List<String> = listOf("AAA", "BBB", "CCC", "DDD")
for (s in list) {
println(s)
}
for ((index, item) in list.withIndex()) {
println("元素$item, 下标: $index")
}
3.2 Set 集合
创建 Set:
kotlin
fun main() {
val set: Set<String> = mutableSetOf("AAA", "BBB", "BBB", "CCC")
println(set) // 自动去重: [AAA, BBB, CCC]
}
Set 不支持按索引访问,只能添加 / 删除:
kotlin
val set: MutableSet<String> = mutableSetOf("AAA", "DDD", "CCC")
set.add("BBB")
println(set.elementAt(1)) // 按迭代顺序获取第N个元素
集合运算:
kotlin
val set1 = mutableSetOf("AAA", "DDD", "CCC")
val set2 = mutableSetOf("BBB", "CCC", "EEE")
println(set1 union set2) // 并集: [AAA, DDD, CCC, BBB, EEE]
println(set1 intersect set2) // 交集: [CCC]
println(set1 subtract set2) // 差集: [AAA, DDD]
各种 Set 实现:
kotlin
val set = hashSetOf("AAA", "DDD", "BBB") // 哈希实现,无序
val set = linkedSetOf("AAA", "DDD", "BBB") // 链表实现,保持插入顺序
val set = setOf("AAA", "DDD", "BBB") // 只读Set
val set = sortedSetOf("AAA", "DDD", "BBB") // 自动排序
val set = setOfNotNull("AAA", "DDD", "BBB", null) // 自动过滤Null元素
val hashSet = HashSet<String>() // 不重复且无序的Set集合
val linkedHashSet = LinkedHashSet<String>() // 跟mutableSetOf一样得到一个不重复且有序的Set集合
3.3 Map 集合
使用 to 中缀函数创建键值对:
kotlin
val pair: Pair<Int, String> = 10001 to "小明"
创建 Map:
kotlin
val map: MutableMap<Int, Student> = mutableMapOf(
10001 to Student("小明", 18),
10002 to Student("小红", 17),
10003 to Student("小刚", 19)
)
访问 Map:
kotlin
val student: Student? = map[10001] // 使用[]运算符通过Key查找Value,key不存在返回null
val keys: MutableSet<Int> = map.keys // 获取所有key
val values: Collection<Student> = map.values // 获取所有value
遍历 Map:
kotlin
map.forEach { (k, v) -> println("键: $k, 值 $v") }
for ((key, value) in map) {
println("键: $key, 值 $value")
}
for (entry in map) { // 这里得到的是一个entry
entry.key
entry.value
}
添加键值对:
kotlin
map[10004] = Student("小强", 18) // 直接赋值
map.put(10004, Student("小强", 18)) // 使用函数
val newMap = map + (10004 to Student("小强", 18)) // 添加新的键值对并生成一个新的Map
map += (10004 to Student("小强", 18)) // 直接添加键值对到当前Map里面
map += mapOf(10004 to Student("小强", 18)) // 或者添加其他Map到此Map中
map.putAll(mapOf(10004 to Student("小强", 18))) // 使用函数
map.putAll(setOf(10004 to Student("小强", 18))) // 键值对集合也可以
key 重复时会覆盖,返回被覆盖的值:
kotlin
val old = map.put(10003, Student("小强", 18)) // put的返回值如果没有覆盖元素返回null,否则返回被覆盖的元素
删除键值对:
kotlin
val old = map.remove(10001)
map -= 10001
map -= listOf(10001, 10002) // 批量删除
val map: MutableMap<Int, Student> = mutableMapOf(
10001 to Student("小明", 18),
10002 to Student("小红", 17),
10003 to Student("小明", 18) // 这里存在两个一样的元素
)
map.values.remove(Student("小明", 18)) // 通过这种方式移除也只会移除按顺序下来的第一个
println(map) // {10002=小红 (17), 10003=小明 (18)}
安全获取值:
kotlin
var student: Student = map.getOrDefault(10004, Student("小强", 16)) // 不存在返回默认值
var student: Student = map.getOrElse(10004){ Student("小强", 16) } // 函数式默认值
var student: Student = map.getOrPut(10004){ Student("小强", 16) } // 不存在则添加并返回
各种 Map 实现:
kotlin
val map1 = mapOf(1 to "AAA") // 只读Map
val map2 = hashMapOf(1 to "AAA") // 哈希实现,无序
val map3 = linkedMapOf(1 to "AAA") // 链表实现,保持插入顺序
val map4 = sortedMapOf(1 to "AAA") // 自动按key排序
val map5 = emptyMap<Int, String>() // 空Map
val hashMap = HashMap<Int, String>() // 采用构造函数创建的HashMap,不保存Key顺序的Map,同map2
val linkedHashSet = LinkedHashMap<Int, String>() // 采用构造函数创建的LinkedHashMap,保存Key顺序的Map,同map3
3.4 迭代器
所有集合和数组都可生成迭代器,用于统一遍历:
kotlin
val list = listOf("AAA", "BBB", "CCC")
val iterator: Iterator<String> = list.iterator()
迭代器接口定义:
kotlin
public interface Iterator<out T> {
public operator fun next(): T // 获取下一个元素
public operator fun hasNext(): Boolean // 如果还有元素没有遍历,那么返回true否则返回false
}
使用迭代器遍历:
kotlin
val iterator: Iterator<String> = list.iterator()
while (iterator.hasNext()) {
println(iterator.next())
}
迭代器统一了所有集合的遍历方式:
kotlin
fun <T> forEach(iterator: Iterator<T>) {
while (iterator.hasNext()) {
println(iterator.next())
}
}
forEach(listOf("A", "B", "C").iterator())
forEach(setOf("A", "B", "C").iterator())
forEach(arrayOf("A", "B", "C").iterator())
forEach(mapOf(1 to "AAA", 2 to "BBB",3 to "CCC").iterator())
迭代器是一次性的,重新遍历需生成新迭代器。只要是重写了
operator fun iterator()函数的类型,都可以使用for..in这样的语法去进行遍历,实际上,所有使用for..in语法的操作,最后都会被编译为使用迭代器的while操作。
List 专属迭代器:
kotlin
val iterator: ListIterator<String> = list.listIterator()
println(iterator.next()) // 正向迭代
println(iterator.nextIndex()) // 下一个元素索引
println(iterator.previous()) // 反向迭代
可变迭代器(支持遍历时删除):

在 JVM 环境下,Kotlin 默认不支持在迭代时修改集合里面的内容,无论是插入新的元素还是移除元素,都会 触发并发修改异常。
为了解决这个问题,Kotlin 为所有的 MutableCollection(所有非只读集合类)提供了一个特殊的用于生成 MutableIterator 的函数,只要使用的不是只读的集合类,都可以获得这个特殊的迭代器,它支持在遍历时对元素进行删除:
kotlin
val list = mutableListOf("AAA", "BBB", "CCC")
val iterator: MutableIterator<String> = list.iterator()
while (iterator.hasNext()) {
iterator.next()
iterator.remove() // 删除当前元素
}
3.5 集合与数组扩展操作
数组转集合:
kotlin
val array = arrayOf("AAA", "BBB", "CCC")
val list: List<String> = array.toList()
val list: MutableList<String> = array.toMutableList()
val set: Set<String> = array.toSet()
val set: MutableSet<String> = array.toMutableSet()
映射(map):
kotlin
val list = listOf("AAA", "BB", "CCCCC")
val lenList: List<Int> = list.map { it.length } // 元素转换
val mapList: List<String> = list.mapIndexed { index, it -> "$index.$it" } // 带索引
对于 Map 类型,还可以单独对所有 Key 或是 Value 进行操作:
kotlin
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3)
println(numbersMap.mapKeys { it.key.uppercase() }) //对所有的Key进行Map操作
println(numbersMap.mapValues { it.value + it.key.length })
压缩(zip)与解压(unzip):
kotlin
val list1 = listOf(1, 2, 3)
val list2 = listOf("AAA", "BBB", "CCC")
val pairs: List<Pair<Int, String>> = list1.zip(list2) // 按索引配对
val list = listOf(1 to "AAA", 2 to "BBB", 3 to "CCC")
val unzipList: Pair<List<Int>, List<String>> = list.unzip() // 拆分Pair
普通集合转 Map:
kotlin
val list = listOf("AAA", "BBB", "CCC")
val map1: Map<String, Int> = list.associateWith { it.length } // 元素为key
val map2: Map<Int, String> = list.associateBy { it.length } // 元素为value
val associate: Map<String, Int> = list.associate { it to it.length } //返回一个Pair
扁平化(flatten/flatMap):
kotlin
val list = listOf(listOf("AAA", "BBB"), listOf("CCC", "DDD"))
val flatten: List<String> = list.flatten() // 嵌套集合展平 [AAA, BBB, CCC, DDD]
val list = listOf(Container(listOf("A", "B")), Container(listOf("C", "D")))
val flatList: List<String> = list.flatMap { it.list } // 先映射再展平,通过Lambda将每一个Container映射为List
过滤(filter):
kotlin
val list = listOf("AAA", "BB", "CCC")
val filterList: List<String> = list.filter { it.length > 2 }
val filterList: List<String> = list.filterNotNull() // 过滤null
val filterList: List<String> = list.filterIsInstance<String>() // 过滤指定类型
// Map filter操作
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
println(filteredMap)
分区(partition):
kotlin
val list = listOf("AA", "BBB", "CC", "DDD")
//分区操作得到一个匹配列表和一个不匹配列表
val (match, mismatch) = list.partition { it.length > 2 }
println("匹配的列表: $match")
println("不匹配的列表: $mismatch")
条件判断:
kotlin
val list = listOf("AA", "BBB", "CC", "DDD")
list.any { it.length > 2 } // 判断集合中是否至少存在一个元素满足条件
list.none { it.length > 2 } // 判断集合中是否不存在任何一个元素满足条件
list.all { it.length > 2 } // 判断集合中是否每一个元素都满足条件
分组(groupBy):
kotlin
val list = listOf("AA", "BBB", "CC", "DDD")
println(list.groupBy { it.length }) // 按照字符串的长度进行分组 {2=[AA, CC], 3=[BBB, DDD]}
切片与裁剪:
kotlin
list.slice(1..2) // 截取指定范围
list.take(2) // 取前2个
list.takeLast(2) // 取后2个
list.drop(2) // 跳过前2个
list.dropLast(2) // 跳过后2个
list.takeWhile { it.length > 2 } // 从第一个元素开始,依次按照给定的函数进行判断,如果判断成功则添加到返回列表中,直到遇到一个不满足条件的就返回
重新分块:
kotlin
val list = listOf("AA", "BBB", "CC", "DDD")
//使用chunked进行分块,这里2个元素为一组进行分块,得到一个嵌套的集合
println(list.chunked(2)) //结果 [[AA, BBB], [CC, DDD]]
更多集合特性请参考官网:https://kotlinlang.org/docs/collections-overview.html
3.6 序列
序列(Sequence)只在需要时才生成和处理元素,处理大量数据时性能更好。
kotlin
public interface Sequence<out T> {
public operator fun iterator(): Iterator<T>
}
创建序列:
kotlin
val sequence: Sequence<Int> = generateSequence {
println("生成一个新的元素")
10 // 返回值就是生成的元素
}
普通集合与序列对比:
- 普通集合(立即求值)
kotlin
fun main() {
val list = listOf("AA", "BBB", "CCC", "DD", "EEE", "FF", "GGG", "HH")
// 以下操作用于获取前两个长度大于2的字符串,并进行小写转换操作
val result = list.filter {
println("进行过滤操作: $it")
it.length > 2
}.map {
println("进行小写转换操作")
it.lowercase()
}.take(2)
}

- 序列(惰性求值)
kotlin
fun main() {
val list = listOf("AA", "BBB", "CCC", "DD", "EEE", "FF", "GGG", "HH")
// 以下操作用于获取前两个长度大于2的字符串,并进行小写转换操作
val result = list.asSequence().filter {
println("进行过滤操作: $it")
it.length > 2
}.map {
println("进行小写转换操作")
it.lowercase()
}.take(2)
println(result.joinToString())
}

4.特殊类型
4.1 数据类
数据类既可以像普通类一样使用,也会由编译器自动生成一些常用成员函数,例如输出对象信息、比较实例、解构以及复制实例等。
声明一个数据类:
kotlin
// 在class前面添加data关键字表示为一个数据类
data class User(val name: String, val age: Int)
数据类必须满足以下要求:
- 主构造函数中至少有一个参数。
- 主构造函数中的参数必须标记为
val或var。 - 数据类不能是抽象的(
abstract)、开放的(open)、密封的(sealed)或内部的(inner)。
数据类声明后,编译器会根据主构造函数中声明的所有属性自动生成以下函数:
.equals()/.hashCode().toString(),生成的字符串格式类似于"User(name=John, age=42)".componentN(),按声明顺序生成,用于解构.copy(),用于复制对象
kotlin
data class User(val name: String, val age: Int)
fun main() {
val user1 = User("小明", 18)
val user2 = User("小明", 18)
println(user1) // 打印结果为格式化字符串:User(name=小明, age=18)
println(user1 == user2) // true,因为自动重写了equals函数
val (name, age) = user1 // 自动添加componentN函数,因此支持解构操作
println("名称: $name, 年龄: $age")
}
数据类成员函数的生成遵循以下规则:
-
如果数据类主体中已经显式(手动)实现了
.equals()、.hashCode()或.toString()等函数,或者父类中存在对应的final实现,则不会自动生成这些函数,而是使用已有实现。kotlindata class User(val name: String, val age: Int) { // 如果已经存在toString,则不会自动生成 override fun toString(): String = "我是自定义的toString" } fun main() { val user = User("小明", 18) println(user) // 结果: 我是自定义的toString } -
如果超类型具有
open的.componentN()函数,并且返回类型兼容,则数据类会生成相应函数并覆盖它;如果因关键字限制等原因无法覆盖,则会直接报错。kotlinabstract class AbstractUser { // 此函数必须是open的,否则无法被数据类继承 open operator fun component1() = "父类的component1" } data class User(val name: String, val age: Int): AbstractUser() // 自动覆盖父类的component1函数 -
不允许为
.componentN()和.copy()函数提供显式实现。
注意:编译器只会根据主构造函数中定义的属性生成对应函数。如果不希望某些属性参与自动生成的函数,就需要将其移出主构造函数。此时生成的所有函数都不会再考虑 age 属性:
kotlin
data class Person(val name: String) {
var age: Int = 0 // age属性不会被处理
}
数据类自带 .copy() 函数,可以复制对象,并允许在复制时修改其中一部分属性,其余属性保持不变。以上述 User 类为例:
kotlin
data class User(val name: String, val age: Int)
fun main() {
val user = User("小明", 18)
val copyUser = user.copy() // 使用拷贝函数生成一个内容完全一样的新对象
println(user == copyUser)
println(user === copyUser)
}
在调用 copy 函数时,也可以手动指定某些属性的新值:
kotlin
val user = User("小明", 18)
println(user.copy(age = 17)) // 结果为 User(name=小明, age=17)
4.2 枚举类
当需要存储和表示多种固定状态时,枚举类型非常适合:
kotlin
// 在类前面添加enum表示这是一个枚举类型
enum class LightState {
GREEN, YELLOW, RED // 直接在枚举类内部写上所有枚举的名称,一般全部用大写字母命名
}
使用枚举类对象时,可以通过类名直接获取已定义的枚举值:
kotlin
fun main() {
val state: LightState = LightState.RED // 直接得到红灯
println(state.name) // 自带name属性,也就是我们编写的枚举名称,这里是RED
}
枚举类也可以拥有成员:
kotlin
// 同样可以定义成员变量,但是不能命名为name,因为name用于返回枚举名称
enum class LightState(val color: String) {
GREEN("绿灯"), YELLOW("黄灯"), RED("红灯"); // 枚举在定义时也必须填写参数;如果后面还要编写成员函数等内容,末尾需要添加分号
fun isGreen() = this == LightState.GREEN // 定义一个函数也是没问题的
}
可以像普通类一样使用枚举类的成员:
kotlin
val state: LightState = LightState.RED
println("信号灯的颜色是: ${state.color}")
println("信号灯是否可以通行: ${state.isGreen()}")
枚举类型的状态是有限的,因此很适合配合 when 表达式进行判断:
kotlin
val state: LightState = LightState.RED
val message: String = when(state) {
LightState.RED -> "禁止通行"
LightState.YELLOW -> "减速通行"
LightState.GREEN -> "正常通行"
}
println(message) // 结果为: 禁止通行
枚举类中也可以声明抽象函数,并由每个枚举值自行实现:
kotlin
enum class LightState(val color: String) {
GREEN("绿灯"){
override fun test() = println("我是绿灯,表示可以通过")
}, YELLOW("黄灯") {
override fun test() = println("我是黄灯,表示需要减速")
}, RED("红灯") {
override fun test() = println("我是红灯,禁止通行")
};
abstract fun test() // 抽象函数
}
fun main() {
val state: LightState = LightState.RED
state.test() // 调用函数: 我是红灯,禁止通行
}
如果枚举类实现了某个接口,也可以按以下方式实现:
kotlin
interface Message {
fun test()
}
enum class LightState(val color: String) : Message {
GREEN("绿灯"){
override fun test() = println("我是绿灯,表示可以通过")
}, YELLOW("黄灯") {
override fun test() = println("我是黄灯,表示需要减速")
}, RED("红灯") {
override fun test() = println("我是红灯,禁止通行")
};
}
// 或者
enum class LightState(val color: String) : Message {
GREEN("绿灯"), YELLOW("黄灯"), RED("红灯");
override fun test() = println("")
}
枚举类还提供了许多常用属性和函数:
kotlin
val state: LightState = LightState.valueOf("RED") // 通过valueOf函数将字符串名称转换为对应枚举
val state: LightState = enumValueOf<LightState>("RED") // 同上
println(state)
println(state.ordinal) // 枚举所在的位置
println(state.name) // 枚举名称
kotlin
val entries: EnumEntries<LightState> = LightState.entries // 一键获取全部枚举,结果是EnumEntries类型,它是List的子接口,因此可以当做List使用
val values: Array<LightState> = enumValues<LightState>() // 或者像这样以Array形式获取所有枚举
println(entries)
values.forEach { println(it.color) }
4.3 匿名类
匿名类除了没有名字之外,也可以定义成员。由于它是直接创建出来的,因此不能定义任何构造函数。这种写法也称为 对象表达式。
kotlin
val obj = object { // 使用object关键字声明一个匿名类并创建其对象,可以直接使用变量接收得到的对象
val name: String = ""
override fun toString(): String = "我是一个匿名类" // 匿名类默认继承于Any,可以直接重写其toString
}
println(obj)
匿名类不仅可以直接定义,也可以作为某个类的子类,或者作为某个接口的实现:
kotlin
interface Person {
fun chat()
}
fun main() {
val obj: Person = object : Person { // 直接实现Person接口
override fun chat() = println("你好")
}
obj.chat() // 当做Person的实现类使用
}
kotlin
interface Person
open class Human(val name: String)
fun main() {
val obj: Human = object : Human("小明"), Person { // 继承类时,同样需要调用其构造函数
override fun toString() = "我叫$name" // 因为是子类,直接使用父类的属性也是没问题的
}
println(obj)
}
特别地,只存在一个抽象函数的接口称为 函数式接口 ,也叫 单一抽象方法(SAM)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。对于函数式接口,可以使用前面介绍的 Lambda 表达式来简化代码:
kotlin
fun interface KRunnable { // 在接口声明前面添加fun关键字
fun invoke()
}
...
fun main() {
val runnable = KRunnable { // 支持使用Lambda替换
println("我是函数invoke的实现")
}
runnable.invoke()
}
kotlin
fun interface Printer {
fun print()
}
fun test(printer: Printer) { // 需要Printer接口实现对象
printer.print()
}
fun main() {
test { // 直接Lambda一步到位
println("Hello World")
}
}
kotlin
open class Human(val name: String)
fun test() = object: Human("小明") { // 返回一个匿名类对象
val age: Int = 10
override fun toString() = "我叫$name"
}
fun main() {
println(test().name)
println(test().age) // 编译错误,因为返回类型是Human;由于匿名特性,只能当做Human使用
}
4.4 单例类
object 关键字除了用于声明匿名类型,也可以用于声明单例类。单例类在整个程序中只能存在一个对象,也就是单个实例,不能再创建其他对象。
kotlin
object Singleton { // 声明一个单例类
private var name = "小明"
override fun toString() = "我叫$name"
}
fun main() {
val singleton = Singleton // 通过类名直接得到此单例类的对象
// 不可以通过构造函数的形式创建对象
println(singleton)
}
kotlin
object Singleton {
fun test() = println("单例函数被调用")
}
fun main() {
Singleton.test() // 单例定义的函数直接使用类名即可调用
}
4.5 伴生对象
伴生对象可以让一个类既能像普通 class 一样创建对象,也能像单例类一样通过类名直接调用某些成员。本质上,就是把一个单例对象写到某个类的内部:
kotlin
class Student(val name: String, val age: Int) {
// 使用companion关键字在内部编写一个伴生对象,它同样是单例的
companion object Tools {
// 伴生对象定义的函数可以直接通过外部类名调用
fun create(name: String, age: Int) = Student(name, age)
}
}
fun main() {
// 现在Student不仅具有对象的函数,还可以通过类名直接调用其伴生对象提供的函数
val student = Student.create("小明", 18)
println(student.toString())
}
4.6 委托模式
委托模式是继承之外的一种良好替代方案。
kotlin
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() = println(x)
}
替代方案:委托
kotlin
class Derived(val base: Base): Base { // 将一个Base的实现类作为属性保存到类中,同样实现Base接口
override fun print() = base.print() // 真正实现接口的并不是当前类,而是被委托进来的对象
}
Kotlin 对这种模式提供了原生支持:
kotlin
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() = println(x)
}
class Derived(val b: Base): Base by b // 使用by关键字将接口中待实现的操作委托给指定成员
类的成员属性也可以委托给其他对象:
kotlin
import kotlin.reflect.KProperty
class Example {
var p: String by Delegate() // 属性也可以使用by关键字委托给其他对象
}
// 委托的类
class Delegate {
// 需要重载属性的获取和设置两个运算
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 这里委托了 ${property.name} 属性"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 属性赋值为 $value")
}
}
fun main() {
println(Example().p)
}
Kotlin 标准库中也提供了许多预设委托函数:
kotlin
class Example {
val p: String by lazy { "延迟初始化的值" } // lazy会生成一个委托对象,首次获取属性值时才会执行代码块;由于只能获取,所以只支持val变量
}
fun main() {
println(Example().p)
}
也可以设置观察者,实时观察变量变化:
kotlin
class Example {
var p: String by Delegates.observable("我是初始值") {
prop, old, new ->
println("检测到$prop 的值发生变化,旧值:$old -> 新值:$new")
}
}
fun main() {
Example().p = "新值"
}
属性还可以直接委托给另一个属性:
kotlin
class Example(var a: String) {
var p: String by ::a // 使用双冒号来委托给其他属性
}
fun main() {
val example = Example("初始值")
println(example.p)
}
属性也可以被委托给一个 Map 来存储:
kotlin
class User(map: MutableMap<String, Any>) {
var name: String by map // 直接委托给外部传入的Map集合
var age: Int by map // 变量的值从Map中进行存取
override fun toString(): String = "名称: $name, 年龄: $age"
}
fun main() {
val map: MutableMap<String, Any> = mutableMapOf(
"name" to "John Doe",
"age" to 25
)
val user = User(map)
println(user) // 名称: John Doe, 年龄: 25
map["age"] = 10 // 映射的值修改会直接影响变量的值
println(user) // 名称: John Doe, 年龄: 10
}
注意:使用不可变的 Map 时,只能委托给 val 类型变量,因为不可变 Map 无法修改。
4.7 密封类
有时我们会编写一些类供他人使用,但不希望这些类被随意继承,只希望它们在当前框架或模块内部使用。此时可以将类或接口设定为密封的。
kotlin
package com.test
sealed class A // 声明密封类很简单,直接添加sealed关键字即可
sealed class B: A() // 密封类在同一个模块或包中可以被继承,并且子类也可以继续是密封的
当我们在其他包中使用这个密封类时,就无法继续继承它:
kotlin
class C: A() // 编译错误,不在同一个模块
fun main() {
val b = B() // 编译错误,不可以实例化
}
密封类本身也是抽象的,不能直接实例化,并且可以具有 abstract 成员:
kotlin
sealed class A
sealed class B: A() {
abstract fun test()
}
密封类被继承后,也可以让子类不再继续保持密封,使外部可以正常使用:
kotlin
sealed class A
class B: A()
class C: A()
class D: A() // 不添加sealed关键字,使其不再密封
由于类 A 是密封的,因此所有直接继承自 A 的类只能由我们自己编写,其他人无法编写新的直接子类;除非我们将某个子类设定为 open,允许它继续被继承。这也说明,密封类在设计之初就确定了可用的子类范围。
由于密封类的子类范围有限,因此在使用 when 进行类型判断时,也可以进行穷尽判断:
kotlin
fun main() {
val a: A = C()
when(a) {
is B -> println("是B")
is C -> println("是C")
is D -> println("是D")
}
}
5.异常
5.1 异常使用
Kotlin 中的异常都继承自 Throwable 类。
kotlin
// Exception继承自Throwable类,作为普通的异常类型
// 自定义异常
class TestException(message: String) : Exception(message)
fun main() {
// 抛出异常
throw TestException("自定义异常")
}
5.2 异常处理
可以使用 try-catch 语句块来处理异常:
kotlin
fun main() {
val a: Array<Int> = arrayOf(1, 2, 3)
try {
a[5] = 5
println(array[1])
}catch (e: Exception){
// 异常本身也是一个对象,catch中实际上就是用一个局部变量去接收异常
println(e.stackTraceToString())
}finally {
// 无论是否出现异常,都会在最后执行
println("finally")
}
println("Hello World!")
}
catch 中捕获的类型只能是 Throwable 的子类,也就是说,要么是异常,要么是错误,不能是其他类型。
当代码可能出现多种类型的异常时,如果希望针对不同异常分别处理,可以使用多重异常捕获:
kotlin
try {
//....
} catch (e: Exception) { // 父类型在前,会将子类异常一并捕获
} catch (e: NullPointerException) { // 因为NullPointerException是Exception的子类型,永远都不会进入这里
} catch (e: IndexOutOfBoundsException) { // 永远都不会进入这里
}
try 也可以作为表达式使用,这意味着它可以有返回值:
kotlin
fun main() {
val input = readln()
val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }
println(a)
}
针对可空类型,也可以在判断为空时直接抛出异常:
kotlin
val s = person.name ?: throw IllegalArgumentException("Name required")
✨✨✨