From 4b0f0d80b6d4d4aefa34c902b20ccd7dc09d0236 Mon Sep 17 00:00:00 2001 From: SOR2171 Date: Sat, 10 Oct 2026 09:15:42 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=20Kotlin=20=E8=BF=9B?= =?UTF-8?q?=E9=98=B6=E7=BB=86=E8=8A=82=E4=B8=8E=E6=88=90=E4=B8=BA=E8=B6=85?= =?UTF-8?q?=E4=BA=BA=E7=AF=87=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Learning/Kotlin-Ktor.md | 2 +- Learning/全栈KMP.md | 2 +- Learning/复杂逻辑与多平台不同逻辑.md | 2 +- Learning/引入Kotlin.md | 4 +- Learning/引入Kotlin_fast.md | 2 +- Learning/成为Kotlin超人.md | 227 ++++++++++++++++++++++++++- Learning/深入Kotlin.md | 46 +++++- Learning/第一个KMP应用.md | 4 +- Learning/进入KMP.md | 4 +- 9 files changed, 280 insertions(+), 13 deletions(-) diff --git a/Learning/Kotlin-Ktor.md b/Learning/Kotlin-Ktor.md index f620cee..3655b0e 100644 --- a/Learning/Kotlin-Ktor.md +++ b/Learning/Kotlin-Ktor.md @@ -12,7 +12,7 @@ Ktor 是一个比较新的、作为 KMP server 最优选的服务器后端框架 Ktor 本身的功能很少,所有的复杂功能都由依赖提供。 -在 Ktor 里加插件一般分两步: +在 Ktor 里加依赖一般分两步: ### 在 `build.gradle.kts` 中添加依赖 diff --git a/Learning/全栈KMP.md b/Learning/全栈KMP.md index 50085ec..50dfe82 100644 --- a/Learning/全栈KMP.md +++ b/Learning/全栈KMP.md @@ -16,7 +16,7 @@ - commonMain - core - commonMain -- Server +- server 除了 iOS 部分必须由 Xcode 做额外设置后编译以外,其他的应用均可在 JDK 下完成编译(Android 需要相应 SDK,其他需要编译为字节码的需要其他编译器)。 diff --git a/Learning/复杂逻辑与多平台不同逻辑.md b/Learning/复杂逻辑与多平台不同逻辑.md index 52aa0a5..61fe54d 100644 --- a/Learning/复杂逻辑与多平台不同逻辑.md +++ b/Learning/复杂逻辑与多平台不同逻辑.md @@ -57,7 +57,7 @@ actual val isDebugMode: Boolean = BuildConfig.DEBUG actual val isDebugMode: Boolean = Platform.isDebugBinary ``` -### 在实验中 / 不稳定的写法(截至20261009) +### 在实验中 / 不稳定的写法(截至2026.10.09) 各种类定义也是可以用 expect / actual 的。这里用普通类举例子: diff --git a/Learning/引入Kotlin.md b/Learning/引入Kotlin.md index 81b703d..7e8e503 100644 --- a/Learning/引入Kotlin.md +++ b/Learning/引入Kotlin.md @@ -123,7 +123,7 @@ class Demo { ### 可空类型 -Kotlin 把“可能为空”写进类型里: +Kotlin 有一个专门的可空类型: ```kotlin var name: String = "Kotlin" @@ -133,7 +133,7 @@ var nullableName: String? = null nullableName = "Kotlin" ``` -`String?` 表示这个变量可能是 `String`,也可能是 `null`。要访问可空对象,需要安全调用: +`String?` 表示这个变量可能是 `String`,也可能是 `null`。要访问可空对象的属性,需要安全调用: ```kotlin val length = nullableName?.length ?: 0 diff --git a/Learning/引入Kotlin_fast.md b/Learning/引入Kotlin_fast.md index 2de94a9..9aed818 100644 --- a/Learning/引入Kotlin_fast.md +++ b/Learning/引入Kotlin_fast.md @@ -61,7 +61,7 @@ println("Version: ${version + 1}") #### 可空类型 -Kotlin 把“可能为空”写进类型里: +Kotlin 有一套方法处理空指针问题: ```kotlin var name: String = "Kotlin" diff --git a/Learning/成为Kotlin超人.md b/Learning/成为Kotlin超人.md index 7f848ec..ffbe6e6 100644 --- a/Learning/成为Kotlin超人.md +++ b/Learning/成为Kotlin超人.md @@ -20,11 +20,234 @@ plugins { } ``` +还有一个依赖 + +```kotlin +implementation("org.jetbrains.kotlin:kotlin-reflect") +``` + ## Kotlin 协程与多线程细节 -## 不依赖 Springboot web 的网络 api +核心依赖: -## 加载动态链接库(JNA) +```kotlin +dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") +} +``` + +要点概括: +- **不要用 `GlobalScope`**:建议自定义 `CoroutineScope(SupervisorJob() + Dispatchers.Default)` 或跟随生命周期,防止协程泄露。 +- **线程调度(Dispatchers)**: + - `Dispatchers.Main`:UI 线程(Android/Compose)。 + - `Dispatchers.IO`:网络、磁盘文件等阻塞 I/O 操作。 + - `Dispatchers.Default`:计算密集型、复杂数据处理。 + - 线程切换使用 `withContext(Dispatchers.IO) { ... }` 即可。 +- **并发任务**:多个耗时任务同时跑用 `async { ... }`,最后再调用 `.await()` 获取结果。 +- **响应式数据流**:状态监听推荐用 `StateFlow` 或 `SharedFlow`,替代传统的回调接口。 + +## 不依赖网络框架的网络 api + +如果你不想引入 Spring Boot 或 Ktor 这样庞大的框架,只是想发个请求或者写个简单的接口: + +### 发送 HTTP 请求(客户端) + +直接用 Java 11+ 原生自带的 `HttpClient` 即可,零依赖且原生支持异步: + +```kotlin +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse + +val client = HttpClient.newHttpClient() +val request = HttpRequest.newBuilder() + .uri(URI.create("https://api.github.com")) + .GET() + .build() + +val response = client.send(request, HttpResponse.BodyHandlers.ofString()) +println(response.body()) +``` + +如果是 KMP 跨平台客户端,首选官方轻量客户端 `io.ktor:ktor-client-core`。 + +### 极简 HTTP 服务端 + +JDK 自带了轻量级的 `HttpServer`,适合写小工具或内网 Mock: + +```kotlin +import com.sun.net.httpserver.HttpServer +import java.net.InetSocketAddress + +val server = HttpServer.create(InetSocketAddress(8080), 0) +server.createContext("/ping") { exchange -> + val response = "pong" + exchange.sendResponseHeaders(200, response.length.toLong()) + exchange.responseBody.use { it.write(response.toByteArray()) } +} +server.start() +``` + +## 加载动态链接库(JNA) + +如果需要调用 C/C++ 编译出的动态链接库(`.dll` / `.so` / `.dylib`),比起折腾麻烦的 JNI 头文件和胶水代码,**JNA (Java Native Access)** 是 JVM 和 Android 上最轻松的选择。 + +依赖: + +```kotlin +dependencies { + implementation("net.java.dev.jna:jna:5.14.0") + // 如果是 Android 端则使用: + // implementation("net.java.dev.jna:jna:5.14.0@aar") +} +``` + +使用时只需要定义一个接口继承 `com.sun.jna.Library`: + +```kotlin +import com.sun.jna.Library +import com.sun.jna.Native + +interface MyNativeLib : Library { + fun add(a: Int, b: Int): Int + + companion object { + // "mylib" 对应 libmylib.so / mylib.dll + val INSTANCE: MyNativeLib = Native.load("mylib", MyNativeLib::class.java) + } +} + +// 直接调用 C 函数 +val result = MyNativeLib.INSTANCE.add(1, 2) +``` + +注意,库文件需要放在特定的文件夹,这里参考[官方文档](https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md) + +## 与 C 混编(cinterop) + +在 Kotlin/Native(编译为原生二进制的目标平台,如 iOS、Linux、macOS、Windows)中,不需要 JVM 和 JNA,Kotlin 官方直接提供了 **cinterop** 工具。 + +1. 在项目源码旁新建 `.def` 文件(例如 `src/nativeInterop/cinterop/myclib.def`): +```properties +headers = myclib.h +staticLibraries = libmyclib.a +libraryPaths = /path/to/lib +``` + +2. 在 `build.gradle.kts` 中声明 cinterop: +```kotlin +kotlin { + linuxX64 { + binaries.executable() + compilations.getByName("main").cinterops { + val myclib by creating + } + } +} +``` + +3. Gradle 构建后会自动生成 Kotlin 绑定包,在代码中直接 import 即可像调用普通 Kotlin 函数一样调用 C 函数,并通过 `memScoped` 安全管理 C 指针与内存。 + +## 委托(Delegation 与 by 关键字) + +Kotlin 的 `by` 能极大精简代码,主要用在两个地方: + +### 类委托 + +不必手动重写接口的所有方法再转发给内部对象,直接 `by` 代理: + +```kotlin +interface Repository { + fun fetch(): String + fun save(data: String) +} + +// 缓存包装类,未重写的方法自动转发给 inner +class CachedRepository(private val inner: Repository) : Repository by inner { + override fun fetch(): String { + return "from cache" // 只需重写你要拦截的方法 + } +} +``` + +### 属性委托 + +除了常见的 `by lazy`,只要类提供了 `operator fun getValue / setValue`,就能代理属性的读写(Compose / KMP 里的 `var text by remember { mutableStateOf("") }` 就是如此): + +```kotlin +// 监听属性变化的标准库委托 +var name: String by Delegates.observable("初始值") { prop, old, new -> + println("$old -> $new") +} +``` + +## 内联值类 + +如果想给基础类型(如 `Long`、`Int`)包一层业务类型(例如 `UserId`、`OrderId`)以保证强类型安全,又不想每次创建对象都在堆内存里产生 GC 开销: + +```kotlin +@JvmInline +value class UserId(val value: Long) + +fun getUser(id: UserId) { ... } +``` + +编译后会在字节码里直接拆包还原成底层的原始类型,完全是零运行时开销。Compose 里的 `Color`、`Dp` 底层全部是用它实现的。 + +## 优雅的异常处理:runCatching + +在现代 Kotlin 代码中,尽量少写层层嵌套的 `try-catch`。标准库的 `runCatching` 更加函数式和连贯: + +```kotlin +val result = runCatching { + // 可能会抛异常的代码 + "123".toInt() +}.getOrDefault(0) // 报错直接返回默认值,也可使用 getOrElse { ... } + +// 配合链式调用 +runCatching { fetchNetworkData() } + .onSuccess { data -> println("成功: $data") } + .onFailure { error -> println("失败: ${error.message}") } +``` + +## 大集合流式处理:Sequence + +普通集合操作(`list.filter { }.map { }`)每一步都会立即创建一个全新的临时 `List`,在处理大数据或长链条时浪费大量内存。 + +加上 `asSequence()` 开启惰性求值(类似 Java 的 Stream),一个元素走完所有变换才处理下一个: + +```kotlin +val result = (1..100000).asSequence() + .filter { it % 2 == 0 } + .map { it * 2 } + .take(10) // 只计算出前 10 个就提前停止,后面的根本不会去算 + .toList() +``` + +## Java 互操作注解 + +当 Kotlin 代码需要被 Java 代码调用,或者被依赖 Java 反射的旧框架识别时,别忘了这几个注解: + +- **`@JvmOverloads`**:给带有默认参数的函数自动生成 Java 重载方法,否则 Java 只能看到全参方法。 +- **`@JvmStatic`**:把 `companion object` 或 `object` 里的函数真正编译成 Java 视角的 `static` 方法。 +- **`@JvmField`**:直接暴露为底层公有字段,不生成 Kotlin 默认的 getter/setter。 + +## 注解的使用处目标(Use-site Targets) + +Kotlin 声明一个属性 `val name: String`,在字节码里其实包含了:底层字段(field)、getter 方法、构造函数参数。 + +当配合 Spring、Room、JSON 序列化等注解使用时,如果不显式指定,注解可能会打在 getter 上导致框架失效。此时必须加上前缀: + +```kotlin +data class User( + @field:NotBlank // 作用于字段 + val username: String, + + @get:JsonIgnore // 作用于 getter 方法上 + val secretToken: String +) +``` ## 将 jar 包上传至 Maven 仓库 diff --git a/Learning/深入Kotlin.md b/Learning/深入Kotlin.md index 5b51bd0..19a1e87 100644 --- a/Learning/深入Kotlin.md +++ b/Learning/深入Kotlin.md @@ -886,7 +886,7 @@ println("是否为可变长参数: ${param.isVararg}") ## 定义一个特殊的变量/函数名 -只需要使用反引号 ``` ` ``` 将一个任意符串包裹起来,它几乎可以被定义为所有东西的名称。 +只需要使用反引号 ``` ` ``` 将一个任意字符串包裹起来,它几乎可以被定义为任何东西的名称。 ```kotlin fun `fun`() = "fun" // 例如定义一个函数 @@ -894,6 +894,50 @@ val `this` = 1 // 或是一个变量 val `or write a sentence` = true ``` +## 类型别名 typealias + +当某些类型名字特别长,或者某些 lambda 函数类型签名较长时,写起来会很啰嗦。使用 `typealias` 可以给现有类型起一个简单易读的别名。 + +```kotlin +// 给复杂的函数类型起别名 +typealias ClickListener = (viewId: Int, message: String) -> Unit + +fun setOnClick(listener: ClickListener) { + listener(1, "clicked") +} + +// 给长泛型起别名 +typealias UserMap = Map>> + +// 在 KMP 中非常关键:直接将平台已有类型映射给 commonMain 的公共声明 +expect class AtomicInt +actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger +``` + +## Nothing 类型与 TODO() + +在 Kotlin 中,`Nothing` 是一个非常特殊的底层类型:它是**所有其他类型的子类型(Bottom type)**。 + +它表示“这个函数永远不会正常返回”,比如抛出异常或者死循环的函数: + +```kotlin +fun fail(message: String): Nothing { + throw IllegalArgumentException(message) +} +``` + +因为 `Nothing` 是所有类型的子类,所以在类型推断和空安全配合时非常方便: + +```kotlin +// 1. Elvis 操作符配合抛异常,编译器会自动把 Noting 识别为非空 String +val name: String = inputName ?: fail("Name is required") + +// 2. TODO() 占位 +fun calculateComplexLogic(): Int { + TODO("还没写完,占个位编译不报错") // TODO() 的返回值就是 Nothing +} +``` + ## IDEA 的常用快捷键 `ctrl + alt + L`:格式化代码 diff --git a/Learning/第一个KMP应用.md b/Learning/第一个KMP应用.md index cb7fdb5..aa0afc0 100644 --- a/Learning/第一个KMP应用.md +++ b/Learning/第一个KMP应用.md @@ -21,7 +21,7 @@ ## 跑起来看看效果 -Gradle 默认会注册启动项 `hotRun`,它会被 IDEA / Android Studio 识别为 `desktopApp [hot] 🔥`。这是开发阶段的主要的测试方法。点击,即可启动编译,并运行热重载程序,它可以在你修改源代码并编译后,直接在原有程序窗口同步你的修改。 +Gradle 默认会注册启动项 `hotRun`,如果是在 IDEA / Android Studio 创建的项目,会在 IDE 中添加一个叫 `desktopApp [hot] 🔥` 的配置文件。这是开发阶段的主要的测试方法。点击,即可启动编译,并运行热重载程序,它可以在你修改源代码并编译后,直接在原有程序窗口同步你的修改。 ## commonMain 编写设计 @@ -77,7 +77,7 @@ Column 内部,有一个 `Button`,还有一个被 `AnimatedVisibility` 框起 我们来看它的 showContent 变量如何被定义,然后在 Button 被点击时被改变 ```kotlin - // 将 showContent 委托给一个 Compose state + // 将 showContent 委托给一个 Compose State var showContent by remember { mutableStateOf(false) } // 一些排版 diff --git a/Learning/进入KMP.md b/Learning/进入KMP.md index 9fc07b3..0050add 100644 --- a/Learning/进入KMP.md +++ b/Learning/进入KMP.md @@ -25,13 +25,13 @@ - build.gradle.kts - .gitignore - README.md - - gradlew (二进制脚本,一个 bat,一个 sh) + - gradlew (脚本文件,一个 bat,一个 sh) - gradle.proporties - local.proporties (默认被 ignore) - build.gradle.kts - settings.gradle.kts -如果你的项目还要包含 server 的部分的话,那会稍微复杂一点点。 +如果你的项目还要包含 server 的部分的话,那会稍微复杂一点点,这个后面再说。 ### 各部分用途