# 引入 Kotlin 预计时间 1d > 个人认为,Kotlin 是最好的 JVM 语言。 ## 如果你会写 Java 或 Cpp/Python 请看 [为会写其他高级语言的朋友们设计的引入](./引入Kotlin_fast.md) ## 推荐 IDE 这边推荐 Jetbrains 的 [`IntelliJ IDEA`](https://www.jetbrains.com/idea/download/) 当然,各种类型的 VS Code,Neo Vim 也是很不错的,这边不提供具体教学。 ## Hello World! 先创建一个完全空的 Kotlin 项目/文件,在里面写: ```kotlin fun main() { println("Hello World!") } ``` 编译运行。要么点击上方的绿色开始键,要么是 main 函数左侧的绿色开始键。 结果: ```bash Hello World 进程已结束,退出代码为 0 ``` 这是一切的开始。 ## 变量 变量很简单,对于一个数字变量,可以直接这么写: ```kotlin val a = 1 var b = 2 ``` 这里,`val` 的意思是,a 是一个不可变的变量,`var` 的意思是,b 是一个可变的变量。 更准确地说,`val` 是只读变量:你不能让 `a` 再指向另一个对象,但如果它指向的对象本身可变,对象内部仍然可能改变。初学阶段可以先把它理解成“不能重新赋值的变量”。 ### 类型推断与显式类型 Kotlin 会根据右边的值自动推断类型,这叫类型推断: ```kotlin val intValue = 1 // Int val longValue = 1L // Long val doubleValue = 3.14 // Double val floatValue = 3.14f // Float val text = "Kotlin" // String val flag = true // Boolean val letter = 'K' // Char ``` 如果需要,也可以显式写出类型: ```kotlin val a: Int = 1 val b: String = "abc" val c: Double = 3.0 ``` Kotlin 的类型都使用大写开头,例如 `Int`、`Long`、`Double`、`Boolean`、`String`、`Char`。 数字还支持常见的非十进制写法: ```kotlin val hex = 0xFF val binary = 0b1010 val underscore = 1_000_000 ``` ### 字符串模板 Kotlin 的字符串可以直接嵌入变量或表达式: ```kotlin val name = "Kotlin" val version = 2 println("Hello, $name!") println("Version: ${version + 1}") ``` `$name` 用于简单变量,`${...}` 用于表达式。 ### 常量、延迟初始化与懒加载 如果值在编译期就能确定,并且是不可变的,可以使用 `const val`。它通常写在顶层或 `object` / `companion object` 中: ```kotlin const val MAX_COUNT = 100 const val DEFAULT_NAME = "guest" ``` 普通类或函数中不能定义 `const` 变量。类中的只读属性通常用 `val`: ```kotlin class Demo { val normal: Int = 1 lateinit var text: String val lazyValue: Int by lazy { println("第一次访问时才会计算") 42 } } ``` - `lateinit var`:允许稍后初始化,适合不能在构造函数中确定的值。直接使用未初始化的 `lateinit` 变量会抛异常。 - `by lazy`:第一次访问时才执行 lambda 并得到值,之后缓存结果。适合初始化成本较高的只读属性。 ### 可空类型 Kotlin 有一个专门的可空类型: ```kotlin var name: String = "Kotlin" // name = null // 编译错误 var nullableName: String? = null nullableName = "Kotlin" ``` `String?` 表示这个变量可能是 `String`,也可能是 `null`。要访问可空对象的属性,需要安全调用: ```kotlin val length = nullableName?.length ?: 0 ``` - `?.`:如果对象为 `null`,整个表达式返回 `null`。 - `?:`:Elvis 运算符,如果左边为 `null`,使用右边的值。 - `!!`:强制认为对象非空,如果真的是 `null` 会抛异常。除非你很确定,否则少用。 ## 函数 函数的作用,是把一段逻辑取个名字,之后可以重复调用。Kotlin 的函数比 Java 更自由:函数可以直接写在文件顶层,不必先放进某个类里。 ### 基本写法 Kotlin 函数使用 `fun` 关键字: ```kotlin fun add(a: Int, b: Int): Int { return a + b } ``` - `fun`:声明这是一个函数。 - `add`:函数名。 - `a: Int, b: Int`:参数列表。每个参数都必须写成 `参数名: 类型`。 - `: Int`:返回值类型。表示这个函数会返回一个 `Int`。 - `{ ... }`:函数体。 - `return`:返回最终结果,可以是表达式,也可以是一个变量。 调用函数时直接写函数名和参数: ```kotlin val result = add(1, 2) println(result) // 3 ``` ### main 函数 Kotlin 程序从 `main` 函数开始执行: ```kotlin fun main() { println("Hello World!") } ``` `main` 是顶层函数,不需要写在类里面。它没有参数,也没有返回值时,返回类型可以省略。 ### 没有返回值 如果函数没有返回值,可以省略返回类型,也可以显式写 `Unit`: ```kotlin fun printMessage(message: String) { println(message) } fun printMessage2(message: String): Unit { println(message) } ``` `Unit` 相当于 Java / C++ 里的 `void`。实际写代码时,大多数情况直接省略即可。 `Unit` 函数里也可以写 `return`,但后面不跟值: ```kotlin fun printPositive(number: Int) { if (number <= 0) { return } println(number) } ``` 这里 `return` 的含义是终止函数。 ### 单表达式函数 如果函数体只有一个表达式,可以省掉大括号,直接写等号: ```kotlin fun add(a: Int, b: Int): Int = a + b fun square(x: Int) = x * x ``` `square` 的返回类型可以由编译器推断出来。不过如果是给别人用的公开函数,建议还是把返回类型写清楚,代码会更易读。 ### 默认参数 Kotlin 支持给参数设置默认值: ```kotlin fun greet(name: String = "Kotlin") { println("Hello, $name!") } greet() // Hello, Kotlin! greet("World") // Hello, World! ``` 默认参数的好处是,不需要为了几种常见情况写很多重载函数。调用时如果不传这个参数,就使用默认值。 ### 写出参数名 调用函数时,可以写出参数名: ```kotlin fun createUser(name: String, age: Int, city: String = "Unknown") { println("$name, $age, $city") } createUser(age = 18, name = "Alice") ``` 命名参数的意义是,调用处更清楚每个值代表什么。参数很多时尤其有用。命名参数还可以和默认参数配合,只传需要修改的部分。 ### 变长参数 如果参数个数不确定,可以使用 `vararg`: ```kotlin fun sum(vararg numbers: Int): Int { var result = 0 for (number in numbers) { result += number } return result } println(sum(1, 2, 3)) println(sum(10, 20, 30, 40)) ``` `vararg` 参数在函数内部可以当作数组一样遍历。一个函数通常只有一个 `vararg`,而且一般放在参数列表最后。 如果已经有一个数组,可以用 `*` 展开后传进去: ```kotlin val numbers = intArrayOf(1, 2, 3) println(sum(*numbers)) ``` ### 局部函数 函数内部也可以定义函数,这叫局部函数: ```kotlin fun outer() { fun inner(x: Int): Int { return x * 2 } println(inner(3)) // 6 } ``` 局部函数只在外部函数内部可见,适合把一段只在这里使用的逻辑拆出来,避免重复。 ### 函数也可以作为值 Kotlin 里函数不只能调用,还可以像变量一样传递。函数类型写成: ```kotlin (参数类型, 参数类型) -> 返回类型 ``` 例如: ```kotlin val square: (Int) -> Int = { x -> x * x } ``` 这里的 `square` 是一个变量,它的类型是 `(Int) -> Int`,表示接收一个 `Int`,返回一个 `Int`。具体 lambda 写法后面会详细讲。 ## 控制流 控制流决定代码按什么顺序执行。Kotlin 的 `if`、`when` 都是表达式,也就是它们可以直接产生一个值。 ### if 表达式 普通写法: ```kotlin val a = 10 val b = 20 if (a > b) { println("a 更大") } else { println("b 更大") } ``` 因为 `if` 可以返回值,所以可以这样写: ```kotlin val max = if (a > b) a else b println(max) // 20 ``` 也可以写代码块,最后一行作为结果: ```kotlin val result = if (a > b) { println("a 更大") a } else { println("b 更大") b } ``` Kotlin 没有三目运算符 `? :`,因为 `if ... else ...` 已经能当表达式使用。 ### when 表达式 `when` 类似其他语言的 `switch`,但更灵活: ```kotlin fun describe(obj: Any): String { return when (obj) { 1 -> "One" "Hello" -> "Greeting" is Long -> "Long" !is String -> "Not a string" else -> "Unknown" } } ``` 特点: - 不需要 `break`。 - 每个分支用 `->`。 - 可以匹配具体值、区间、类型、条件。 - `when` 也可以作为表达式,直接把结果赋给变量。 ```kotlin val score = 85 val level = when (score) { in 90..100 -> "优秀" in 80 until 90 -> "良好" in 60 until 80 -> "及格" else -> "不及格" } ``` 如果 `when` 作为表达式使用,并且没有覆盖所有情况,就必须写 `else`。 ### for 循环 遍历区间: ```kotlin for (i in 1..5) { println(i) // 1 2 3 4 5 } ``` `..` 表示闭区间,包含左右两端。 左闭右开区间用 `until`: ```kotlin for (i in 1 until 5) { println(i) // 1 2 3 4 } ``` 降序和步长: ```kotlin for (i in 5 downTo 1) { println(i) // 5 4 3 2 1 } for (i in 1..10 step 2) { println(i) // 1 3 5 7 9 } ``` 遍历集合: ```kotlin val items = listOf("apple", "banana", "orange") for (item in items) { println(item) } ``` 同时拿到下标和值: ```kotlin for ((index, value) in items.withIndex()) { println("$index: $value") } ``` ### while 与 do-while `while` 先判断条件,再执行: ```kotlin var i = 0 while (i < 3) { println(i) i++ } ``` `do-while` 先执行一次,再判断条件: ```kotlin var j = 0 do { println(j) j++ } while (j < 3) ``` ### break 与 continue `break` 结束整个循环,`continue` 跳过本次循环,进入下一次: ```kotlin for (i in 1..10) { if (i == 3) { continue } if (i == 8) { break } println(i) } ``` 嵌套循环中,可以用标签指定跳出哪一层: ```kotlin outer@ for (i in 1..3) { for (j in 1..3) { if (i == 2 && j == 2) { break@outer } println("$i, $j") } } ``` 初学阶段知道有标签即可,平时不一定会经常用到。 ## 集合 集合是日常写程序最常用的工具之一。Kotlin 把集合分成“只读”和“可变”两类。 ### 创建集合 ```kotlin val list = listOf(1, 2, 3, 4, 5, 6) // 只读 List val mutableList = mutableListOf(1, 2, 3) // 可变 List val set = setOf(1, 2, 2, 3) // 只读 Set,自动去重 val mutableSet = mutableSetOf(1, 2, 3) val map = mapOf("a" to 1, "b" to 2) // 只读 Map val mutableMap = mutableMapOf() val array = arrayOf("1", "2", "3") // 数组 val ints = intArrayOf(1, 2, 3) // 基本类型数组 ``` `listOf`、`setOf`、`mapOf` 创建的是只读集合,不能添加、删除元素。需要修改时用 `mutableListOf`、`mutableSetOf`、`mutableMapOf`。 ### 访问与修改 ```kotlin val list = listOf(10, 20, 30) println(list[0]) // 10 println(list.first()) // 10 println(list.last()) // 30 println(list.size) // 3 ``` 可变列表可以修改: ```kotlin val list = mutableListOf(10, 20, 30) list.add(40) list.remove(20) list[0] = 100 println(list) // [100, 30, 40] ``` Map 的访问: ```kotlin val map = mutableMapOf("a" to 1, "b" to 2) println(map["a"]) // 1 map["c"] = 3 println(map) // {a=1, b=2, c=3} ``` ### 遍历集合 ```kotlin val list = listOf("a", "b", "c") for (item in list) { println(item) } list.forEach { item -> println(item) } ``` 遍历 Map: ```kotlin val map = mapOf("a" to 1, "b" to 2) for ((key, value) in map) { println("$key = $value") } ``` ### 常用集合操作 Kotlin 的集合操作很简洁,很多操作会返回一个新集合,而不是修改原集合: ```kotlin val numbers = listOf(1, 2, 3, 4, 5) val doubled = numbers.map { it * 2 } // [2, 4, 6, 8, 10] val evens = numbers.filter { it % 2 == 0 } // [2, 4] val total = numbers.sum() // 15 val hasBig = numbers.any { it > 4 } // true val allPositive = numbers.all { it > 0 } // true val firstBig = numbers.find { it > 3 } // 4 val sorted = numbers.sortedDescending() // [5, 4, 3, 2, 1] val text = numbers.joinToString() // "1, 2, 3, 4, 5" ``` 链式调用: ```kotlin val result = numbers .filter { it % 2 == 0 } .map { it * 10 } println(result) // [20, 40] ``` 这种写法在 Kotlin 中非常常见。`filter` 负责筛选,`map` 负责转换,最后得到新的结果。 ## Lambda 与高阶函数 Lambda 是一段可以当作值传递的代码。高阶函数则是“接收函数作为参数”或者“返回函数”的函数。 ### Lambda 基本写法 ```kotlin val add = { a: Int, b: Int -> a + b } println(add(1, 2)) // 3 ``` Lambda 用大括号包起来,箭头左边是参数,箭头右边是函数体。函数体最后一行会作为返回值,不需要写 `return`。 没有参数: ```kotlin val hello = { println("Hello") } hello() ``` 只有一个参数时,可以省略参数名,用 `it` 代替: ```kotlin val square: (Int) -> Int = { it * it } println(square(5)) // 25 ``` 也可以自己命名参数: ```kotlin val square2: (Int) -> Int = { x -> x * x } ``` Lambda 的类型写法: ```kotlin val func: (Int, String) -> String = { number, text -> text + number } ``` `(Int, String) -> String` 表示:接收一个 `Int` 和一个 `String`,返回一个 `String`。 可以用 `::funcName` 把一个函数用作 lambda 变量。 ### 高阶函数 如果函数的参数或返回值是函数,它就是高阶函数: ```kotlin fun applyTwice(x: Int, operation: (Int) -> Int): Int { return operation(operation(x)) } val result = applyTwice(3) { it * 2 } println(result) // 12 ``` 这里 `operation` 的类型是 `(Int) -> Int`,表示它接收一个 `Int`,返回一个 `Int`。 ### 尾随 lambda 如果函数的最后一个参数是 lambda,可以把 lambda 写在圆括号外面: ```kotlin fun repeatTwice(action: () -> Unit) { action() action() } repeatTwice { println("Hello") } ``` 如果 lambda 是唯一参数,圆括号也可以省略: ```kotlin repeatTwice { println("Kotlin") } ``` 如果函数还有其它参数,最后一个 lambda 仍然可以写在外面: ```kotlin val sum = listOf(1, 2, 3).fold(0) { acc, item -> acc + item } ``` `fold` 的第一个参数是初始值 `0`,后面的 lambda 负责累加。 ### 返回函数 函数也可以返回另一个函数: ```kotlin fun makeAdder(x: Int): (Int) -> Int { return { y -> x + y } } val add5 = makeAdder(5) println(add5(3)) // 8 ``` 返回的 lambda 可以捕获外部变量 `x`,这种能力让 Kotlin 在写回调和 DSL 时非常方便。 ### 可空函数类型 函数类型的变量也可以为空: ```kotlin var callback: (() -> Unit)? = null callback?.invoke() callback = { println("Done") } callback?.invoke() ``` `?.invoke()` 表示如果 `callback` 不为空,就调用它。 ## 空安全 Kotlin 把“这个值可能为 null”写进类型系统里。这样可以减少很多运行时的空指针异常。 ### 可空类型 普通类型不能赋值为 `null`: ```kotlin var name: String = "Kotlin" // name = null // 编译错误 ``` 如果确实可能为空,要在类型后面加 `?`: ```kotlin var nullableName: String? = null nullableName = "Kotlin" ``` `String` 和 `String?` 是两种不同的类型。`String?` 表示它可能是 `String`,也可能是 `null`。 ### 安全调用 访问可空对象时,使用 `?.`: ```kotlin val name: String? = null val length = name?.length ``` 如果 `name` 是 `null`,整个表达式返回 `null`,不会抛异常。所以 `length` 的类型是 `Int?`。 ### Elvis 运算符 `?:` 表示如果左边为 `null`,就使用右边的值: ```kotlin val name: String? = null val length = name?.length ?: 0 println(length) // 0 ``` Elvis 运算符经常和安全调用一起使用: ```kotlin val displayName = user?.name ?: "匿名用户" ``` ### 非空断言 `!!` 表示“我确定它不是 null”: ```kotlin val name: String? = "Kotlin" val length = name!!.length ``` 如果 `name` 真的是 `null`,这里会抛出异常。除非你非常确定,否则不要随便用 `!!`。 ### 安全转换 类型转换时,可以用 `as?`,转换失败会返回 `null`,而不是抛异常: ```kotlin val obj: Any = "hello" val text: String? = obj as? String ``` 对应的 `as` 如果转换失败,会抛异常。初学阶段知道 `as?` 更安全即可。 ## 类与接口 这部分只讲最基础的概念。类里面的成员函数、继承细节、接口默认实现等内容,本引入不展开。 ### 类 最简单的类: ```kotlin class MyClass ``` 带主构造函数的类: ```kotlin class Person(val name: String, var age: Int) ``` - `val name`:只读属性,创建后不能重新赋值。 - `var age`:可变属性,可以修改。 创建对象: ```kotlin val person = Person("Alice", 20) println(person.name) person.age = 21 println(person.age) ``` 如果需要在创建对象时执行一些初始化逻辑,可以用 `init` 块: ```kotlin class Person(val name: String) { init { println("创建了 $name") } } ``` 类里面也可以定义函数,但本引入先不展开成员函数。 ### 继承 Kotlin 的类默认不能被继承。要允许继承,需要加 `open`: ```kotlin open class Animal(val name: String) class Dog(name: String) : Animal(name) ``` 子类继承父类时,需要在类名后面写出父类构造调用。 ### 接口 接口用 `interface` 定义: ```kotlin interface Greeter { fun greet(): String } ``` 接口可以只声明函数,也可以提供默认实现。这里先知道接口长什么样即可。 ### 数据类 如果类的主要作用是保存数据,可以用 `data class`: ```kotlin data class User(val id: Int, val name: String) val user = User(1, "Alice") println(user) // User(id=1, name=Alice) val copied = user.copy(name = "Bob") println(copied) // User(id=1, name=Bob) ``` `data class` 会自动生成 `toString`、`equals`、`hashCode`、`copy` 等方法,很适合表示简单数据。是一个替代 `Lombook` 的好东西。 ## 接下来可以看 [目录](../README.md) [先回顾所学内容:引入 Kotlin fast](./引入Kotlin_fast.md) [下一篇:深入 Kotlin](./深入Kotlin.md) [进入 KMP](./进入KMP.md)