ZetCode

Kotlin expect 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 多平台项目使用 expectactual 关键字来定义平台特定的实现。 expect 关键字声明一个必须为每个目标平台实现的 API。

基本定义

expect 关键字标记一个声明,该声明预期具有平台特定的实现。 它与平台模块中的 actual 声明配对。 这种机制支持共享公共代码,同时允许平台差异。

基本的 expect/actual 函数

最简单的用例是声明一个每个平台都必须实现的预期函数。 公共模块定义了预期的签名和返回类型。

CommonModule.kt
package com.zetcode.common

expect fun getPlatformName(): String
JvmModule.kt
package com.zetcode.jvm

actual fun getPlatformName(): String = "JVM"

公共模块声明了一个预期的函数 getPlatformName。 JVM 模块提供了实际的实现。 其他平台将有自己的实现。 编译器确保所有预期的声明都有实际的实现。

expect/actual 类

类也可以使用 expect 声明,并在每个平台上以不同的方式实现。 公共模块定义了预期的接口,而平台模块提供实现。

CommonModule.kt
package com.zetcode.common

expect class PlatformTimer {
    fun start()
    fun stop()
    fun getCurrentTime(): Long
}
JvmModule.kt
package com.zetcode.jvm

import java.time.Instant

actual class PlatformTimer {
    private var running = false
    private var startTime: Instant? = null
    
    actual fun start() {
        running = true
        startTime = Instant.now()
    }
    
    actual fun stop() {
        running = false
    }
    
    actual fun getCurrentTime(): Long {
        return Instant.now().toEpochMilli() - 
            (startTime?.toEpochMilli() ?: 0)
    }
}

公共模块声明了一个预期的 PlatformTimer 类,它有三个方法。 JVM 实现使用 Java 的 Instant 类。 其他平台将使用其本地计时机制,同时保持相同的接口。

expect/actual 属性

属性可以使用 expect 声明,并在每个平台上以不同的方式实现。 这对于特定于平台的常量或值很有用。

CommonModule.kt
package com.zetcode.common

expect val platformLineSeparator: String
JvmModule.kt
package com.zetcode.jvm

actual val platformLineSeparator: String = System.lineSeparator()
NativeModule.kt
package com.zetcode.native

actual val platformLineSeparator: String = "\n"

公共模块期望一个换行符属性。 JVM 实现使用 System.lineSeparator,而本地实现使用一个简单的换行符。 这允许特定于平台的行为,同时保持一致的 API。

expect/actual 对象

单例对象可以使用 expect 声明,并在每个平台上以不同的方式实现。 这对于特定于平台的服务很有用。

CommonModule.kt
package com.zetcode.common

expect object PlatformService {
    fun initialize()
    fun getVersion(): String
    fun shutdown()
}
JvmModule.kt
package com.zetcode.jvm

actual object PlatformService {
    private var initialized = false
    
    actual fun initialize() {
        // JVM-specific initialization
        initialized = true
    }
    
    actual fun getVersion(): String {
        return System.getProperty("java.version")
    }
    
    actual fun shutdown() {
        // JVM-specific cleanup
        initialized = false
    }
}

公共模块声明了一个预期的单例对象,它有三个方法。 JVM 实现使用 Java 系统属性并维护初始化状态。 其他平台将提供自己的实现,同时保持相同的接口。

expect/actual 与默认实现

预期的声明可以提供一个默认实现,平台可以选择性地覆盖它。 这减少了平台共享行为时的样板代码。

CommonModule.kt
package com.zetcode.common

expect class FileHandler {
    fun readFile(path: String): String
    fun writeFile(path: String, content: String)
    
    fun fileExists(path: String): Boolean {
        // Default implementation
        return try {
            readFile(path)
            true
        } catch (e: Exception) {
            false
        }
    }
}
JvmModule.kt
package com.zetcode.jvm

import java.io.File

actual class FileHandler {
    actual fun readFile(path: String): String {
        return File(path).readText()
    }
    
    actual fun writeFile(path: String, content: String) {
        File(path).writeText(content)
    }
    
    // Inherits default fileExists implementation
}

公共模块声明 FileHandler,它有两个抽象方法和一个默认实现。 JVM 模块使用 Java 的 File 类实现抽象方法,并继承默认的 fileExists 实现。 平台可以在需要时覆盖默认值。

expect/actual 枚举类

枚举类可以使用 expect 声明,并在每个平台上以不同的方式实现。 这允许特定于平台的枚举条目或行为。

CommonModule.kt
package com.zetcode.common

expect enum class PlatformType {
    DESKTOP, MOBILE, WEB, EMBEDDED;
    
    abstract fun getDisplayName(): String
}
JvmModule.kt
package com.zetcode.jvm

actual enum class PlatformType {
    DESKTOP {
        override fun getDisplayName() = "Java Desktop"
    },
    MOBILE {
        override fun getDisplayName() = "Android"
    },
    WEB {
        override fun getDisplayName() = "JVM Web"
    },
    EMBEDDED {
        override fun getDisplayName() = "JVM Embedded"
    };
    
    actual override fun getDisplayName(): String
}

公共模块声明了一个预期的枚举,它有一个抽象方法。 JVM 实现为每个枚举条目提供特定于平台的显示名称。 其他平台将实现自己的版本,并提供适当的显示名称。

expect/actual 接口

接口可以使用 expect 声明,并在每个平台上以不同的方式实现。 这对于通用接口后面的特定于平台的功能很有用。

CommonModule.kt
package com.zetcode.common

expect interface PlatformStorage {
    fun saveData(key: String, value: String)
    fun loadData(key: String): String?
    fun clearData(key: String)
}
JvmModule.kt
package com.zetcode.jvm

import java.util.prefs.Preferences

actual class PlatformStorageImpl : PlatformStorage {
    private val prefs = Preferences.userRoot()
    
    actual override fun saveData(key: String, value: String) {
        prefs.put(key, value)
    }
    
    actual override fun loadData(key: String): String? {
        return prefs.get(key, null)
    }
    
    actual override fun clearData(key: String) {
        prefs.remove(key)
    }
}

公共模块声明了一个预期的存储接口。 JVM 实现使用 Java 的 Preferences API。 其他平台将使用其本地存储机制,同时保持相同的接口约定。

expect/actual 的最佳实践

来源

Kotlin 多平台文档

本教程深入探讨了 Kotlin 的 expect 关键字,展示了它如何实现多平台开发。 我们探讨了各种声明类型及其特定于平台的实现。 适当使用 expect/actual 允许共享代码,同时干净地处理平台差异。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有多年的编程经验。 我从 2007 年开始撰写编程文章。 到目前为止,我已经写了 1400 多篇文章和 8 本电子书。 我拥有超过八年的编程教学经验。

列出 所有 Kotlin 教程