Files
From_Kotlin_to_Kotlin-Multi…/Learning/深入Kotlin.md
T
2026-09-21 19:44:59 +08:00

12 KiB
Raw Blame History

深入 Kotlin

预计时间 3d

确保你已经了解所有的基础语法,这很重要。

更全面的 class 用法

更加详细的 class 构造函数与对象初始化

类初始化

在 Kotlin 的类定义中可以写 init {} 代码块,它发生在构造函数之后,作用是使用构造函数定义的成员变量值再做一些构造函数没有的操作,比如利用构造函数的参数再做一些细致的变量处理。

构造函数

构造函数有多种写法:

  1. 直接写在类名之后
class MyClass(
    val a: Int,
    private var b: String,
    c: Int // 这里的变量 c 仅用于成员初始化,随后被抛弃,不作为成员变量
) {
    val d = c * a
    // something else
}
class MyClass private constructor( // 非私有时,constructor 可省
    // ...
) {
    // something else
}

这是最方便的方法,我如果不用私有构造函数基本就是这样写,再使用默认值配合。

  1. 写在类定义之内
class MyClass {
    val d: Int
    constructor(
        val a: Int,
        private var b: String,
        c: Int
    ) {
        d = c * a
    }
    // something else
}
  1. 构造函数调用其他构造函数
class MyClass(
    var name: String?,
    val amount: Int
) {
    constructor(
        val amount: Int
    ) : this("Unknown", amount)
    // something else
}

伴生对象

定义方法:

class MyClass() {
    companion object {}
}

它的使用和 object 完全一致,用于替代 Java 中的 static 修饰符,是一个非常方便的东西。可以省去工厂类,直接在伴生对象里定义工厂函数。

类中类

嵌套类

你可以在类中定义任何类,它们的调用需要带上外层类,或者在 import 中多写一层外层类名:

class OuterClass() {
    class InnerClass() {}
}

// 如果只 import 外层类
import pack.age.OuterClass

val a = OuterClass.InnerClass()

// 如果想 import 内层类
import pack.age.OuterClass.InnerClass

val a = InnerClass()

内部类

内部类的定义需要在 class 前添加一个 inner 关键字。

内部类的特点是:它可以使用外部类的任意属性,包括私有的。有时会很方便。

如果出现同名属性可以使用标签来指明是哪一个类的属性:

class MainActivity {
    fun showToast() {
        println("display")
    }

    inner class MyButton {
        fun showToast() {}
            
        fun setOnClickListener() {
            this@MainActivity.showToast()
        } // @ 后面写类名,用法与 this 一致
    }
}

类继承细节

在 Kotlin 中,一个类最多只能直接继承一个类,但可以继承任意多个接口

子类

如果类被继承,其中的类中类不会有任何影响,子类不含有类中类的任何信息。

Kotlin 中所有的类,成员变量,函数都是不可被继承,不可被重写的。如果需要继承 / 重写,只要在定义的开头加上 open。而接口 interface 的任意属性包括它自己默认就是 open 的。

在一个类的子类中可以使用它的父类(超类)的任意属性,用 super 代指,用法与 this 一致。

class FilledRectangle : Rectangle() {
    override fun draw() {
        super.draw()
        println("Filling the rectangle")
    }

    val fillColor: String get() = super.borderColor
}

这里,父类名后括号的含义是指,子类使用与父类一致的构造函数,且没有参数。

如果一个类继承的类 / 接口中含有重名的属性,那么子类必须重写。你可以在子类中以这种方式使用来自不同定义的属性:

open class Rectangle {
    open fun draw() {}
}

interface Polygon { // interface 默认 open
    fun draw() {}
}

class Square() : Rectangle(), Polygon {
    override fun draw() {
        super<Rectangle>.draw() // 调用 Rectangle.draw()
        super<Polygon>.draw()   // 调用 Polygon.draw()
    }
}

接口

接口

抽象类

抽象类介于接口和类之间,

泛型类

特殊类

object

data class

enum class

特殊类函数

重写 toString

重载运算符

component 函数

invoke

类似于 Python 的 __call__()

特殊函数

嵌套 lambda 与标签

泛型函数

infix 函数

扩展函数

概念与 lambda 变量类型

常用的标准库扩展函数

apply

它的返回值是对象本身,lambda 相当于类函数。

val user = User().apply {
    name = "Alice"
    age = 18
} // 修改对象属性后返回 user 对象本身
also

它的返回值是对象本身,lambda 相当于参数是同类的函数。

val numbers = mutableListOf(1, 2, 3).also {
    println("The list elements were: $it")
} // numbers 仍是是可变 List
let

可以利用 let 让一段代码只在对象非空时执行:

val name: String? = "Kotlin"

name?.let {
    println("长度是 ${it.length}")
}

如果 name 是 null,let 里面的代码不会执行。

run

它的返回值是最后的任意结果,lambda 相当于类函数。

val result = StringBuilder().run {
    append("Hello, ")
    append("World!")
    toString() // 返回结果
}
use

它的返回值是对象本身,lambda 相当于参数是同类的函数。 但是仅用于继承了 AutoCloseable 的类,相当于 Java 的 try-with-resources。

val fileContent = File(filePath)
    .bufferedReader()
    .use { reader ->
        reader.readText()
    } // use 会自动调用 BufferedReader 的 close() 方法

suspend 函数

如果你的函数内部需要做一些不得不等待的事,或者需要很久的事的话,那么你可以选择给它添加 suspend 关键字。这样它可以在任意时间暂停。

如果你想要调用一个 suspend 函数,那么你需要注意,它不能直接写在任何非 suspend 函数中。

如果你非要在一个普通函数中使用 suspend 函数,那么你可以使用 runBlocking,它只有一个参数,一个 lambda,可以运行 suspend 函数并阻塞线程直到 lambda 运行完毕。

更细致的讲解在 成为 Kotlin 超人 的协程中。

文件 IO

Kotlin 的文件 IO 在 java.io.File 的基础上提供了许多充满 Kotlin 风味的方法。

val file = File("example.txt") // 拿到文件(来自 Java)
val content: String = file.readText() // 默认 UTF-8
val lines: List<String> = file.readLines()
file.forEachLine { line ->
    println(line)
}

file.writeText("Hello, Kotlin!")    // 完全覆盖
file.appendText("\nNew line data.") // 末尾添加

File("my_folder").walk().forEach { file ->
    println(file.absolutePath) // 遍历文件夹
}

val source = File("source.txt")
val dest = File("dest.txt")
source.copyTo(dest, overwrite = true) // 复制文件

用 Gradle 加载依赖(外部库)

build.gradle.kts

这是最常被修改的 gradle 配置文件,里面不只有依赖的设置,还有一些 JVM / Kotlin 的设置。这里不做详细介绍。对于一个普通的 Kotlin 项目,控制依赖的部分写在这里:

dependencies {
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    // 加载最新版的 Kotlin 反射,但不是所有的库都能自动添加版本号
    implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0")
    // 加载0.6.0版本的 Kotlin 时间
}

依赖的名称一般从 Maven 仓库中查找,不少在 GitHub 有仓库的 Kotlin 库会写明自己在 Maven 上的名称。

有时会用到 testImplementation,它只在测试源文件中起作用。

settings.gradle.kts

这个文件用的很少,我的建议是,一般别动。

时间

在 Kotlin 中可以直接使用 Java 的时间处理方法,如果需要实现复杂一点的逻辑或是需要用到 Kotlin 序列化(在成为 Kotlin 超人中有讲解),更推荐使用 kotlinx-datetime。

官方文档:kotlinx-datetime

加载依赖

可以在官方文档中找到目前的版本,随后在 build.gradle.kts 中写:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.8.0")
}

拿到当前时间或任意时间

val nowInstant = Clock.System.now()
// 获取当前时刻 (UTC)

val customDate = LocalDate(2026, 10, 1)
// 也可以写成 LocalDate(2026, Month.OCTOBER, 1)

val customTime = LocalTime(14, 30, 0) // 时, 分, 秒, 纳秒

val customDateTime = LocalDateTime(2026, 10, 1, 14, 30, 0)

println(customDate)     // 2026-10-01
println(customDateTime) // 2026-10-01T14:30:00

println(nowInstant) 
// 2026-09-19T08:39:08.123Z
println(nowInstant.toEpochMilliseconds())
// 毫秒时间戳

分析日期

val localDateTime = currentMoment.toLocalDateTime(TimeZone.currentSystemDefault())

println(localDateTime)              // 2026-09-19T17:39:08.123
println(localDateTime.year)         // 年: 2026
println(localDateTime.monthNumber)  // 月: 9
println(localDateTime.dayOfMonth)   // 日: 19
println(localDateTime.hour)         // 时: 17
println(localDateTime.minute)       // 分: 39
println(localDateTime.today)        // 2026-09-19
println(localDateTime.dayOfWeek)    // SATURDAY 这是一个枚举类 DayOfWeek

Kotlin 反射

拿到类变量

Kotlin 类变量的类型是 KClass<T>,可以通过一个类的类名直接得到,例如

val c = MyClass::class
val javaClass = MyClass::class.java

也可以通过实际变量拿到精确类名:

val a: Number = 1               // Int 是 Number 子类
println(a::class.qualifiedName) // kotlin.Int

利用类变量获得 / 修改对象

var x = 1

fun main() {
    println(::x.get()) // 1
    println(::x.name)  // x
    ::x.set(2)         // 将它设为 2
}

这里 ::x 引用到的是本类中的 x 变量,也就是说如果 x 定义在函数 main 中,就不能这么引用了。注意,只有可变变量才能用 set(),除非使用 Java 反射强行更改。Kotlin 反射不推荐如此操作。

复杂类反射

class A(val p: Int)

val a = A(1)
val prop = A::p
println(prop.get(a))     // 1

// 也可以拿到它的 Java 定义。
println(A::p.javaGetter) // public final int A.getP()
println(A::p.javaField)  // private final int A.p

通过这种方法,可以拿到一个类对象的一个变量值。

val lambda = ::A // 拿到类 A 的无参构造函数

这个 lambda 相当于 A(),如果 A 的构造函数必须有参数(有默认值的话算作可以没有参数)。那么 lambda 是不能调用的。

class Person(val name: String, age: Int)
val kClass = Person::class

val constructor = kClass.primaryConstructor // 这是主构造函数,是第一个被定义的,而且可以填入参数

// 这样,你可以拿到它的全部构造函数
for (constructor in kClass.constructors) {
    val parameters = constructor.parameters // 获取构造函数的参数列表
    if (parameters.size == 2) {
        val instance = constructor.call("李四", 20)
        break
    }
}

这里的 parameters 是一个列表,它里面的元素是用来描述构造函数的每一个参数的:

println("参数位置: ${param.index}")
println("参数名称: ${param.name}")
println("参数类型: ${param.type}")
println("是否为可选参数(有默认值): ${param.isOptional}")
println("是否为可变长参数: ${param.isVararg}")

定义一个特殊的变量/函数名

只需要使用反引号 ` 将一个任意符串包裹起来,它几乎可以被定义为所有东西的名称。

fun `fun`() = "fun" // 例如定义一个函数
val `this` = 1      // 或是一个变量
val `or write a sentence` = true

IDEA 的常用快捷键

ctrl + alt + L:格式化代码

ctrl + alt + O:整理文件中的 import

alt + enter:光标在警告 / 报错上时显示快速处理方案或光标处的格式转换

shift + F6:重命名文件名 / 属性名

接下来可以看

目录

下一篇:Kotlin Springboot

更进一步:成为 Kotlin 超人

进入 KMP

KMP:复杂逻辑与多平台不同逻辑