From 26ee8636ea8fdd1e3323f413974a293d16e186a9 Mon Sep 17 00:00:00 2001 From: SOR <3272194354@qq.com> Date: Sat, 12 Sep 2026 10:20:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=95=E5=85=A5Kotlin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Learning/引入Kotin_fast.md | 259 ++++++++++- Learning/引入Kotlin.md | 894 ++++++++++++++++++++++++++++++++++++- 2 files changed, 1135 insertions(+), 18 deletions(-) diff --git a/Learning/引入Kotin_fast.md b/Learning/引入Kotin_fast.md index d5e66c2..0d2036b 100644 --- a/Learning/引入Kotin_fast.md +++ b/Learning/引入Kotin_fast.md @@ -1,4 +1,8 @@ -# 引入 Kotlin +已按你的要求,把补全版里新增、但 FAST 版未覆盖的内容合并进 FAST 版,并顺手修正了几处小笔误。完整如下: + +--- + +# 引入 Kotlin (FAST) 预计时间 1h @@ -28,6 +32,7 @@ fun main() { ```kotlin val a: Int // 可以注意到,所有类都以大写开头 val a = 0xFF // 支持先声明,再赋值,还有经典非十进制写法 +val million = 1_000_000 val b = "abc" // 直接赋值省略显式变量类型 @@ -36,9 +41,61 @@ var c = 3.0f // 可变变量 const val CONST_STR = "常值变量支持String,推荐大写下划线" ``` +普通类或函数中不能定义一个 `const` 变量。`const val` 通常写在顶层或 `object` / `companion object` 中。 + +```kotlin +private val a: Int +lateinit var b: String + +val c: Int by lazy { + // do something difficult +} +``` + +类定义可以出现没有初始值的情况,但是在构造函数中需要指明初始值,`lateinit` 变量允许更晚进行初始化,但不初始化直接使用会抛出异常。`by lazy` 通过一个 lambda 进行变量初始化,可以推迟一些重资源使用,在普通变量中也可以使用。 + +字符串模板: + +```kotlin +val name = "Kotlin" +val version = 2 +println("Hello, $name!") +println("Version: ${version + 1}") +``` + +#### 可空类型 + +Kotlin 把“可能为空”写进类型里: + +```kotlin +var name: String = "Kotlin" +// name = null // 编译错误 + +var nullableName: String? = null +nullableName = "Kotlin" +``` + +安全调用和 Elvis 运算符: + +```kotlin +val length = nullableName?.length ?: 0 +``` + +- `?.`:对象为 `null` 时整个表达式返回 `null`。 +- `?:`:左边为 `null` 时使用右边的值。 +- `!!`:强制非空,如果为 `null` 会抛异常。如果使用 Java 函数,会发现所有的函数返回值都是可空类型,因而有时会大量使用。 + +也可以用 `let`: + +```kotlin +nullableName?.let { + println("长度是 ${it.length}") +} +``` + ### 定义普通函数 -在 Kotlin 里面,cpp 的 void $\Leftrightarrow$ Kotlin 的 Unit +在 Kotlin 里面,Java / Cpp 的 void $\Leftrightarrow$ Kotlin 的 Unit ```kotlin fun oneFuntion(): Unit { @@ -61,21 +118,183 @@ fun thatFuntion(vararg folders: String): String { 在函数定义明确的时候是不需要指明返回值的,除非是接口或用作 lambda 变量。 +默认参数与命名参数: + +```kotlin +fun greet(name: String = "Kotlin") { + println("Hello, $name!") +} + +greet() +greet("World") + +fun createUser(name: String, age: Int, city: String = "Unknown") { + // ... +} + +createUser(age = 18, name = "Alice") +``` + +表达式函数体: + +```kotlin +fun add(a: Int, b: Int): Int = a + b +fun square(x: Int) = x * x +``` + +### 控制流 + +`if` 是表达式,可以返回值,代替三目运算符: + +```kotlin +val max = if (a > b) a else b +``` + +`when` 类似 switch,但更强大,也可以作为表达式: + +```kotlin +fun describe(obj: Any): String = when (obj) { + 1 -> "One" + "Hello" -> "Greeting" + is Long -> "Long" + !is String -> "Not a string" + else -> "Unknown" +} +``` + +循环: + +```kotlin +for (i in 1..5) { + println(i) +} + +for (i in 5 downTo 1 step 2) { + println(i) +} + +for (i in 1 until 5) { + println(i) // 1, 2, 3, 4 +} + +var i = 0 +while (i < 3) { + println(i) + i++ +} +``` ### 定义类与接口 ```kotlin -class MyClass() {} -interface MyInterface() {} +class MyClass() +interface MyInterface +``` + +主构造函数可以直接声明属性: + +```kotlin +class Person(val name: String, var age: Int) + +val person = Person("Alice", 20) +println(person.name) +person.age = 21 +``` + +`init` 块用于创建对象时执行逻辑: + +```kotlin +class Person(val name: String, var age: Int) { + init { + println("创建了 $name") + } + + fun introduce(): String { + return "我是 $name,今年 $age 岁" + } +} +``` + +Kotlin 类默认是 `final`,要允许继承需要 `open`: + +```kotlin +open class Animal(val name: String) { + open fun sound() { + println("...") + } +} + +class Dog(name: String) : Animal(name) { + override fun sound() { + println("Woof") + } +} +``` + +接口可以有默认实现: + +```kotlin +interface Named { + val name: String + + fun showName() { + println(name) + } +} +``` + +`data class` 适合保存数据,会自动生成 `toString`、`equals`、`hashCode`、`copy`: + +```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") ``` ## 高级一点 -### 列表 +### 列表与集合 ```kotlin -val list = ListOf(1, 2, 3, 4, 5, 6) // List 不可变 -val array = arrayOf("1", "2", "3") // array 可变长 +val list = listOf(1, 2, 3, 4, 5, 6) // List 只读 +val mutableList = mutableListOf(1, 2, 3) // 可变 List +mutableList.add(4) + +val array = arrayOf("1", "2", "3") // 数组长度固定,元素可修改 +val ints = intArrayOf(1, 2, 3) // 基本类型数组 +``` + +Map 和 Set: + +```kotlin +val map = mapOf( + "a" to 1, + "b" to 2 +) +println(map["a"]) + +val mutableMap = mutableMapOf() +mutableMap["c"] = 3 + +val set = setOf(1, 2, 2, 3) +println(set) // [1, 2, 3] +``` + +常用集合操作: + +```kotlin +val numbers = listOf(1, 2, 3, 4, 5) + +val doubled = numbers.map { it * 2 } +val evens = numbers.filter { it % 2 == 0 } +val total = numbers.sum() + +println(doubled) // [2, 4, 6, 8, 10] +println(evens) // [2, 4] +println(total) // 15 ``` ### lambda变量与复杂函数 @@ -87,13 +306,20 @@ val func = { firstVal: Int, secVal: String -> } ``` -func 变量的类型是 `(Int, String) -> String`,简单易懂 +func 变量的类型是 `(Int, String) -> String`,简单易懂。也可以显式写类型: + +```kotlin +val add: (Int, Int) -> Int = { a, b -> a + b } +val square: (Int) -> Int = { it * it } +``` + +如果 lambda 只有一个参数,可以用 `it` 指代。 当函数的最后一行是 lambda 变量时,可以在调用时简化 ```kotlin -fun doOrNull( - obj: SomeClass?, // 问号表示类型可空 +fun doOrNull( // 这是一种常见的分行写法 + obj: SomeClass?, // 问号表示类型可空 func: (SomeClass) -> Int ): Int? { return if (obj == null) obj // if 可以被直接返回,这代替了三目运算符 @@ -106,15 +332,14 @@ val a = doOrNull(b) { someClass -> someClass.c + 1 } // 在圆括号后直接写 lambda 定义,很方便 ``` - -## 某些独特的 Kotlin 小特性 - -### 可空变量与对象 +只有一个参数时也可以: ```kotlin +val a = doOrNull(b) { it.c + 1 } ``` -### if 与 when +## 接下来可以看 -```kotlin -``` +[目录](../README.md) + +[下一篇:深入 Kotlin](../深入Kotlin.md) \ No newline at end of file diff --git a/Learning/引入Kotlin.md b/Learning/引入Kotlin.md index 1256baf..3eee402 100644 --- a/Learning/引入Kotlin.md +++ b/Learning/引入Kotlin.md @@ -24,4 +24,896 @@ fun main() { } ``` -编译运行。 \ No newline at end of file +编译运行。要么点击上方的绿色开始键,要么是 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) +} + +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`。 + +### 高阶函数 + +如果函数的参数或返回值是函数,它就是高阶函数: + +```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`,这里会抛出异常。除非你非常确定,否则不要随便用 `!!`。 + +### let 处理可空值 + +`let` 可以让一段代码只在对象非空时执行: + +```kotlin +val name: String? = "Kotlin" + +name?.let { + println("长度是 ${it.length}") +} +``` + +如果 `name` 是 `null`,`let` 里面的代码不会执行。 + +### 安全转换 + +类型转换时,可以用 `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` 等方法,很适合表示简单数据。 + +## 接下来可以看 + +[目录](../README.md) + +[下一篇:深入 Kotlin](../深入Kotlin.md) \ No newline at end of file