Kotlin: Kotlin泛型详解

最后更新:2026-08-26

Kotlin 的泛型比 Java 更安全——声明处型变(declaration-site variance)让 Charlie 在定义 时就决定协变,而不是在每个使用点重复写 。

1. 你将学到


2. 一个架构师的真实故事

(1) 痛点:Java 泛型通配符地狱

Charlie 在 Java 中写 表示只读订单列表, 表示只写列表。每次使用都要重复声明通配符,代码可读性极差。

(2) Kotlin 声明处型变的解法

JAVA
// Java: repeat wildcard at every use site
void process(List<? extends Order> orders) { }
void addAll(List<? super Order> target) { }

// Kotlin: declare variance once at definition site
class Repository<out T : Order> {  // Covariant at definition
    fun getAll(): List`<T>`  // T only appears in 'out' position
}
// No wildcards needed at use site!

声明处型变:定义一次,使用无限次。Java 使用处型变:每次使用都要重复。


3. 泛型基础

(1) 泛型类与函数

KOTLIN
// Generic class
class Box`<T>`(val value: T) {
    fun unwrap(): T = value
}

val intBox = Box(42)         // T inferred as Int
val strBox = Box("Hello")   // T inferred as String

// Generic function
fun `<T>` singletonList(item: T): List`<T>` = listOf(item)

// Generic with type constraint
fun <T : Comparable`<T>`> maxOf(a: T, b: T): T = if (a > b) a else b

// Multiple constraints
fun `<T>` process(item: T) where T : CharSequence, T : Comparable`<T>` {
    // T is both CharSequence and Comparable
}

(2) 型变问题:为什么需要 out/in

KOTLIN
// This seems reasonable but is a TYPE ERROR
val strings: List`<String>` = listOf("Hello", "World")
// val objects: List`<Any>` = strings  // ERROR in Kotlin (OK in Java arrays)

// Why? If allowed:
// objects.add(42)  // Would corrupt the String list!
// strings[0].length  // ClassCastException at runtime

4. 声明处型变

(1) out(协变):只生产,不消费

KOTLIN
// Producer: T only appears in 'out' position (return type)
interface Repository<out T> {
    fun findById(id: String): T
    fun findAll(): List`<T>`
    // fun save(item: T)  // ERROR: T in 'in' position
}

// Now this works:
val orderRepo: Repository`<Order>` = OrderRepositoryImpl()
val anyRepo: Repository`<Any>` = orderRepo  // OK! Covariant

(2) in(逆变):只消费,不生产

KOTLIN
// Consumer: T only appears in 'in' position (parameter type)
interface Filter<in T> {
    fun test(item: T): Boolean
    fun filter(items: List`<T>`): List`<T>`
    // fun getResult(): T  // ERROR: T in 'out' position
}

// Now this works:
val orderFilter: Filter`<Order>` = object : Filter`<Order>` {
    override fun test(item: Order) = item.total > 1_000
    override fun filter(items: List`<Order>`) = items.filter { test(it) }
}
val anyFilter: Filter`<Any>` = orderFilter  // Cannot assign - contravariant direction

(3) 型变规则速查

修饰符 含义 T 出现位置 子类型方向 类比
协变 只在返回位置 是 的子类型 Java
逆变 只在参数位置 是 的子类型 Java
无修饰 不变 任意位置 无子类型关系 Java

(4) 型变流向图

100%
flowchart TD
    subgraph "Covariant (out)"
        A1[Producer of Sub] -->|"IS-A"| A2[Producer of Base]
    end
    subgraph "Contravariant (in)"
        B1[Consumer of Base] -->|"IS-A"| B2[Consumer of Sub]
    end
    subgraph "Invariant"
        C1[Box of Sub] -.->|"NOT IS-A"| C2[Box of Base]
    end

5. 使用处型变(类型投影)

当无法在声明处决定型变时,可以在使用时投射。

KOTLIN
// Use-site projection: when declaration-site is invariant
fun copyFrom(source: Array<out Any>, dest: Array<in Any>) {
    for (i in source.indices) {
        dest[i] = source[i]
    }
}

// Equivalent to Java:
// void copyFrom(Object[] source, Object[] dest)

(1) 使用处型变场景对比

场景 Java Kotlin
只读参数
只写参数
定义处协变 不支持
定义处逆变 不支持

6. 星投影

当泛型类型未知时,用星投影安全访问。

KOTLIN
// List<*> = list of unknown type (safe to read, not write)
val unknownList: List<*> = listOf("Hello", 42, 3.14)
val first: Any? = unknownList[0]  // OK - returns Any?
// unknownList.add("New")  // ERROR - can't write

// Comparison
fun printSize(list: List<*>) {
    println("Size: ${list.size}")  // OK - size doesn't depend on T
}

// Array<*> is different
val array: Array<*> = arrayOf("A", "B")
// array[0] = "C"  // ERROR - can't write to Array<*>
val elem: Any? = array[0]  // OK - can read

(1) 星投影规则

类型 星投影等价 可读 可写
✅ (Any?)
✅ (Any?)
✅ (Any?)
✅ (Nothing = 不可写)

7. reified 类型参数

(1) 类型擦除与 reified

KOTLIN
// Normal generic: type is erased at runtime
fun `<T>` isA(value: Any): Boolean = value is T  // ERROR: Cannot check for instance of erased type

// reified: inline function preserves type at call site
inline fun <reified T> isA(value: Any): Boolean = value is T  // OK!

// Usage
isA`<String>`("Hello")  // true
isA`<Int>`(42)          // true
isA`<Int>`("42")        // false

// Practical: filter by type
inline fun <reified T> Iterable<*>.filterIsInstance(): List`<T>` {
    return filter { it is T }.map { it as T }
}

(2) reified 实用场景

KOTLIN
// JSON deserialization without Class parameter
inline fun <reified T> Json.decodeFromString(json: String): T

// Instead of:
// json.decodeFromString(Order::class.java, jsonString)

// Just:
// json.decodeFromString`<Order>`(jsonString)

8. 完整示例:类型安全的订单仓库

KOTLIN
// ============================================
// OrderProcessor - Type-Safe Repository with Generics
// Feature: Generic repository with variance
// ============================================

open class Order(val id: String, val total: Double)
class BulkOrder(id: String, total: Double, val minQty: Int) : Order(id, total)
class PriorityOrder(id: String, total: Double, val priority: String) : Order(id, total)

// Covariant repository: only produces T
interface ReadOnlyRepository<out T : Order> {
    fun findById(id: String): T?
    fun findAll(): List`<T>`
}

// Contravariant writer: only consumes T
interface WriteRepository<in T : Order> {
    fun save(item: T)
    fun saveAll(items: List`<T>`)
}

// Full repository: invariant (both reads and writes)
interface OrderRepository<T : Order> : ReadOnlyRepository`<T>`, WriteRepository`<T>`

// In-memory implementation
class InMemoryOrderRepository<T : Order> : OrderRepository`<T>` {
    private val storage = mutableMapOf<String, T>()

    override fun findById(id: String): T? = storage[id]
    override fun findAll(): List`<T>` = storage.values.toList()
    override fun save(item: T) { storage[item.id] = item }
    override fun saveAll(items: List`<T>`) { items.forEach { save(it) } }
}

// Generic filter with contravariance
class OrderFilter<in T : Order>(private val predicate: (T) -> Boolean) {
    fun test(item: T): Boolean = predicate(item)
    fun filter(items: List`<T>`): List`<T>` = items.filter(predicate)
}

inline fun <reified T : Order> List`<Order>`.filterByType(): List`<T>` {
    return this.filterIsInstance`<T>`()
}

fun main() {
    val orders = listOf(
        Order("ORD-001", 299.99),
        BulkOrder("ORD-002", 5_000.00, 100),
        PriorityOrder("ORD-003", 15_000.00, "VIP"),
        BulkOrder("ORD-004", 8_000.00, 50),
        PriorityOrder("ORD-005", 2_500.00, "GOLD")
    )

    // Covariant: BulkOrder repo assignable to Order repo
    val bulkRepo: ReadOnlyRepository`<BulkOrder>` = InMemoryOrderRepository()
    val orderRepo: ReadOnlyRepository`<Order>` = bulkRepo  // OK: covariant

    // reified: filter by runtime type
    val bulkOrders: List`<BulkOrder>` = orders.filterByType`<BulkOrder>`()
    println("Bulk orders: ${bulkOrders.map { it.id }}")

    val priorityOrders: List`<PriorityOrder>` = orders.filterByType`<PriorityOrder>`()
    println("Priority orders: ${priorityOrders.map { it.id }}")

    // Generic filter
    val highValueFilter = OrderFilter`<Order>` { it.total > 1_000 }
    val highValue = highValueFilter.filter(orders)
    println("High value orders: ${highValue.map { it.id }}")
}

输出:

TEXT 📖 仅展示
Bulk orders: [ORD-002, ORD-004]
Priority orders: [ORD-003, ORD-005]
High value orders: [ORD-002, ORD-003, ORD-004, ORD-005]

❓ 常见问题

Q out 和 in 可以同时用在同一个类型参数上吗?
A 不能。一个类型参数只能是 out、in 或不变之一。如果 T 同时出现在输入和输出位置,只能是不变。
Q Kotlin 的泛型在运行时还存在吗?
A 大部分情况下被擦除了(和 Java 一样)。但 inline + reified 可以保留类型信息。内联函数在调用处展开,类型信息在编译时可用。
Q 星投影 和 有什么区别?
A 表示"元素类型未知",只能读为 Any?; 表示"元素类型是 Any",可以添加任何值。前者更安全。
Q 什么时候用声明处型变,什么时候用使用处?
A 优先声明处型变——在设计 API 时就确定 T 是只读(out)还是只写(in)。使用处型变是最后的手段。
Q reified 有性能开销吗?
A 没有额外运行时开销。因为 inline 函数在编译时展开,reified 类型检查在编译期完成,运行时无额外操作。
Q 泛型上界约束 和 子句有什么区别?
A 单个约束用 简写;多个约束必须用 语法。

📖 小节


📝 作业

  1. 基础题(难度⭐):实现一个 <T>,测试不同类型参数的赋值兼容性。提示: 能赋给 吗?
  2. 进阶题(难度⭐⭐):定义 和 ,演示协变和逆变的赋值关系。提示: →
  3. 挑战题(难度⭐⭐⭐):实现一个 <Any> 函数,先过滤类型再转换。提示:<T>

← 上一课 | 下一课 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏