28.Kotlin 空安全:可空类型的扩展与平台类型

56 阅读7分钟

一、可空类型的扩展函数

1.1 扩展函数定义语法

扩展函数允许为现有类(包括可空类型)添加新功能,无需继承或修改原始类。

// 为非空类型定义扩展
fun String.toTitleCase(): String =
    split(" ").joinToString(" ") { it.replaceFirstChar(Char::uppercase) }

// 为可空类型定义扩展
fun String?.orEmpty(): String = this ?: ""
fun String?.isNullOrBlank(): Boolean = this == null || isBlank()

// 使用泛型的可空扩展
fun  T?.or(default: T): T = this ?: default

1.2 接收者可为空的扩展

可空接收者的扩展函数在this为null时仍可调用,通常用于空值处理。

// 基础示例
fun String?.safeSubstring(start: Int, end: Int): String? =
    this?.substring(start.coerceAtLeast(0), end.coerceAtMost(length))

// 链式操作扩展
fun String?.takeIfNotEmpty(): String? =
    this?.takeIf { it.isNotEmpty() }

// 带默认值的转换
fun String?.toIntOr(default: Int): Int =
    this?.toIntOrNull() ?: default

// 复杂扩展示例
fun String?.transformIfNotNull(
    predicate: (String) -> Boolean,
    transform: (String) -> String
): String? = this?.let {
    if (predicate(it)) transform(it) else it
}

1.3 标准库中的实用扩展

Kotlin标准库提供了丰富的可空类型扩展:

// String?.orEmpty() - 最常用的扩展
val nullableString: String? = null
val safeString: String = nullableString.orEmpty()  // 返回""

// CharSequence?.isNullOrEmpty() / isNullOrBlank()
fun validateInput(input: String?): Boolean {
    if (input.isNullOrBlank()) {
        return false
    }
    return true
}

// Collection?.orEmpty()
fun processList(list: List?): List {
    return list.orEmpty().filter { it.length > 3 }
}

// Map?.orEmpty()
fun getConfigValue(map: Map?, key: String): String {
    return map.orEmpty()[key].orEmpty()
}

1.4 自定义扩展函数设计

设计良好的可空扩展函数应遵循单一职责原则并提供清晰的空值处理语义。

// 1. 工具类扩展
object StringExtensions {
    fun String?.toDefault(default: String = ""): String = this ?: default
    fun String?.toDefaultIfBlank(default: String = ""): String =
        this?.takeIf { it.isNotBlank() } ?: default
}

// 2. 领域特定扩展
class UserDomainExtensions {
    fun String?.validateEmail(): Boolean =
        this?.matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}\$")) == true

    fun String?.normalizePhone(): String? =
        this?.replace(Regex("[^+\\d]"), "")?.takeIf { it.length >= 10 }
}

// 3. 链式处理扩展
fun  T?.chain(
    vararg transformers: (T) -> R?,
    default: () -> R
): R = this?.let { value ->
    transformers.firstNotNullOfOrNull { it(value) }
} ?: default()

// 使用示例
val result = nullableString.chain(
    { it.trim() },
    { it.take(10) },
    default = { "default" }
)

二、let/apply/run/also/with与可空性

2.1 作用域函数的可空处理

作用域函数是处理可空值的强大工具,它们有不同的接收者和返回值特性。

// 比较表
// 函数    | 接收者    | 参数      | 返回值    | 可空处理
// let     | it       | 接收者     | lambda结果 | 通过?.调用
// run     | this     | 接收者     | lambda结果 | 通过?.调用
// with    | this     | 参数       | lambda结果 | 不直接支持
// apply   | this     | 接收者     | 接收者     | 通过?.调用
// also    | it       | 接收者     | 接收者     | 通过?.调用

2.2 可空接收者的处理模式

// 1. let - 转换可空值
fun processInput(input: String?): String? {
    return input?.let {
        it.trim()
           .takeIf { str -> str.length >= 3 }
           ?.uppercase()
    }
}

// 2. apply - 配置可空对象
class Config {
    var host: String = "localhost"
    var port: Int = 8080
    var timeout: Long = 5000
}

fun createConfig(init: Config.() -> Unit = {}): Config {
    return Config().apply(init)
}

val config: Config? = getConfigFromFile()?.apply {
    host = "example.com"
    port = 443
    timeout = 10000
}

// 3. run - 在可空对象上执行操作并返回结果
fun calculateDiscount(price: Double?, discountRate: Double?): Double? {
    return price?.run {
        val rate = discountRate ?: 0.1
        this * (1 - rate)
    }
}

// 4. also - 执行附加操作
fun processUser(user: User?): User? {
    return user?.also {
        logger.info("Processing user: ${it.id}")
        auditService.logAccess(it.id)
    }?.also {
        it.lastAccessed = Instant.now()
    }
}

// 5. with - 不直接支持可空,需要结合安全调用
fun formatUserInfo(user: User?): String {
    return with(user) {  // 编译错误,需要?.
        "${this?.name} (${this?.email})"
    }

    // 正确方式
    return user?.let {
        with(it) {
            "$name ($email)"
        }
    } ?: "Unknown user"
}

2.3 各函数的适用场景

// 场景1: 数据验证和转换 - 使用let
fun parseAndValidate(json: String?): Data? = json?.let { raw ->
    try {
        parseJson(raw)?.takeIf { it.isValid() }
    } catch (e: Exception) {
        null
    }
}

// 场景2: 对象构建和配置 - 使用apply
fun createUser(dto: UserDto?): User? = dto?.apply {
    User(
        id = id ?: generateId(),
        name = requireNotNull(name) { "Name is required" },
        email = email,
        metadata = metadata.orEmpty()
    )
}

// 场景3: 多个操作链 - 使用run
fun processOrder(order: Order?): Receipt? = order?.run {
    val items = validateItems(items)
    val total = calculateTotal(items, discount)
    val tax = calculateTax(total, shippingAddress)

    Receipt(
        orderId = id,
        items = items,
        total = total,
        tax = tax
    )
}

// 场景4: 副作用操作 - 使用also
fun sendNotification(user: User?, message: String): User? = user?.also {
    notificationService.send(it.email, message)
    analytics.track("notification_sent", it.id)
}

// 场景5: 替代多个安全调用 - 使用let链
// 传统方式
val city = user?.address?.city
val formattedCity = city?.uppercase()

// 使用let链
val formattedCity = user?.address?.city?.let { it.uppercase() }

// 复杂转换
val discount = user?.let { user ->
    order?.let { order ->
        calculateDiscount(user, order)
    }
}

三、平台类型处理

3.1 Java互操作中的可空性

平台类型是从Java代码中引入的类型,Kotlin不知道它们的可空性,表示为 T!

// Java类
public class JavaService {
    public String getName() { return "name"; }
    public String getNullableName() { return null; }
    public @Nullable String getAnnotatedNullable() { return null; }
    public @NotNull String getAnnotatedNotNull() { return "not null"; }
}

// Kotlin使用
val service = JavaService()

// 1. 平台类型 - Kotlin不知道是否可空
val name1 = service.name  // 类型: String!
val name2: String = service.name  // 可能NPE
val name3: String? = service.name  // 安全,但可能不必要

// 2. 带注解的方法
val annotatedNullable: String? = service.annotatedNullable  // 可空
val annotatedNotNull: String = service.annotatedNotNull     // 非空

3.2 平台类型注解

Kotlin支持多种注解来标记Java类型的可空性。

// Java代码中添加注解
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

public class AnnotatedJavaClass {

    // Kotlin会识别为可空
    @Nullable
    public String getNullableValue() { return null; }

    // Kotlin会识别为非空
    @NotNull
    public String getNotNullValue() { return "value"; }

    // 支持多种注解框架
    @javax.annotation.Nonnull
    public String getJavaxNonNull() { return "value"; }

    @android.support.annotation.NonNull
    public String getAndroidNonNull() { return "value"; }
}

3.3 @Nullable与@NotNull注解

理解不同注解框架的支持情况。

// Kotlin编译器支持的注解框架
// 1. JetBrains (@Nullable, @NotNull)
// 2. JSR-305 (@Nonnull, @Nullable)
// 3. Android (@NonNull, @Nullable)
// 4. FindBugs (@Nullable, @NonNull)
// 5. ReactiveX (@Nullable)

// 配置gradle使用特定注解
// build.gradle.kts
dependencies {
    compileOnly("com.google.code.findbugs:jsr305:3.0.2")
}

// 或者在模块级别配置
// build.gradle.kts
android {
    kotlinOptions {
        freeCompilerArgs = listOf(
            "-Xjsr305=strict"  // 严格模式
        )
    }
}

3.4 类型推断与平台类型

平台类型在类型推断时可能导致问题,需要显式处理。

// 平台类型推断问题
fun processJavaData() {
    val javaList = java.util.ArrayList()  // 类型: (Mutable)List!

    // 问题1: 推断为平台类型
    val first = javaList[0]  // 类型: String!

    // 问题2: 可能NPE
    val length = first.length  // 可能NPE

    // 解决方案1: 显式声明类型
    val firstExplicit: String? = javaList[0]
    val safeLength = firstExplicit?.length ?: 0

    // 解决方案2: 使用标准库转换
    val kotlinList: List = javaList.toList()
    val kotlinMutableList: MutableList = javaList

    // 解决方案3: 创建安全包装器
    fun  List.safeGet(index: Int): T? =
        if (index in indices) get(index) else null

    val safeValue = javaList.safeGet(0)  // 类型: String?
}

四、集合与可空性

4.1 集合元素的可空性

集合的可空性有三个层次:集合本身、集合元素、集合元素的属性。

// 三层可空性
val list1: List           // 列表非空,元素非空
val list2: List?          // 列表可空,元素非空
val list3: List          // 列表非空,元素可空
val list4: List?         // 列表可空,元素可空

// 实际示例
data class Person(val name: String, val age: Int?)

// 不同层次的可空性
val people: List?             // 列表可能为null
val peopleWithNullableNames: List  // 列表元素可能为null
val peopleWithOptionalAge: List     // age属性可能为null

4.2 可空集合的操作

处理可空集合需要特殊注意操作的类型安全性。

// 1. 映射操作
val names: List = listOf("Alice", null, "Bob", "Carol")
val lengths: List = names.map { it?.length }  // [5, null, 3, 5]

// 2. 过滤操作
val nonNullNames: List = names.filterNotNull()  // ["Alice", "Bob", "Carol"]

// 3. mapNotNull - 映射并过滤null
val nameLengths: List = names.mapNotNull { it?.length }  // [5, 3, 5]

// 4. 复杂转换
data class User(val id: String, val profile: Profile?)
data class Profile(val name: String, val email: String?)

val users: List = getUsers()

// 获取所有有效的邮箱
val emails: List = users
    .mapNotNull { it.profile?.email }  // 嵌套可空属性处理
    .filter { it.isNotBlank() }

// 5. 分组操作
val groupedByNull: Map> = names.groupBy { it == null }

4.3 filterNotNull()等辅助函数

Kotlin提供了丰富的集合操作函数来处理可空性。

// 1. filterNotNull() - 过滤null元素
fun processItems(items: List): List {
    return items.filterNotNull().filter { it.length > 3 }
}

// 2. requireNoNulls() - 要求没有null元素
fun strictProcess(items: List): List {
    return items.requireNoNulls()  // 如果有null,抛出IllegalArgumentException
}

// 3. mapNotNull() - 映射并过滤null
val numbers = listOf("1", "2", "three", "4")
val ints: List = numbers.mapNotNull { it.toIntOrNull() }  // [1, 2, 4]

// 4. firstNotNullOf() / firstNotNullOfOrNull() - 查找第一个非空映射结果
val users: List = getUsers()

// 查找第一个有邮箱的用户邮箱
val firstEmail: String? = users.firstNotNullOfOrNull { it.profile?.email }

// 5. 分区处理
val (validItems, invalidItems) = items.partition { it != null }
val valid = validItems.filterIsInstance()  // 需要类型转换

五、进阶模式与技巧

5.1 可空类型的模式匹配

使用when表达式和智能转换进行模式匹配。

// 1. 基本模式匹配
fun describe(value: Any?): String = when (value) {
    null -> "null"
    is String -> "字符串: ${value.length} 个字符"
    is Int -> "整数: $value"
    is List<*> -> &#34;列表: ${value.size} 个元素&#34;
    else -> &#34;其他类型&#34;
}

// 2. 嵌套模式匹配
sealed interface Response
data class Success(val data: Data) : Response
data class Error(val message: String, val cause: Throwable?) : Response

fun handleResponse(response: Response?) {
    when (response) {
        is Success -> {
            val data = response.data
            // data被智能转换为Success类型
            processData(data)
        }
        is Error -> {
            val cause = response.cause
            // cause可能是null
            handleError(response.message, cause)
        }
        null -> {
            handleNullResponse()
        }
    }
}

// 3. 使用解构
data class Point(val x: Int?, val y: Int?)

fun processPoint(point: Point?) {
    when (point) {
        is Point -> {
            val (x, y) = point
            when {
                x == null && y == null -> println(&#34;两个坐标都为空&#34;)
                x == null -> println(&#34;x坐标为空, y=$y&#34;)
                y == null -> println(&#34;y坐标为空, x=$x&#34;)
                else -> println(&#34;坐标: ($x, $y)&#34;)
            }
        }
        null -> println(&#34;点为null&#34;)
    }
}

5.2 sealed class与可空性

密封类是处理有界可空性的强大工具。

// 1. 替代简单可空
sealed class Optional {
    data class Some(val value: T) : Optional()
    object None : Optional()

    companion object {
        fun  of(value: T?): Optional =
            if (value != null) Some(value) else None
    }
}

// 使用
fun processOptional(opt: Optional): String = when (opt) {
    is Optional.Some -> &#34;有值: ${opt.value}&#34;
    Optional.None -> &#34;无值&#34;
}

// 2. 替代多重可空结果
sealed class ApiResult {
    data class Success(val data: T) : ApiResult()
    data class Error(val message: String, val code: Int) : ApiResult()
    object Loading : ApiResult()

    val isLoading: Boolean get() = this is Loading
    val isError: Boolean get() = this is Error
    val isSuccess: Boolean get() = this is Success<*>
}

// 3. 处理嵌套可空性
sealed class Validated {
    data class Valid(val value: T) : Validated()
    data class Invalid(val errors: List) : Validated()

    fun  map(transform: (T) -> R): Validated = when (this) {
        is Valid -> Valid(transform(value))
        is Invalid -> this
    }
}

// 4. 组合多个可空值
fun validateForm(
    name: String?,
    email: String?,
    age: Int?
): Validated {
    val errors = mutableListOf()

    if (name.isNullOrBlank()) errors.add(&#34;姓名不能为空&#34;)
    if (email.isNullOrBlank()) errors.add(&#34;邮箱不能为空&#34;)
    if (age == null || age < 0) errors.add(&#34;年龄无效&#34;)

    return if (errors.isEmpty()) {
        Validated.Valid(Person(name!!, email!!, age!!))
    } else {
        Validated.Invalid(errors)
    }
}

5.3 泛型与可空性结合

泛型类型参数可以约束可空性,提供更精确的类型安全。

// 1. 泛型上界约束
class Container(val value: T)  // T必须是非空类型
// Container  // 编译错误

class NullableContainer(val value: T)  // T可以是可空或非空

// 2. 带可空性约束的泛型函数
fun  processNonNull(value: T): String =
    &#34;处理: ${value.toString()}&#34;

fun  processNullable(value: T?): String? =
    value?.toString()

// 3. 泛型扩展函数
fun  T?.toNullableString(): String? =
    this?.toString()

fun  T.toNonNullString(): String =
    toString()

// 4. 泛型类型投影
interface Repository {
    fun findById(id: String): T?  // 返回可空的T
    fun save(entity: T): T
}

// 5. 泛型可空转换
inline fun  T?.mapNotNull(transform: (T) -> R?): R? =
    this?.let(transform)

inline fun  T?.map(transform: (T) -> R): R? =
    this?.let(transform)

// 6. 泛型集合操作
fun  List.filterNotNullToList(): List =
    filterNotNull()

fun  List.mapNotNullToList(transform: (T) -> R?): List =
    mapNotNull(transform)

5.4 反射与可空类型

通过反射可以检查和操作类型的可空性信息。

import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.typeOf
import kotlin.reflect.full.*

// 1. 检查类型的可空性
fun  checkNullability(value: T) {
    val type = typeOf()
    println(&#34;类型: $type&#34;)
    println(&#34;是否可空: ${type.isMarkedNullable}&#34;)
    println(&#34;实际类型: ${value?.let { it::class.simpleName } ?: &#34;null&#34;}&#34;)
}

// 2. 运行时获取泛型可空性信息
inline fun  getTypeInfo() {
    val type = typeOf()
    println(&#34;类型: $type&#34;)
    println(&#34;是否可空: ${type.isMarkedNullable}&#34;)
    println(&#34;分类: ${type.classifier}&#34;)

    if (type.isMarkedNullable) {
        val underlyingType = type.arguments.firstOrNull()?.type
        println(&#34;基础类型: $underlyingType&#34;)
    }
}

// 使用
getTypeInfo()      // 类型: kotlin.String, 是否可空: false
getTypeInfo()     // 类型: kotlin.String?, 是否可空: true
getTypeInfo?>()  // 嵌套可空性

// 3. 通过反射创建可空实例
fun createInstance(type: KClass<*>): Any? {
    return try {
        type.createInstance()
    } catch (e: Exception) {
        null
    }
}

// 4. 检查属性的可空性
data class User(val name: String, val email: String?)

fun inspectUserClass() {
    User::class.memberProperties.forEach { property ->
        println(&#34;属性: ${property.name}&#34;)
        println(&#34;  类型: ${property.returnType}&#34;)
        println(&#34;  是否可空: ${property.returnType.isMarkedNullable}&#34;)
        println(&#34;  泛型参数: ${property.returnType.arguments}&#34;)
    }
}

// 5. 动态调用可空方法
fun safeReflectiveCall(instance: Any?, methodName: String, vararg args: Any?): Any? {
    return instance?.let { obj ->
        try {
            val method = obj::class.members
                .firstOrNull { it.name == methodName && it.parameters.size == args.size + 1 }
            method?.call(obj, *args)
        } catch (e: Exception) {
            null
        }
    }
}

实际应用示例

// 综合示例:安全的API调用链
class ApiClient {
    suspend fun  safeApiCall(
        call: suspend () -> T,
        errorHandler: (Throwable) -> T? = { null }
    ): Result = try {
        val result = call()
        Result.success(result)
    } catch (e: Exception) {
        val fallback = errorHandler(e)
        if (fallback != null) {
            Result.success(fallback)
        } else {
            Result.failure(e)
        }
    }
}

// 使用
val result: Result = apiClient.safeApiCall(
    call = { api.getUser(userId) },
    errorHandler = {
        logger.error(&#34;API调用失败&#34;, it)
        cachedUserRepository.findById(userId)
    }
)

// 处理结果
when (result) {
    is Result.Success -> {
        val user = result.value
        updateUI(user)
    }
    is Result.Failure -> {
        showError(result.exception)
    }
}

最佳实践总结

  1. 优先使用非空类型,只在必要时使用可空
  2. 利用扩展函数封装可空处理逻辑
  3. 正确使用作用域函数处理可空链
  4. 显式处理平台类型,不要依赖隐式推断
  5. 清晰表达集合的可空性层次
  6. 使用密封类替代复杂的可空状态
  7. 利用泛型约束增强类型安全
  8. 反射处理可空性时保持谨慎

通过掌握这些高级技巧,可以构建更健壮、可维护的Kotlin应用程序,充分利用类型系统的优势。