Files

8.3 KiB
Raw Permalink Blame History

成为 Kotlin 超人

来到这里,我不再会给出详尽的用法。
以你的实力,只要我指出在什么时候应该使用什么就够了。

在哪里找到最全面的 Kotlin 文档

虽然 深入 Kotlin 中已经讲了很多内容,但其实我仍有删减,比如生成随机数相关的内容。

官方文档

JSON 序列化

Kotlin-serialization 是最佳选择,它需要加载一个 gradle 插件,注意要与你的 Kotlin 版本一致:

plugins {
    kotlin("jvm") version "2.3.20"
    kotlin("plugin.serialization") version "2.3.20"
}

还有一个依赖

implementation("org.jetbrains.kotlin:kotlin-reflect")

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 即可,零依赖且原生支持异步:

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:

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 上最轻松的选择。

依赖:

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:

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)

注意,库文件需要放在特定的文件夹,这里参考官方文档

与 C 混编(cinterop)

在 Kotlin/Native(编译为原生二进制的目标平台,如 iOS、Linux、macOS、Windows)中,不需要 JVM 和 JNA,Kotlin 官方直接提供了 cinterop 工具。

  1. 在项目源码旁新建 .def 文件(例如 src/nativeInterop/cinterop/myclib.def):
headers = myclib.h
staticLibraries = libmyclib.a
libraryPaths = /path/to/lib
  1. 在 build.gradle.kts 中声明 cinterop:
kotlin {
    linuxX64 {
        binaries.executable()
        compilations.getByName("main").cinterops {
            val myclib by creating
        }
    }
}
  1. Gradle 构建后会自动生成 Kotlin 绑定包,在代码中直接 import 即可像调用普通 Kotlin 函数一样调用 C 函数,并通过 memScoped 安全管理 C 指针与内存。

委托(Delegation 与 by 关键字)

Kotlin 的 by 能极大精简代码,主要用在两个地方:

类委托

不必手动重写接口的所有方法再转发给内部对象,直接 by 代理:

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("") } 就是如此):

// 监听属性变化的标准库委托
var name: String by Delegates.observable("初始值") { prop, old, new ->
    println("$old -> $new")
}

内联值类

如果想给基础类型(如 Long、Int)包一层业务类型(例如 UserId、OrderId)以保证强类型安全,又不想每次创建对象都在堆内存里产生 GC 开销:

@JvmInline
value class UserId(val value: Long)

fun getUser(id: UserId) { ... }

编译后会在字节码里直接拆包还原成底层的原始类型,完全是零运行时开销。Compose 里的 Color、Dp 底层全部是用它实现的。

优雅的异常处理:runCatching

在现代 Kotlin 代码中,尽量少写层层嵌套的 try-catch。标准库的 runCatching 更加函数式和连贯:

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),一个元素走完所有变换才处理下一个:

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 上导致框架失效。此时必须加上前缀:

data class User(
    @field:NotBlank          // 作用于字段
    val username: String,

    @get:JsonIgnore          // 作用于 getter 方法上
    val secretToken: String
)

将 jar 包上传至 Maven 仓库

这需要一个 gradle 插件:

plugins {
    id("com.vanniktech.maven.publish") version "0.37.0"
}

具体的写法可以参考 FFmpeg-Kit_KMP 的 gradle 配置。