Kotlin 学习笔记-程序结构

363 阅读12分钟

一、常量与变量

1.1 定义变量和常量

Kotlin 中定义变量和常量有两种关键字:valvar

  • val:不可变引用(相当于 Java 中的 final 变量)。
  • var:可变引用。
val constantValue = 42 // 常量
var variableValue = 42 // 变量
variableValue = 43 // 变量可以重新赋值

1.2 类型推断

Kotlin 有强大的类型推断功能,在定义变量和常量时可以省略类型声明,编译器会自动推断类型:

val constantValue = 42 // 编译器推断类型为 Int
var variableValue = "Hello" // 编译器推断类型为 String

如果需要显式声明类型,也可以这样写

val constantValue: Int = 42
var variableValue: String = "Hello"

1.3 延迟初始化

对于 var 变量,如果在定义时无法立即赋值,可以使用 lateinit 关键字延迟初始化。lateinit 只能用于可变的非空类型的属性,并且不能用于原始类型(如 IntFloat)。

lateinit var name: String

fun initializeName() {
    name = "Kotlin"
}

fun printName() {
    if (this::name.isInitialized) {
        println(name)
    } else {
        println("Name is not initialized")
    }
}

对于不可变类型(val),可以使用 lazy 委托属性进行延迟初始化,lazy 初始化的代码块只会在第一次访问该属性时执行:

val name: String by lazy {
    println("Computing the name...")
    "Kotlin"
}

fun main() {
    println(name) // 第一次访问,执行 lazy 代码块,输出 "Computing the name..." 和 "Kotlin"
    println(name) // 后续访问,不再执行 lazy 代码块,只输出 "Kotlin"
}

1.4 不可变集合

Kotlin 提供了不可变集合(如 ListSetMap),这些集合类型是使用 val 定义的,不能改变其引用,但集合本身可以是可变的:

val list = listOf(1, 2, 3) // 不可变列表
val mutableList = mutableListOf(1, 2, 3) // 可变列表

mutableList.add(4) // 可以修改可变列表的内容

1.5 编译时常量与运行时常量

  • 编译时常量:使用 const val 声明的常量,在编译期确定其值,必须是顶层属性或者 object 中的属性,且类型必须是基本类型或 String。

  • 运行时常量:使用 val 声明的常量,其值在运行时确定,可以是任意类型。

const val COMPILE_TIME_CONSTANT = 42 // 编译时常量

val runtimeConstant: Int
    get() {
        // 一些运行时计算
        return (1..100).random()
    }

二、函数

2.1 基本定义

Kotlin 中定义函数使用 fun 关键字,函数可以有返回类型,也可以没有返回类型。

fun sum(a: Int, b: Int): Int {
    return a + b
}

//实际上,`printSum` 函数返回 `Unit`,但在函数声明中可以省略 `Unit` 返回类型
//Unit 它是一个对象
fun printSum(a: Int, b: Int) {
    println("Sum of $a and $b is ${a + b}")
} 

简写形式:

fun sum(a: Int, b: Int) = a + b

2.2 局部函数

Kotlin 允许在函数内部定义函数,这种函数称为局部函数,通常用于封装逻辑。

fun outerFunction(a: Int, b: Int) {
    fun innerFunction(x: Int): Int {
        return x * x
    }

    val result = innerFunction(a) + innerFunction(b)
    println("Result: $result")
}

编译后的 Java 代码(示例) 编译后的 Java 代码将展示局部函数如何被转换为静态私有方法。

public final class ExampleKt {
    public static final void outerFunction(int a, int b) {
        int result = ExampleKt.innerFunction(a) + ExampleKt.innerFunction(b);
        System.out.println("Result: " + result);
    }

    private static final int innerFunction(int x) {
        return x * x;
    }

    public static void main(String[] args) {
        outerFunction(2, 3); // 输出 "Result: 13"
    }
}

2.3 扩展函数

扩展函数允许在不修改现有类的情况下向其添加新功能。

fun String.addExclamation() = this + "!" //扩展了String类

val message = "Hello".addExclamation()
println(message) // 输出 "Hello!"

2.4 内联函数

内联函数使用 inline 关键字,可以减少高阶函数带来的性能开销。

内联函数原理 当一个函数被标记为 inline 时,Kotlin 编译器会在编译时将该函数的代码替换到每个调用该函数的地方。这意味着函数调用的开销被消除了,减少了 Lambda 表达式和函数对象的创建。

inline fun inlineCalculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

* 2.4.1 使用内联函数的注意事项

  • 代码膨胀: 内联函数会将函数体复制到每个调用点,如果函数体非常大或被频繁调用,可能会导致代码膨胀(code bloat)。这可能会增加最终生成的字节码大小。

  • 禁止内联: 并非所有函数都适合内联。可以使用 noinline 关键字来禁止某些 Lambda 表达式内联。假设我们有一个高阶函数,它接受两个 Lambda 表达式作为参数,并且我们希望其中一个 Lambda 表达式不被内联:

inline fun <T> execute(block: () -> T, noinline onComplete: (T) -> Unit): T {
    val result = block()
    onComplete(result)
    return result
}
  • 递归函数:递归函数不能标记为 inline,因为内联会导致无限递归的展开,从而导致编译失败。
  • 非局部返回:在内联函数中,Lambda 表达式可以非局部返回(即从包含它的函数返回)。如果不希望允许非局部返回,可以使用 crossinline 关键字。
inline fun inlineFunction(block: () -> Unit) {
    println("Before block")
    block()
    println("After block")
}

fun main() {
    inlineFunction {
        println("Inside block")
        return // 非局部返回,直接返回到 main 函数
    }
    println("This will not be printed")
}

在上面的例子中,return 语句直接从 main 函数返回,导致 println("This will not be printed") 永远不会执行。

现在,来看一下 crossinline 如何防止非局部返回:

inline fun inlineFunction(crossinline block: () -> Unit) {
    println("Before block")
    block()
    println("After block")
}

fun main() {
    inlineFunction {
        println("Inside block")
        // return // 编译错误,不能从 crossinline 的 Lambda 表达式中非局部返回
    }
    println("This will be printed")
}

2.5 尾递归函数

尾递归函数使用 tailrec 关键字优化递归调用,防止栈溢出。详见:高阶函数-尾递归优化

2.6 匿名函数

匿名函数类似于普通函数,但没有函数名。匿名函数可以指定返回类型,并且可以在函数体中使用 return 语句。

val sum = fun(x: Int, y: Int): Int {
    return x + y
}

println(sum(2, 3)) // 输出 5

三、Lambda 表达式

3.1 基本语法

Lambda 表达式的基本语法如下:

val lambda: (Int, Int) -> Int = { x, y -> x + y }
  • Lambda 表达式由花括号 {} 包围。

  • 参数在箭头 -> 之前声明。

  • 表达式在箭头 -> 之后。

3.2 单个参数的隐式名称 it

如果 Lambda 表达式只有一个参数,可以使用隐式名称 it 来引用该参数:

val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
println(doubled) // 输出 [2, 4, 6, 8, 10]

3.3 多行 Lambda 表达式

Lambda 表达式可以包含多行代码,在这种情况下,最后一行代码是返回值:

val sum = { x: Int, y: Int ->
    println("Adding $x and $y")
    x + y
}

println(sum(2, 3)) // 输出 "Adding 2 and 3" 和 5

3.4 Lambda 表达式作为参数

Lambda 表达式可以作为函数参数传递:

fun performOperation(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

val result = performOperation(5, 3) { x, y -> x + y }
println(result) // 输出 8

3.5 返回 Lambda 表达式的函数

可以定义返回 Lambda 表达式的函数:

fun getOperation(): (Int, Int) -> Int {
    return { x, y -> x + y }
}

val operation = getOperation()
println(operation(5, 3)) // 输出 8

3.6 带接收者的 Lambda 表达式

Kotlin 支持带接收者的 Lambda 表达式,可以用来模拟扩展函数。接收者作为 Lambda 表达式的隐式参数,可以直接调用接收者对象的成员:

val stringAppender: StringBuilder.() -> Unit = { 
    this.append("Hello ")
    append("World!")
}

val stringBuilder = StringBuilder()
stringBuilder.stringAppender()
println(stringBuilder.toString()) // 输出 "Hello World!"

3.7 闭包

Lambda 表达式可以捕获并访问其所在环境中的变量,这种行为称为闭包

var sum = 0
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach { sum += it }
println(sum) // 输出 15

3.8 匿名函数与Lambda 表达式

Lambda 表达式是匿名函数类型的实例,它与匿名函数有如下区别

  • 语法

    • Lambda 表达式更简洁,适合短小的代码段。
    • 匿名函数语法更接近普通函数,适合需要显式返回类型和 return 语句的场景。
  • 返回

    • 在 Lambda 表达式中,return 语句通常用于非局部返回,从包含它的函数返回。
    fun foo() {
    println("Starting foo")
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // 非局部返回,直接返回到 foo 函数
        println(it)
    }
    println("This line will not be printed")
    }
    
    fun main() {
        foo() // 输出 "Starting foo", "1", "2"
        println("End of main")
    }
    
    • 在匿名函数中,return 语句只返回到匿名函数内部,不会影响外部函数。
  • 类型推断

    • Lambda 表达式的参数类型通常由上下文推断。
    • 匿名函数的参数类型需要显式声明。

四、类成员(成员方法、成员变量)

4.1 属性(成员变量)

  • 基本属性声明:Kotlin 中的属性可以使用 valvar 关键字声明,分别表示不可变和可变属性。
    class Person {
        val name: String = "John" // 不可变属性
        var age: Int = 30         // 可变属性
    }
    
  • 延迟初始化属性:对于不能立即初始化的属性,可以使用 lateinit 关键字进行延迟初始化。这只能用于 var 属性,并且不能用于原始类型(如 IntFloat)。
    class Person {
        lateinit var name: String
    
        fun initializeName(name: String) {
            this.name = name
        }
    
        fun printName() {
            if (this::name.isInitialized) {
                println(name)
            } else {
                println("Name is not initialized")
            }
        }
    }
    
  • 自定义 getter 和 setter:可以自定义属性的 getter 和 setter 方法
    class Person {
        var age: Int = 0
            get() = field
            set(value) {
                if (value >= 0) {
                    field = value
                } else {
                    println("Age cannot be negative")
                }
            }
    }
    
  • 惰性初始化:使用 lazy 委托属性进行惰性初始化。lazy 只适用于 val 属性,并且只能在第一次访问时初始化。
    class Person {
        val name: String by lazy {
            println("Initializing name")
            "John Doe"
        }
    }
    

4.2 可见性修饰符

Kotlin 支持四种可见性修饰符:publicprivateprotectedinternal

  • public:默认修饰符,任何地方都可以访问。
  • private:仅在类内部可见。
  • protected:在类及其子类中可见。
  • internal:在同一模块中可见。

4.3 扩展函数

扩展函数允许向现有类添加新功能,而不需要继承或使用装饰器模式。

fun Person.sayGoodbye() {
    println("Goodbye!")
}

fun main() {
    val person = Person()
    person.sayGoodbye() // 扩展函数调用
}

4.4 初始化块

init 块用于初始化类中的属性。它会在主构造函数之后执行。

class Person(val name: String, var age: Int) {
    init {
        println("Person initialized with name: $name and age: $age")
    }
}

4.5 次构造函数

次构造函数使用 constructor 关键字声明,可以有多个次构造函数,并且它们需要调用主构造函数。

class Person(val name: String) {
    var age: Int = 0

    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
}

4.6 数据类

数据类用于仅包含数据的类,并自动生成常用方法,如 toString()equals()hashCode()copy()

data class User(val name: String, val age: Int)

fun main() {
    val user1 = User("Alice", 30)
    val user2 = user1.copy(name = "Bob")
    println(user1) // 输出 "User(name=Alice, age=30)"
    println(user2) // 输出 "User(name=Bob, age=30)"
}

五、基本运算

5.1 算术运算

Kotlin 支持常见的算术运算符

val a = 10
val b = 20

// 加法
val sum = a + b

// 减法
val difference = a - b

// 乘法
val product = a * b

// 除法
val quotient = b / a

// 取模(余数)
val remainder = b % a

println("sum: $sum, difference: $difference, product: $product, quotient: $quotient, remainder: $remainder")
// 输出:sum: 30, difference: -10, product: 200, quotient: 2, remainder: 0

5.2 比较运算

Kotlin 支持常见的比较运算符:

val x = 10
val y = 20

// 相等
val isEqual = (x == y)

// 不相等
val isNotEqual = (x != y)

// 大于
val isGreater = (x > y)

// 小于
val isLess = (x < y)

// 大于或等于
val isGreaterOrEqual = (x >= y)

// 小于或等于
val isLessOrEqual = (x <= y)

println("isEqual: $isEqual, isNotEqual: $isNotEqual, isGreater: $isGreater, isLess: $isLess, isGreaterOrEqual: $isGreaterOrEqual, isLessOrEqual: $isLessOrEqual")
// 输出:isEqual: false, isNotEqual: true, isGreater: false, isLess: true, isGreaterOrEqual: false, isLessOrEqual: true

5.3 逻辑运算

Kotlin 支持常见的逻辑运算符:

val a = true
val b = false

// 逻辑与
val and = a && b

// 逻辑或
val or = a || b

// 逻辑非
val notA = !a

println("and: $and, or: $or, notA: $notA")
// 输出:and: false, or: true, notA: false

5.4 位运算

Kotlin 支持位运算符,主要用于整数类型:

val a = 2  // 二进制:10
val b = 3  // 二进制:11

// 按位与
val and = a and b // 结果:2(10)

// 按位或
val or = a or b   // 结果:3(11)

// 按位异或
val xor = a xor b // 结果:1(01)

// 按位取反
val inv = a.inv() // 结果:-3(按位取反后得到的补码)

// 左移
val shl = a shl 1 // 结果:4(100)

// 右移
val shr = a shr 1 // 结果:1(1)

// 无符号右移
val ushr = a ushr 1 // 结果:1(1)

println("and: $and, or: $or, xor: $xor, inv: $inv, shl: $shl, shr: $shr, ushr: $ushr")
// 输出:and: 2, or: 3, xor: 1, inv: -3, shl: 4, shr: 1, ushr: 1

5.5 赋值运算

Kotlin 支持常见的赋值运算符:

var x = 10

// 加法赋值
x += 5  // x = x + 5

// 减法赋值
x -= 3  // x = x - 3

// 乘法赋值
x *= 2  // x = x * 2

// 除法赋值
x /= 4  // x = x / 4

// 取模赋值
x %= 3  // x = x % 3

println("x: $x")
// 输出:x: 1

5.6 范围运算符

Kotlin 提供了简洁的范围运算符:

fun main() {
    // 使用 `..` 运算符创建一个闭区间的范围(包含范围的结束值)。
    val inclusiveRange = 1..5
    println("Inclusive Range:")
    for (i in inclusiveRange) {
        println(i) // 输出 1, 2, 3, 4, 5
    }

    // 使用 `until` 运算符创建一个开区间的范围(不包含范围的结束值)。
    val exclusiveRange = 1 until 5
    println("Exclusive Range:")
    for (i in exclusiveRange) {
        println(i) // 输出 1, 2, 3, 4
    }

    // 使用 `downTo` 运算符创建一个降序的范围。
    val descendingRange = 5 downTo 1
    println("Descending Range:")
    for (i in descendingRange) {
        println(i) // 输出 5, 4, 3, 2, 1
    }

    // 使用 `step` 函数指定范围的步长。
    val stepRange = 1..10 step 2
    println("Step Range:")
    for (i in stepRange) {
        println(i) // 输出 1, 3, 5, 7, 9
    }
}

5.7 其他常用运算符

5.7.1 安全调用运算符

用于处理可空类型的安全调用:

val str: String? = null
val length = str?.length // 如果 str 不为 null,则返回其长度,否则返回 null
println("length: $length") // 输出:length: null

5.7.2 Elvis 运算符

用于为可空类型提供默认值:

val str: String? = null
val length = str?.length ?: -1 // 如果 str 不为 null,则返回其长度,否则返回 -1
println("length: $length") // 输出:length: -1

5.7.3 非空断言运算符

如果你确定某个可空值不为 null,可以使用非空断言运算符 !!

val str: String? = "Hello"
val length = str!!.length // 如果 str 为 null,会抛出 NullPointerException
println("length: $length") // 输出:length: 5

5.8 重载运算符(自定义运算符)

5.8.1 基本使用

以下是一些常见的可重载运算符及其对应的函数名称:

  • + 对应 plus
  • - 对应 minus
  • * 对应 times
  • / 对应 div
  • % 对应 rem
  • .. 对应 rangeTo
  • [] 对应 getset
  • in 对应 contains

重载 + 运算符

首先,我们定义一个向量类,然后重载 + 运算符来实现向量相加:

data class Vector(val x: Int, val y: Int) {
    operator fun plus(other: Vector): Vector {
        return Vector(x + other.x, y + other.y)
    }
}

fun main() {
    val v1 = Vector(1, 2)
    val v2 = Vector(3, 4)
    val v3 = v1 + v2
    println(v3) // 输出 Vector(x=4, y=6)
}

重载 .. 运算符

接下来,我们定义一个范围类,并重载 .. 运算符来创建范围:

class MyRange(val start: Int, val end: Int)

operator fun Int.rangeTo(other: Int) = MyRange(this, other)

fun main() {
    val myRange = 1..5
    println("Start: ${myRange.start}, End: ${myRange.end}") // 输出 Start: 1, End: 5
}

重载 getset 运算符

我们定义一个包含整数数组的 Box 类,并重载 getset 运算符,使其可以通过索引访问和修改数组中的元素:

fun main() {
    val box = Box(5)

    // 使用 set 运算符设置数组元素
    box[0] = 1
    box[1] = 2
    box[2] = 3
    box[3] = 4
    box[4] = 5

    // 使用 get 运算符获取数组元素
    for (i in 0 until box.size) {
        print("box[$i]=${box[i]}  ")
        //输出 box[0]=2  box[1]=3  box[2]=4  box[3]=5  box[4]=6  
    }
}

class Box(val size: Int) {
    private val elements = IntArray(size)

    // 重载 get 运算符
    operator fun get(index: Int): Int {
        return elements[index]
    }

    // 重载 set 运算符
    operator fun set(index: Int, value: Int) {
        elements[index] = value + 1
    }
}

重载 in 运算符

我们可以重载 in 运算符来实现自定义包含检查。例如,定义一个区间类并重载 contains 函数

class MyRange(val start: Int, val end: Int) {
    operator fun contains(value: Int): Boolean {
        return value in start..end
    }
}

fun main() {
    val myRange = MyRange(1, 5)
    println(3 in myRange) // 输出 true
    println(6 in myRange) // 输出 false
}

5.8.2 规则和限制

  • 重载运算符只能在类或对象的成员函数中实现。

  • 重载运算符的函数必须使用 operator 修饰符。

  • 重载运算符的函数名称必须与相应的运算符函数名称匹配。

六、表达式(中缀表达式、分支表达式、when表达式)

6.1 中缀表达式

中缀表达式是一种简洁的调用方法的方式,使用 infix 关键字定义。它们通常用于表示具有自然语言风格的操作。

定义中缀函数

要定义一个中缀函数,需要满足以下条件:

  • 必须是成员函数或扩展函数
  • 必须只有一个参数
  • 必须使用 infix 关键字修饰
class Person(val name: String) {
    infix fun likes(other: Person): Boolean {
        return this.name == other.name
    }
}

fun main() {
    val alice = Person("Alice")
    val bob = Person("Bob")

    // 使用中缀表达式
    val result = alice likes bob
    println(result) // 输出 false
}

6.2 分支表达式

Kotlin 的 if 表达式不仅可以作为条件语句使用,还可以作为表达式返回值。

if 表达式

fun max(a: Int, b: Int): Int {
    return if (a > b) {
        a
    } else {
        b
    }
}

fun main() {
    val maxValue = max(10, 20)
    println(maxValue) // 输出 20
}

单行 if 表达式

fun max(a: Int, b: Int): Int = if (a > b) a else b

6.3 when 表达式

when 表达式是 Kotlin 中用于多分支选择的结构,功能类似于其他语言的 switch 语句。when 表达式可以返回一个值。

6.3.1 基本用法

fun describe(obj: Any): String =
    when (obj) {
        1          -> "One"
        "Hello"    -> "Greeting"
        is Long    -> "Long"
        !is String -> "Not a string"
        else       -> "Unknown"
    }

fun main() {
    println(describe(1))       // 输出 "One"
    println(describe("Hello")) // 输出 "Greeting"
    println(describe(1000L))   // 输出 "Long"
    println(describe(2))       // 输出 "Unknown"
}

6.3.2 when 表达式作为返回值

when 表达式可以作为返回值,赋值给变量:

fun getColorName(color: String): String {
    return when (color) {
        "Red"   -> "Color is Red"
        "Green" -> "Color is Green"
        "Blue"  -> "Color is Blue"
        else    -> "Unknown Color"
    }
}

fun main() {
    val color = getColorName("Red")
    println(color) // 输出 "Color is Red"
}

6.3.3 组合条件

when 表达式中,可以使用逗号组合多个条件:

fun getNumberDescription(number: Int): String {
    return when (number) {
        1, 2, 3 -> "Number is 1, 2, or 3"
        4, 5, 6 -> "Number is 4, 5, or 6"
        else    -> "Unknown Number"
    }
}

fun main() {
    println(getNumberDescription(2)) // 输出 "Number is 1, 2, or 3"
}

6.3.4 区间和集合

when 表达式可以检查数值是否在某个区间或集合内:

fun getNumberCategory(number: Int): String {
    return when (number) {
        in 1..10  -> "Number is between 1 and 10"
        in 11..20 -> "Number is between 11 and 20"
        else      -> "Number is out of range"
    }
}

fun main() {
    println(getNumberCategory(5))  // 输出 "Number is between 1 and 10"
    println(getNumberCategory(15)) // 输出 "Number is between 11 and 20"
    println(getNumberCategory(25)) // 输出 "Number is out of range"
}

6.3.5 带参数的 when

when 表达式可以根据任意条件进行分支,而不需要使用输入参数:

fun getDayType(day: String): String {
    return when {
        day.equals("Saturday", ignoreCase = true) -> "Weekend"
        day.equals("Sunday", ignoreCase = true)   -> "Weekend"
        else                                      -> "Weekday"
    }
}

fun main() {
    println(getDayType("Saturday")) // 输出 "Weekend"
    println(getDayType("Monday"))   // 输出 "Weekday"
}

七、循环语句(for循环、while循环、continue、break)

7.1 for 循环

Kotlin 的 for 循环用于遍历集合、区间或数组。for 循环的语法非常简洁,可以使用 in 关键字来遍历元素。

7.1.1 基本用法

//遍历集合:
val items = listOf("apple", "banana", "kiwi")
for (item in items) {
    println(item)
}
// 输出:
// apple
// banana
// kiwi

//遍历区间:
for (i in 1..5) {
    println(i)
}
// 输出:
// 1
// 2
// 3
// 4
// 5

//遍历数组:
val array = arrayOf(1, 2, 3, 4, 5)
for (i in array) {
    println(i)
}
// 输出:
// 1
// 2
// 3
// 4
// 5

7.1.2 遍历带索引的集合

使用 withIndex 遍历集合时,可以同时获取元素和索引:

val items = listOf("apple", "banana", "kiwi")
for ((index, item) in items.withIndex()) {
    println("Item at $index is $item")
}
// 输出:
// Item at 0 is apple
// Item at 1 is banana
// Item at 2 is kiwi

7.2 while 循环

while 循环在给定条件为 true 时重复执行循环体。条件在每次迭代前检查。

var x = 5
while (x > 0) {
    println(x)
    x--
}
// 输出:
// 5
// 4
// 3
// 2
// 1

7.3 do-while 循环

do-while 循环类似于 while 循环,但它先执行循环体,然后检查条件。因此,无论条件是否为 true,循环体至少会执行一次。

var y = 5
do {
    println(y)
    y--
} while (y > 0)
// 输出:
// 5
// 4
// 3
// 2
// 1

7.4 continue 和 break

continuebreak 用于控制循环的执行。continue 跳过当前迭代并继续下一次迭代,而 break 则终止循环。

7.4.1 continue

for (i in 1..5) {
    if (i == 3) continue // 跳过当前迭代
    println(i)
}
// 输出:
// 1
// 2
// 4
// 5

7.4.2 break

for (i in 1..5) {
    if (i == 3) break // 终止循环
    println(i)
}
// 输出:
// 1
// 2

7.4.3 标签(Label)

Kotlin 支持为循环加标签,允许在多重循环中使用 breakcontinue 跳出或继续指定的循环。

outer@ for (i in 1..5) {
    for (j in 1..5) {
        if (j == 3) continue@outer // 跳过外层循环的当前迭代
        println("i: $i, j: $j")
    }
}
// 输出:
// i: 1, j: 1
// i: 1, j: 2
// i: 2, j: 1
// i: 2, j: 2
// i: 3, j: 1
// i: 3, j: 2
// i: 4, j: 1
// i: 4, j: 2
// i: 5, j: 1
// i: 5, j: 2

7.5 循环的常见用法

7.5.1 使用 step 控制步长

通过 step 函数可以指定循环的步长:

for (i in 1..10 step 2) {
    println(i)
}
// 输出:
// 1
// 3
// 5
// 7
// 9

集合使用 filterIndexed 实现 step 功能

val items = listOf("apple", "banana", "kiwi", "orange", "grape")
val steppedItems = items.filterIndexed { index, _ -> index % 2 == 0 }

for (item in steppedItems) {
    println(item)
}
// 输出:apple, kiwi, grape

7.5.2 使用 downTo 实现倒序循环

通过 downTo 函数可以实现倒序循环:

for (i in 10 downTo 1) {
    println(i)
}
// 输出:
// 10
// 9
// 8
// 7
// 6
// 5
// 4
// 3
// 2
// 1

集合通过 reversed 可以实现集合的逆序

val items = listOf("apple", "banana", "kiwi", "orange", "grape")
val reversedItems = items.reversed()

for (item in reversedItems) {
    println(item)
}
// 输出:grape, orange, kiwi, banana, apple

八、异常捕获

8.1 非受检异常(Unchecked Exceptions)

与 Java 不同,Kotlin 没有受检异常(Checked Exceptions)。在 Kotlin 中,所有异常都是非受检异常,这意味着函数不需要声明它们可能抛出的异常,调用者也不需要强制捕获这些异常。

fun riskyOperation() {
    throw Exception("Something went wrong")
}

fun main() {
    try {
        riskyOperation()
    } catch (e: Exception) {
        println("Caught an exception: ${e.message}")
    }
}

在这个示例中,riskyOperation 函数抛出异常,但不需要在函数签名中声明 throws,调用者也不需要强制捕获异常。

8.2 try 表达式

Kotlin 的 try 块不仅可以用于捕获异常,还可以作为表达式返回值。这使得在异常处理中返回值变得更加简洁和自然。

fun parseInt(str: String): Int? {
    return try {
        str.toInt()
    } catch (e: NumberFormatException) {
        null
    }
}

fun main() {
    val number = parseInt("123") ?: 0
    println("Parsed number: $number") // 输出:Parsed number: 123

    val invalidNumber = parseInt("abc") ?: 0
    println("Parsed number: $invalidNumber") // 输出:Parsed number: 0
}

在这个示例中,try 表达式的结果可以直接作为返回值,这在处理需要返回默认值的场景中特别有用。

8.3 runCatching 函数

Kotlin 提供了 runCatching 函数,它可以简化异常捕获的流程,并返回一个封装了结果的对象(Result),从而避免显式的 try-catch 块。

fun riskyOperation(): Int {
    throw Exception("Something went wrong")
}

fun main() {
    val result = runCatching { riskyOperation() }
    println(result.isFailure) // 输出:true

    val value = result.getOrElse { 0 }
    println("Result: $value") // 输出:Result: 0
}

8.3.1 扩展函数 getOrElsegetOrNull

Result 类提供了扩展函数 getOrElsegetOrNull,用于在处理异常时提供默认值或返回 null

fun riskyOperation(): Int {
    throw Exception("Something went wrong")
}

fun main() {
    val result = runCatching { riskyOperation() }

    // 使用 getOrElse 提供默认值
    val value = result.getOrElse { 0 }
    println("Result: $value") // 输出:Result: 0

    // 使用 getOrNull 返回 null
    val nullableValue = result.getOrNull()
    println("Nullable Result: $nullableValue") // 输出:Nullable Result: null
}

8.3.2 扩展函数 onFailureonSuccess

Result 类还提供了扩展函数 onFailureonSuccess,用于分别处理成功和失败的情况。

fun riskyOperation(): Int {
    throw Exception("Something went wrong")
}

fun safeOperation(): Int {
    return 42
}

fun main() {
    val result = runCatching { riskyOperation() }

    result.onFailure { exception ->
        println("Caught an exception: ${exception.message}")
    }.onSuccess { value ->
        println("Operation successful, result: $value")
    }

    val safeResult = runCatching { safeOperation() }

    safeResult.onFailure { exception ->
        println("Caught an exception: ${exception.message}")
    }.onSuccess { value ->
        println("Operation successful, result: $value")
    }
}

九、具名参数、变长参数、默认参数

9.1 具名参数

具名参数(Named Parameters)允许在调用函数时明确指定参数名称,使代码更加清晰和易读。具名参数可以与位置参数混合使用,但具名参数必须在位置参数之后。

fun displayDetails(name: String, age: Int, city: String) {
    println("Name: $name, Age: $age, City: $city")
}

fun main() {
    // 使用位置参数调用函数
    displayDetails("John", 25, "New York")

    // 使用具名参数调用函数
    displayDetails(name = "Alice", age = 30, city = "Los Angeles")

    // 混合使用位置参数和具名参数
    displayDetails("Bob", city = "Chicago", age = 28)
}

在这个示例中,使用具名参数使得代码更加清晰易读,特别是在参数较多时。

9.2 变长参数

变长参数(Vararg Parameters)允许传递可变数量的参数。使用 vararg 关键字声明变长参数。变长参数在函数内部作为数组处理。

fun printAll(vararg items: String) {
    for (item in items) {
        println(item)
    }
}

fun main() {
    // 传递单个参数
    printAll("apple")

    // 传递多个参数
    printAll("banana", "kiwi", "orange")

    // 传递数组
    val fruits = arrayOf("grape", "pineapple")
    printAll(*fruits) // 使用展开操作符
}

在这个示例中,printAll 函数可以接受任意数量的字符串参数,并逐个打印。

9.3 默认参数

默认参数(Default Parameters)允许为函数参数提供默认值。在调用函数时,如果没有传递相应的参数,就使用默认值。默认参数使得函数调用更加灵活。

fun greet(name: String = "Guest") {
    println("Hello, $name")
}

fun main() {
    // 使用默认参数
    greet() // 输出 "Hello, Guest"

    // 覆盖默认参数
    greet("John") // 输出 "Hello, John"
}

在这个示例中,greet 函数的 name 参数有一个默认值 "Guest"。

9.4 综合示例

fun showInfo(name: String, age: Int = 18, vararg hobbies: String) {
    println("Name: $name, Age: $age")
    print("Hobbies: ")
    for (hobby in hobbies) {
        print("$hobby ")
    }
    println()
}

fun main() {
    // 使用默认参数和变长参数
    showInfo("Alice")

    // 覆盖默认参数
    showInfo("Bob", 25)

    // 使用变长参数
    showInfo("Charlie", hobbies = *arrayOf("Reading", "Swimming"))

    // 使用具名参数和变长参数
    showInfo(name = "Dave", hobbies = "Gaming", "Traveling")
}

在这个综合示例中:

  • showInfo 函数的 age 参数有默认值 18
  • hobbies 参数使用 vararg 关键字,可以接受任意数量的字符串参数。
  • 函数调用可以使用具名参数,使代码更具可读性。