第5章:Kotlin类型系统(Kotlin Type System)
一、导读
1.1 本章学习目标
第5章深入讲解 Kotlin 的类型系统——这是 Kotlin 相比 Java 最显著的改进之一。Kotlin 的类型系统不仅提供了空安全,还在泛型、型变、星投影等方面做出了创新性的设计。
本章的核心学习目标包括:
- 深入理解空类型系统:可空类型、安全调用、平台类型、类型别名
- 掌握泛型的完整用法:泛型函数、泛型类、泛型约束
- 理解型变(Variance):声明处型变(
in/out)和使用处型变(星投影) - 掌握类型擦除与 reified 类型参数
- 学会使用类型别名(typealias)提高代码可读性
1.2 为什么本章如此重要
类型系统是 Kotlin 的核心竞争力。Java 的类型系统经过几十年的发展,积累了许多历史包袱——协变数组的类型安全问题、缺乏空类型区分、泛型的类型擦除限制等。Kotlin 在设计类型系统时,既保留了与 Java 的兼容性,又引入了现代化的改进。
空类型系统让你在编译期就发现潜在的空指针异常,而不是在生产环境中。泛型的型变系统让集合操作的类型安全性更加精确。reified 类型参数突破了类型擦除的限制,让你可以在泛型函数中使用 is 检查。
1.3 核心问题引导
为什么 Kotlin 的 List<String> 不是 List<Any> 的子类型?这与 Java 有什么不同?
out 和 in 关键字在泛型中的作用是什么?它们与 Java 的通配符 ? extends 和 ? super 有什么对应关系?
什么是星投影(star projection)?在什么场景下需要使用它?
reified 类型参数是如何突破类型擦除限制的?它有什么限制?
平台类型(platform type)String! 的含义是什么?为什么 Kotlin 不对 Java 代码强制空检查?
二、核心概念详解
2.1 空类型系统深入
2.1.1 可空类型的安全操作
Kotlin 提供了丰富的空安全操作符:
val name: String? = getName()
// 安全调用 ?.
val length: Int? = name?.length
// Elvis 操作符 ?:
val lengthOrDefault: Int = name?.length ?: 0
// 安全调用链
val city: String? = person?.company?.address?.city
// let + 安全调用
name?.let { n ->
println("Name is $n with length ${n.length}")
}
// 可空类型的集合操作
val list: List<String?>? = getList()
val nonNullCount = list?.count { it != null } ?: 02.1.2 平台类型(Platform Type)
当 Kotlin 调用 Java 代码时,Java 的引用类型被映射为"平台类型" T!:
// Java 方法: public String getName() { return name; }
// 在 Kotlin 中,返回类型是 String!(平台类型)
val name: String! = javaObject.getName()
// 平台类型可以当作可空或非空使用
val nonNull: String = javaObject.getName() // 如果实际为 null,运行时 NPE
val nullable: String? = javaObject.getName() // 总是安全的平台类型是 Kotlin 对 Java 可空性未知的妥协。Kotlin 不会对 Java 返回值强制空检查(因为那会破坏互操作性),但会在你将其赋值给非空类型时插入运行时检查。
2.1.3 类型别名(Type Alias)
typealias UserId = Long
typealias UserMap = Map<UserId, String>
typealias Predicate<T> = (T) -> Boolean
fun findUser(id: UserId): String? = userMap[id]
// 类型别名不是新类型,只是语法糖
// UserId 和 Long 是完全相同的类型
val id: UserId = 42L
val longId: Long = id // 完全合法2.2 泛型
2.2.1 泛型函数
fun <T> List<T>.secondOrNull(): T? = when {
size < 2 -> null
else -> this[1]
}
fun <T : Comparable<T>> List<T>.sorted(): List<T> {
return this.toMutableList().apply { sort() }
}2.2.2 泛型类与类型约束
// 单个上界
class Repository<T : BaseEntity> {
fun findById(id: Long): T? { /* ... */ }
}
// 多个上界(使用 where 子句)
fun <T> copyIfAssignable(item: T)
where T : Comparable<T>, T : Serializable {
// item 既是 Comparable 又是 Serializable
}
// 泛型类的约束
class NumberBox<T : Number> {
fun sum(a: T, b: T): Double = a.toDouble() + b.toDouble()
}2.2.3 类型擦除与 reified
Kotlin 和 Java 一样,泛型在运行时会被擦除。但 Kotlin 的 inline 函数可以使用 reified 类型参数来突破这个限制:
// 没有 reified:无法使用 is 检查泛型类型
// fun <T> isInstance(obj: Any): Boolean {
// return obj is T // 编译错误!
// }
// 使用 reified
inline fun <reified T> isInstance(obj: Any): Boolean {
return obj is T // OK!
}
// 实际应用
inline fun <reified T> Gson.fromJson(json: String): T {
return this.fromJson(json, T::class.java)
}
val user: User = gson.fromJson(jsonString)
// 编译器自动推断 T 为 User,reified 让运行时能获取 User::class.javareified 的限制:
- 只能在
inline函数中使用 - 不能使用
reified类(只有函数可以) - 不能创建类型参数的实例(
T()仍然不合法)
2.3 型变(Variance)
2.3.1 为什么需要型变
// 这个函数能编译吗?
fun addInt(list: MutableList<Any>) {
list.add(42)
}
val strings: MutableList<String> = mutableListOf("a", "b")
addInt(strings) // 如果允许,strings 中就有了 Int!类型不安全!Kotlin 的 MutableList<Int> 不是 MutableList<Any> 的子类型,这是正确的——因为 MutableList 是可变的,允许向其中添加元素。
2.3.2 声明处型变
Kotlin 使用 out 和 in 关键字在声明处指定型变:
// out: 生产者,只能产出 T
interface Producer<out T> {
fun produce(): T
// fun consume(item: T) // 编译错误!out 参数位置不能使用 T
}
// in: 消费者,只能消费 T
interface Consumer<in T> {
fun consume(item: T)
// fun produce(): T // 编译错误!in 参数位置不能返回 T
}
// 实际应用
interface List<out E> { // List 是 E 的生产者
fun get(index: Int): E // E 在 out 位置(返回值)
// fun set(index: Int, element: E) // List 没有这个方法
}
interface MutableList<E> : List<E> { // MutableList 不变
fun add(element: E) // E 在 in 位置
fun get(index: Int): E // E 在 out 位置
}2.3.3 使用处型变
类似于 Java 的通配符,Kotlin 支持在使用处指定型变:
// Java: void addAll(Collection<? extends E> c)
// Kotlin:
fun <T> MutableList<T>.addAll(elements: Collection<T>): Boolean
// 使用处型变
fun <T> copy(from: List<T>, to: MutableList<T>) {
// ...
}
// 更灵活:使用 in/out 投影
fun <T> copy(from: List<out T>, to: MutableList<in T>) {
// from 是 T 的生产者(可以读取 T)
// to 是 T 的消费者(可以写入 T)
}2.3.4 星投影(Star Projection)
当不知道泛型参数的具体类型时,可以使用星投影 *:
// List<*> 等价于 List<out Any?>
val list: List<*> = listOf(1, "hello", true)
// 只能读取,不能写入(因为不知道具体类型)
val first: Any? = list[0] // OK
// list.add(42) // 编译错误
// 实际应用场景
fun printFirst(list: List<*>) {
if (list.isNotEmpty()) {
println(list[0])
}
}2.4 类型系统的高级特性
2.4.1 Nothing 类型
Nothing 是所有类型的子类型,表示"没有值":
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
// Nothing 作为类型参数
val emptyList: List<Nothing> = emptyList()
// List<Nothing> 是 List<T> 的子类型(因为 List 是 out 的)
// 与 Elvis 结合
val name: String = nullableName ?: fail("Name is required")
// fail() 返回 Nothing,编译器知道 else 分支不会产生值2.4.2 具体化类型的工厂
inline fun <reified T : Enum<T>> enumValueOfOrNull(name: String): T? {
return enumValues<T>().find { it.name == name }
}
enum class Color { RED, GREEN, BLUE }
val color = enumValueOfOrNull<Color>("RED") // Color.RED
val invalid = enumValueOfOrNull<Color>("YELLOW") // null三、实战案例
3.1 案例一:类型安全的构建器模式
// 使用泛型和类型约束实现类型安全的构建器
class Builder<T> {
private val values = mutableMapOf<String, Any?>()
infix fun String.to(value: Any?) {
values[this] = value
}
inline fun <reified V> get(key: String): V? {
return values[key] as? V
}
fun build(): Map<String, Any?> = values.toMap()
}
fun <T> build(block: Builder<T>.() -> Unit): Map<String, Any?> {
return Builder<T>().apply(block).build()
}
fun main() {
val config = build<Any> {
"host" to "localhost"
"port" to 8080
"debug" to true
}
println(config)
}3.2 案例二:泛型 Result 类型
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()
fun <R> map(transform: (T) -> R): Result<R> = when (this) {
is Success -> Success(transform(data))
is Error -> this
Loading -> this
}
fun <R> flatMap(transform: (T) -> Result<R>): Result<R> = when (this) {
is Success -> transform(data)
is Error -> this
Loading -> this
}
fun getOrElse(default: @UnsafeVariance T): T = when (this) {
is Success -> data
else -> default
}
companion object {
inline fun <reified T> of(block: () -> T): Result<T> {
return try {
Success(block())
} catch (e: Exception) {
Error(e)
}
}
}
}
fun main() {
val result = Result.of { "42".toInt() }
val mapped = result
.map { it * 2 }
.map { "Result: $it" }
when (mapped) {
is Result.Success -> println(mapped.data)
is Result.Error -> println("Error: ${mapped.exception.message}")
Result.Loading -> println("Loading...")
}
}3.3 案例三:使用 reified 实现类型安全的偏好存储
import android.content.SharedPreferences
inline fun <reified T> SharedPreferences.get(key: String, default: T): T {
return when (T::class) {
String::class -> getString(key, default as String) as T
Int::class -> getInt(key, default as Int) as T
Long::class -> getLong(key, default as Long) as T
Boolean::class -> getBoolean(key, default as Boolean) as T
Float::class -> getFloat(key, default as Float) as T
else -> throw IllegalArgumentException("Unsupported type: ${T::class}")
}
}
inline fun <reified T> SharedPreferences.put(key: String, value: T) {
edit().apply {
when (T::class) {
String::class -> putString(key, value as String)
Int::class -> putInt(key, value as Int)
Long::class -> putLong(key, value as Long)
Boolean::class -> putBoolean(key, value as Boolean)
Float::class -> putFloat(key, value as Float)
else -> throw IllegalArgumentException("Unsupported type: ${T::class}")
}
}.apply()
}
// 使用
// val name: String = prefs.get("name", "")
// prefs.put("name", "Alice")
// val count: Int = prefs.get("count", 0)四、本章小结
第5章深入讲解了 Kotlin 的类型系统:
空类型系统:String 和 String? 的区分是 Kotlin 安全性的基石。平台类型处理 Java 互操作中的可空性不确定性。
泛型:Kotlin 的泛型支持类型约束(where 子句)、reified 类型参数(突破类型擦除)。
型变:声明处型变(out/in)和使用处型变(星投影)提供了精确的类型安全性。
Nothing 类型:所有类型的子类型,用于表示"永不返回"的函数。
五、思考与练习
思考题
为什么 Kotlin 的 Array<String> 不是 Array<Any> 的子类型,但 Java 的 String[] 是 Object[] 的子类型?哪种设计更安全?
reified 类型参数的底层实现原理是什么?为什么它只能用于 inline 函数?
星投影 List<*> 和 List<Any?> 有什么区别?在什么场景下应该使用星投影?
平台类型的设计是一个权衡——它牺牲了编译期安全性来换取互操作性。你认为这个权衡合理吗?
编程练习
实现一个泛型函数 merge,接收两个 Map<String, T> 参数,返回合并后的 Map。
使用 reified 类型参数实现一个 deserialize<T> 函数,从 JSON 字符串反序列化为指定类型。
设计一个类型安全的事件总线,使用泛型确保事件类型和监听器类型的匹配。