Desktop (JVM) 互操作
Kotlin/JVM 是 Kotlin Multiplatform 项目中与 Java 生态集成最紧密的平台。理解如何利用 JVM 平台的强大功能,对于构建桌面应用或服务端组件至关重要。
Kotlin 调用 Java API
kotlin
// jvmMain
import java.io.File
import java.util.concurrent.Executors
import javax.swing.JFrame
import javax.swing.JButton
// 调用 Java File API
fun readFile(path: String): String {
return File(path).readText()
}
fun writeFile(path: String, content: String) {
File(path).writeText(content)
}
// 使用 Java 集合
fun processJavaList(javaList: java.util.List<String>): List<String> {
return javaList.map { it.uppercase() }
}
// 使用 ExecutorService
fun runAsync(task: () -> Unit) {
val executor = Executors.newSingleThreadExecutor()
executor.submit(task)
executor.shutdown()
}
// Swing GUI
fun createWindow() {
val frame = JFrame("Kotlin Desktop App")
val button = JButton("Click Me").apply {
addActionListener {
println("Button clicked!")
}
}
frame.add(button)
frame.setSize(300, 200)
frame.isVisible = true
}Java 类型映射
| Kotlin | Java |
|---|---|
kotlin.String | java.lang.String |
kotlin.Int | int / java.lang.Integer |
kotlin.collections.List | java.util.List |
kotlin.Array | Java 数组 T[] |
Unit | void |
Java 调用 Kotlin 代码
@JvmStatic 注解
kotlin
// Kotlin
object Utils {
@JvmStatic
fun format(text: String): String {
return text.uppercase()
}
}
class Config {
companion object {
@JvmStatic
val DEFAULT_TIMEOUT = 30
@JvmStatic
fun create(): Config = Config()
}
}java
// Java 调用
String formatted = Utils.format("hello"); // 静态调用
int timeout = Config.DEFAULT_TIMEOUT;
Config config = Config.create();@JvmField 注解
kotlin
// Kotlin
class Server {
@JvmField
var port: Int = 8080 // 暴露为 public field
var host: String = "localhost" // 生成 get/set 方法
}java
// Java 调用
Server server = new Server();
server.port = 9000; // 直接访问字段
server.setHost("0.0.0.0"); // 通过 setter@JvmOverloads 注解
kotlin
// Kotlin - 生成多个重载方法
class User @JvmOverloads constructor(
val name: String,
val age: Int = 18,
val email: String = ""
)java
// Java 可以使用
User user1 = new User("Alice");
User user2 = new User("Bob", 25);
User user3 = new User("Charlie", 30, "charlie@example.com");Compose Desktop 集成
标准代码块
kotlin
// desktopMain
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
@Composable
@Preview
fun App(viewModel: UserViewModel) {
var users by remember { mutableStateOf(emptyList<User>()) }
LaunchedEffect(Unit) {
users = viewModel.loadUsers()
}
MaterialTheme {
Column(modifier = Modifier.padding(16.dp)) {
Text("Users", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(8.dp))
users.forEach { user ->
Card(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)) {
Text(user.name, modifier = Modifier.padding(16.dp))
}
}
}
}
}
fun main() = application {
Window(onCloseRequest = ::exitApplication, title = "My App") {
val viewModel = UserViewModel()
App(viewModel)
}
}平台特定功能
kotlin
// desktopMain
import java.awt.Desktop
import java.net.URI
// 打开浏览器
fun openBrowser(url: String) {
if (Desktop.isDesktopSupported()) {
Desktop.getDesktop().browse(URI(url))
}
}
// 系统托盘
import java.awt.SystemTray
import java.awt.TrayIcon
import java.awt.Toolkit
fun createTrayIcon() {
if (SystemTray.isSupported()) {
val tray = SystemTray.getSystemTray()
val image = Toolkit.getDefaultToolkit().getImage("icon.png")
val trayIcon = TrayIcon(image, "My App")
tray.add(trayIcon)
}
}文件系统访问
标准代码块
kotlin
// jvmMain
import java.io.File
import java.nio.file.*
import kotlin.io.path.*
// 读写文件
fun saveData(filename: String, data: String) {
File(filename).writeText(data)
}
fun loadData(filename: String): String {
return File(filename).readText()
}
// 使用 Path API
fun listFiles(directory: String): List<String> {
return Path(directory).listDirectoryEntries()
.map { it.name }
}
// 监听文件变化
fun watchDirectory(path: String, onChange: (String) -> Unit) {
val watchService = FileSystems.getDefault().newWatchService()
val dir = Paths.get(path)
dir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY)
while (true) {
val key = watchService.take()
for (event in key.pollEvents()) {
onChange(event.context().toString())
}
key.reset()
}
}
// 应用数据目录
fun getAppDataDir(): String {
val os = System.getProperty("os.name").lowercase()
return when {
os.contains("win") -> System.getenv("APPDATA")
os.contains("mac") -> "${System.getProperty("user.home")}/Library/Application Support"
else -> "${System.getProperty("user.home")}/.config"
} + "/MyApp"
}多线程与并发
标准代码块
kotlin
// jvmMain
import java.util.concurrent.*
import kotlinx.coroutines.*
// ExecutorService
class TaskExecutor {
private val executor = Executors.newFixedThreadPool(4)
fun execute(task: () -> Unit) {
executor.submit(task)
}
fun shutdown() {
executor.shutdown()
executor.awaitTermination(10, TimeUnit.SECONDS)
}
}
// CompletableFuture 与协程互操作
suspend fun <T> CompletableFuture<T>.await(): T {
return suspendCancellableCoroutine { cont ->
whenComplete { result, exception ->
if (exception != null) {
cont.resumeWithException(exception)
} else {
cont.resume(result)
}
}
}
}
// 使用示例
suspend fun fetchData(): String {
val future = CompletableFuture.supplyAsync {
// 耗时操作
"Data"
}
return future.await()
}JDBC 数据库访问
标准代码块
kotlin
// jvmMain
import java.sql.*
class DatabaseManager(private val url: String) {
private var connection: Connection? = null
fun connect() {
connection = DriverManager.getConnection(url)
}
fun query(sql: String): List<Map<String, Any?>> {
val statement = connection?.createStatement() ?: return emptyList()
val resultSet = statement.executeQuery(sql)
val metadata = resultSet.metaData
val columnCount = metadata.columnCount
val results = mutableListOf<Map<String, Any?>>()
while (resultSet.next()) {
val row = mutableMapOf<String, Any?>()
for (i in 1..columnCount) {
row[metadata.getColumnName(i)] = resultSet.getObject(i)
}
results.add(row)
}
return results
}
fun execute(sql: String): Int {
val statement = connection?.createStatement() ?: return 0
return statement.executeUpdate(sql)
}
fun close() {
connection?.close()
}
}
// 使用示例
fun getUsers(): List<User> {
val db = DatabaseManager("jdbc:sqlite:app.db")
db.connect()
val rows = db.query("SELECT * FROM users")
val users = rows.map { row ->
User(
id = row["id"] as Long,
name = row["name"] as String
)
}
db.close()
return users
}系统集成
系统属性与环境变量
kotlin
// jvmMain
fun getSystemInfo(): Map<String, String> {
return mapOf(
"os" to System.getProperty("os.name"),
"osVersion" to System.getProperty("os.version"),
"javaVersion" to System.getProperty("java.version"),
"userHome" to System.getProperty("user.home"),
"tempDir" to System.getProperty("java.io.tmpdir")
)
}
fun getEnv(name: String): String? {
return System.getenv(name)
}进程管理
kotlin
// jvmMain
fun runCommand(command: String): String {
val process = Runtime.getRuntime().exec(command)
val output = process.inputStream.bufferedReader().readText()
process.waitFor()
return output
}
// 使用 ProcessBuilder
fun runCommandWithArgs(vararg args: String): String {
val process = ProcessBuilder(*args)
.redirectErrorStream(true)
.start()
val output = process.inputStream.bufferedReader().readText()
process.waitFor()
return output
}Gradle 配置
JVM 目标配置
kotlin
// shared/build.gradle.kts
kotlin {
jvm {
compilations.all {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs += listOf(
"-Xjsr305=strict",
"-Xopt-in=kotlin.RequiresOptIn"
)
}
}
testRuns.all {
executionTask.configure {
useJUnitPlatform()
}
}
}
sourceSets {
val jvmMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.9.0")
implementation("org.xerial:sqlite-jdbc:3.47.1.0")
}
}
val jvmTest by getting {
dependencies {
implementation(kotlin("test-junit5"))
implementation("org.junit.jupiter:junit-jupiter:5.11.4")
}
}
}
}Desktop 应用打包
kotlin
// build.gradle.kts
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
compose.desktop {
application {
mainClass = "com.example.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "My App"
packageVersion = "1.0.0"
macOS {
iconFile.set(project.file("icon.icns"))
}
windows {
iconFile.set(project.file("icon.ico"))
}
linux {
iconFile.set(project.file("icon.png"))
}
}
}
}最佳实践
✅ 实践 1:使用 Kotlin 扩展优化 Java API
kotlin
// ✅ 创建扩展函数
fun File.ensureExists(): File {
if (!exists()) {
parentFile?.mkdirs()
createNewFile()
}
return this
}
// 使用
File("data/app.db").ensureExists().writeText("data")✅ 实践 2:资源管理 use 函数
kotlin
// ✅ 自动关闭资源
fun readFile(path: String): String {
return File(path).inputStream().use { stream ->
stream.bufferedReader().readText()
}
}
// ❌ 手动关闭
val stream = File(path).inputStream()
try {
return stream.bufferedReader().readText()
} finally {
stream.close()
}✅ 实践 3:协程替代回调
kotlin
// ✅ 使用协程
suspend fun loadData(): Data = withContext(Dispatchers.IO) {
// 耗时操作
}
// ❌ 回调地狱
fun loadData(callback: (Data) -> Unit) {
Thread {
val data = /* ... */
callback(data)
}.start()
}✅ 实践 4:平台检测
kotlin
enum class Platform {
WINDOWS, MACOS, LINUX, UNKNOWN
}
fun currentPlatform(): Platform {
val os = System.getProperty("os.name").lowercase()
return when {
os.contains("win") -> Platform.WINDOWS
os.contains("mac") -> Platform.MACOS
os.contains("nux") -> Platform.LINUX
else -> Platform.UNKNOWN
}
}Kotlin/JVM 提供了与 Java 生态的完美互操作,使得开发者可以利用庞大的 Java 库生态,同时享受 Kotlin 语言的现代特性。