2-2-36 快速掌握Kotlin-互操作性与可空性

20 阅读6分钟

Kotlin 语言互操作性与可空性详解

一、Kotlin 与 Java 互操作性

1. 基础互操作

从 Kotlin 调用 Java 代码
// Java 类
// public class JavaClass {
//     private String name;
//     public JavaClass(String name) { this.name = name; }
//     public String getName() { return name; }
//     public void setName(String name) { this.name = name; }
// }

// 在 Kotlin 中使用
fun main() {
    val javaObj = JavaClass("Kotlin")
    println(javaObj.name) // 自动转换为属性
    javaObj.name = "Java"
    println(javaObj.name)
}
类型映射
Java 类型Kotlin 类型
byteByte
shortShort
intInt
longLong
floatFloat
doubleDouble
charChar
booleanBoolean
java.lang.IntegerInt?
java.lang.StringString?
java.lang.ObjectAny
voidUnit

2. 空安全与平台类型

平台类型(Platform Types)
// Java 方法
// public String getName() { return name; }

// Kotlin 中调用
fun processJavaObject(obj: JavaClass) {
    // name 是平台类型 String!
    val name = obj.name // 类型推断为 String!
    
    // 需要显式处理可空性
    val length1: Int? = name?.length  // 安全调用
    val length2: Int = name!!.length  // 非空断言(有风险)
    val length3: Int = name?.length ?: 0  // Elvis 操作符
    
    // 或者声明类型
    val safeName: String? = obj.name
    val unsafeName: String = obj.name  // 编译警告,可能 NPE
}
使用注解改进互操作
// Java 中使用注解
// import org.jetbrains.annotations.NotNull;
// import org.jetbrains.annotations.Nullable;

// public class AnnotatedJavaClass {
//     @NotNull
//     public String getNonNullName() { return "NonNull"; }
//     
//     @Nullable
//     public String getNullableName() { return null; }
// }

// Kotlin 中调用
fun useAnnotatedClass(obj: AnnotatedJavaClass) {
    val nonNull: String = obj.nonNullName  // Kotlin 知道这是非空的
    val nullable: String? = obj.nullableName  // Kotlin 知道这是可空的
}

3. SAM 转换(函数式接口)

// Java 接口
// public interface OnClickListener {
//     void onClick(View view);
// }

// Kotlin 中使用 SAM 转换
val listener = OnClickListener { view ->
    println("View clicked: ${view.id}")
}

// 更复杂的例子
// Java: public interface Callback<T> { void onResult(T result); }
val callback = Callback<String> { result ->
    println("Result: $result")
}

4. 集合互操作

// Java 返回的集合
// public List<String> getStrings() { ... }

fun processJavaList(javaList: List<String>) {
    // Java List 在 Kotlin 中是不可变的(只读)
    val kotlinList: List<String> = javaList
    
    // 需要转换为可变集合才能修改
    val mutableList = javaList.toMutableList()
    mutableList.add("new item")
    
    // Java 可变集合在 Kotlin 中是可变的
    // public ArrayList<String> getMutableStrings() { ... }
    val javaMutable: MutableList<String> = getMutableStrings()
    javaMutable.add("another item")
}
集合类型映射
Java 类型Kotlin 只读类型Kotlin 可变类型
List<T>List<T>MutableList<T>
Set<T>Set<T>MutableSet<T>
Map<K, V>Map<K, V>MutableMap<K, V>
ArrayList<T>MutableList<T>MutableList<T>
HashSet<T>MutableSet<T>MutableSet<T>
HashMap<K, V>MutableMap<K, V>MutableMap<K, V>

5. 异常处理互操作

// Java 方法抛出受检异常
// public void riskyMethod() throws IOException { ... }

// Kotlin 中调用
fun callRiskyJavaMethod() {
    try {
        riskyMethod()  // Kotlin 不强制处理受检异常
    } catch (e: IOException) {
        println("捕获到IO异常: ${e.message}")
    }
}

// 在 Kotlin 中声明抛出的异常(给 Java 调用)
@Throws(IOException::class)
fun kotlinMethodThatThrows(): String {
    if (Random.nextBoolean()) {
        throw IOException("随机失败")
    }
    return "成功"
}

6. 静态成员互操作

// Java 静态方法
// public class Utils {
//     public static String format(String input) { ... }
// }

// Kotlin 中调用
val result = Utils.format("hello")

// Kotlin 伴生对象在 Java 中访问
class KotlinClass {
    companion object {
        @JvmStatic  // 生成真正的静态方法
        fun staticMethod() = "static"
        
        fun instanceMethod() = "instance"
    }
}

// Java 中调用
// String staticResult = KotlinClass.staticMethod();  // 可以直接调用
// String instanceResult = KotlinClass.Companion.instanceMethod();  // 需要通过 Companion

7. 默认参数与重载

// Kotlin 函数带默认参数
@JvmOverloads  // 为 Java 生成重载方法
fun createUser(
    name: String,
    age: Int = 0,
    email: String? = null
): User = User(name, age, email)

// 在 Java 中可以使用不同的重载
// User user1 = createUser("Alice");
// User user2 = createUser("Bob", 25);
// User user3 = createUser("Charlie", 30, "charlie@example.com");

8. 属性互操作

// Kotlin 属性
class Person(val name: String) {
    var age: Int = 0
        private set  // 只在类内可修改
    
    @JvmField  // 作为 Java 字段暴露,不生成 getter/setter
    val id: String = UUID.randomUUID().toString()
}

// Java 中使用
// Person person = new Person("Alice");
// String name = person.getName();  // name 有 getter
// int age = person.getAge();  // age 有 getter
// person.setAge(25);  // 编译错误,setter 是 private
// String id = person.id;  // 直接访问字段

二、Kotlin 可空性系统

1. 可空类型基础

// 非空类型
var nonNullString: String = "Hello"
// nonNullString = null  // 编译错误

// 可空类型
var nullableString: String? = "Hello"
nullableString = null  // 允许

// 安全的属性访问
val length1: Int? = nullableString?.length
val length2: Int = nullableString?.length ?: 0  // Elvis 操作符
val length3: Int = nullableString!!.length  // 非空断言(有风险)

2. 安全调用操作符 ?.

class Company(val name: String, val address: Address?)
class Address(val street: String, val city: City?)
class City(val name: String, val country: String?)

// 链式安全调用
fun getCountryName(company: Company?): String? {
    return company?.address?.city?.country
}

// 与 let 结合使用
company?.address?.let { address ->
    println("公司地址: ${address.street}")
}

// 安全调用中的函数调用
val result = nullableString?.let { 
    it.uppercase().reversed() 
}

3. Elvis 操作符 ?:

// 基本用法
val name: String = nullableString ?: "默认值"

// 复杂表达式
val configPath = System.getenv("CONFIG_PATH") 
    ?: System.getProperty("config.path")
    ?: "./config.json"

// 与 return/throw 结合
fun processUser(user: User?) {
    val validUser = user ?: return  // 如果 user 为 null,直接返回
    val name = user.name ?: throw IllegalArgumentException("用户名为空")
    
    // 处理有效用户
}

// 嵌套使用
val cityName = user
    ?.address
    ?.city
    ?.name ?: "未知城市"

4. 非空断言 !!

// 谨慎使用非空断言
val nullableValue: String? = getNullableValue()

// 危险:可能抛出 NPE
val dangerous: String = nullableValue!!

// 稍微安全的使用场景
fun processValues(values: List<String?>): List<String> {
    return values.filterNotNull()  // 更好的方式
    // 或者
    return values.map { it!! }  // 危险,但有时必要
}

// 局部变量验证后使用 !!
fun safeUseOfBangBang() {
    val value: String? = computeValue()
    
    if (value != null) {
        // 在这个作用域内,Kotlin 知道 value 非空
        val length = value.length  // 智能转换
    }
    
    // 或者
    check(value != null)  // 检查失败抛出异常
    val safeValue = value!!  // 这里安全
}

5. 安全转换 as?

val anyValue: Any = "String"

// 安全转换
val stringValue: String? = anyValue as? String
val intValue: Int? = anyValue as? Int  // 返回 null

// 典型用法
fun processInput(input: Any) {
    val string = input as? String
    if (string != null) {
        println("字符串长度: ${string.length}")
    } else {
        println("输入不是字符串")
    }
}

6. 集合的可空性

// 元素可空的集合
val nullableList: List<String?> = listOf("a", null, "c", null)

// 过滤空值
val nonNullList: List<String> = nullableList.filterNotNull()

// 处理可空元素的集合
val lengths: List<Int> = nullableList.mapNotNull { it?.length }

// Map 中的可空值
val nullableMap: Map<String, String?> = mapOf(
    "key1" to "value1",
    "key2" to null
)

val value = nullableMap["key2"] ?: "默认值"

7. 平台类型与可空性注解

// Java 代码
// public class JavaService {
//     @Nullable
//     public String findById(@Nullable String id) { ... }
//     
//     @NotNull
//     public String getName() { ... }
// }

// Kotlin 中使用
fun useJavaService(service: JavaService) {
    // 由于注解,Kotlin 知道正确的类型
    val name: String = service.getName()  // 非空
    val result: String? = service.findById("123")  // 可空
    
    // 如果没有注解,使用平台类型
    val platformResult = service.unannotatedMethod()  // 类型为 String!
    
    // 处理平台类型
    val safeResult1: String? = platformResult  // 显式声明为可空
    val safeResult2: String = platformResult ?: ""  // 提供默认值
}

8. 扩展函数处理可空性

// 为可空类型定义扩展函数
fun String?.safeLength(): Int = this?.length ?: 0

fun String?.isNullOrBlankExtended(): Boolean {
    return this == null || this.isBlank()
}

// 使用
val nullableString: String? = null
println(nullableString.safeLength())  // 0
println(nullableString.isNullOrBlankExtended())  // true

// 接收者为可空的扩展
inline fun <T : Any, R> T?.ifNotNull(block: (T) -> R): R? {
    return if (this != null) block(this) else null
}

val result = nullableString.ifNotNull { 
    it.uppercase() 
}

9. 泛型中的可空性

// 泛型类型参数默认是可空的
class Box<T>(val value: T)  // T 可以是任何类型,包括可空

val box1: Box<String> = Box("hello")
val box2: Box<String?> = Box(null)  // 允许

// 指定非空上界
class NonNullBox<T : Any>(val value: T)  // T 必须是非空类型

val box3: NonNullBox<String> = NonNullBox("hello")
// val box4: NonNullBox<String?> = NonNullBox(null)  // 编译错误

// 协变和逆变中的可空性
interface Producer<out T> {
    fun produce(): T
}

interface Consumer<in T> {
    fun consume(item: T)
}

// 可空性影响类型关系
val stringProducer: Producer<String> = object : Producer<String> {
    override fun produce(): String = "Hello"
}

val nullableStringProducer: Producer<String?> = stringProducer  // 允许,String 是 String? 的子类型

10. 实际场景中的可空性处理

场景1:API 响应解析
data class ApiResponse(
    val success: Boolean,
    val data: UserData?,
    val error: String?
)

data class UserData(
    val id: String,
    val name: String?,
    val email: String
)

fun processApiResponse(response: ApiResponse): Result<User> {
    return if (response.success) {
        val data = response.data ?: return Result.failure("数据为空")
        val name = data.name ?: "匿名用户"
        val user = User(data.id, name, data.email)
        Result.success(user)
    } else {
        val error = response.error ?: "未知错误"
        Result.failure(error)
    }
}
场景2:配置管理
class Configuration {
    private val config = mutableMapOf<String, Any?>()
    
    fun <T : Any> get(key: String, defaultValue: T): T {
        return (config[key] as? T) ?: defaultValue
    }
    
    fun <T : Any> getOrThrow(key: String): T {
        return config[key] as? T
            ?: throw IllegalArgumentException("配置项 '$key' 不存在或类型不匹配")
    }
    
    fun getStringOrNull(key: String): String? {
        return config[key] as? String
    }
}
场景3:数据库实体
@Entity
data class UserEntity(
    @Id
    val id: String,
    
    @Column(nullable = false)
    val username: String,
    
    @Column
    val displayName: String?,  // 可空,用户可能没有设置
    
    @Column
    val avatarUrl: String?,
    
    @Column(nullable = false)
    val createdAt: Instant,
    
    @Column
    val deletedAt: Instant?  // 软删除,可空表示未删除
) {
    val isActive: Boolean
        get() = deletedAt == null
}

11. 可空性的性能优化

// 使用局部变量避免重复空检查
fun processUser(user: User?) {
    // 不好:多次空检查
    if (user != null) {
        println(user.name)
        println(user.email)
        println(user.age)
    }
    
    // 好:使用 let
    user?.let {
        println(it.name)
        println(it.email)
        println(it.age)
    }
    
    // 更好:智能转换
    val nonNullUser = user ?: return
    println(nonNullUser.name)
    println(nonNullUser.email)
    println(nonNullUser.age)
}

// 内联函数减少开销
inline fun <T, R> T?.ifNotNull(block: (T) -> R): R? {
    return if (this != null) block(this) else null
}

// 使用 when 表达式
fun describeUser(user: User?): String {
    return when {
        user == null -> "用户不存在"
        user.name == null -> "匿名用户"
        user.age < 18 -> "未成年用户: ${user.name}"
        else -> "成年用户: ${user.name}"
    }
}

三、互操作与可空性的结合

1. 在混合项目中处理可空性

// Java 和 Kotlin 混合项目的最佳实践

// 1. 在 Java 中使用 @Nullable/@NotNull 注解
// 2. 在 Kotlin 中正确处理平台类型

// 创建互操作友好的 API
class InteropService {
    // 为 Java 调用者提供明确的空安全 API
    @JvmStatic
    fun findUser(id: String): User? = database.findUser(id)
    
    // 使用 @JvmOverloads 提供默认值
    @JvmOverloads
    fun createUser(
        name: String,
        email: String? = null,
        age: Int = 0
    ): User = User(name, email, age)
}

// 处理来自 Java 的平台类型
fun processJavaCollection(javaList: List<String>): List<String> {
    // 假设我们知道 Java 代码不返回 null
    return javaList.filterNotNull()  // 防御性编程
}

2. 互操作中的常见陷阱

// 陷阱1:Java 的 getter/setter 命名约定
// Java 类:
// public class Person {
//     private boolean active;
//     public boolean isActive() { return active; }
//     public void setActive(boolean active) { this.active = active; }
// }

// Kotlin 中:
val person = Person()
val isActive = person.active  // 正确:Kotlin 识别 isActive() 为 active 属性
person.active = true  // 正确:调用 setActive()

// 陷阱2:重载冲突
// Java 方法:
// public void process(String input) { ... }
// public void process(String input, String option) { ... }

// Kotlin 中调用带默认参数的方法可能冲突
fun kotlinProcess(input: String, option: String? = null) {
    // 这个函数在 Java 中看起来和上面的重载冲突
}

// 解决方案:使用 @JvmName 重命名
@JvmName("processWithOption")
fun kotlinProcess(input: String, option: String? = null) { ... }

3. 创建互操作友好的库

// 设计 Kotlin 库供 Java 使用
class InteropFriendlyLibrary {
    companion object {
        // 提供静态方法
        @JvmStatic
        fun staticMethod() = "static"
    }
    
    // 避免使用扩展函数作为公共 API
    fun instanceMethod() = "instance"
    
    // 提供 Java 友好的重载
    @JvmOverloads
    fun complexMethod(
        required: String,
        optional1: Int = 0,
        optional2: Boolean = false
    ) = "$required-$optional1-$optional2"
    
    // 处理可空性
    fun getValueOrNull(key: String): String? = storage[key]
    
    fun getValueOrDefault(key: String, defaultValue: String): String {
        return getValueOrNull(key) ?: defaultValue
    }
}

// 为 Java 提供扩展函数的替代方案
object StringUtils {
    fun String.customExtension(): String = this.uppercase()
    
    // Java 中可以调用:StringUtils.customExtension("hello")
    fun customExtension(str: String): String = str.customExtension()
}

四、最佳实践总结

互操作性最佳实践:

  1. 使用注解:在 Java 代码中使用 @Nullable@NotNull 注解
  2. 提供静态成员:使用 @JvmStatic@JvmField 为 Java 提供便利
  3. 处理默认参数:使用 @JvmOverloads 为 Java 生成重载方法
  4. 命名避免冲突:使用 @JvmName 解决命名冲突
  5. 集合转换:明确转换 Java 和 Kotlin 集合类型

可空性最佳实践:

  1. 优先使用非空类型:只在必要时使用可空类型
  2. 避免使用 !!:除非你能 100% 确定不为 null
  3. 善用 Elvis 操作符:提供有意义的默认值
  4. 使用安全调用链a?.b?.c?.d
  5. 尽早验证:在函数入口处验证参数
  6. 使用智能转换:Kotlin 编译器能跟踪空检查后的类型
  7. 为 Java 调用者考虑:提供非空和可空版本的 API

混合项目指导:

  1. 统一编码规范:团队内统一空安全处理方式
  2. 渐进迁移:逐步将 Java 代码迁移到 Kotlin
  3. 测试覆盖:确保互操作边界有充分的测试
  4. 代码审查:特别注意混合语言边界的代码
  5. 文档化约定:记录团队在处理互操作和可空性时的约定

通过合理利用 Kotlin 的互操作性和可空性系统,可以在保持与 Java 生态兼容的同时,享受到 Kotlin 类型安全带来的好处。