Kotlin Academy · 课时

星号投影与 * 的使用时机

对未知泛型类型应用星号投影,并理解其限制。

第 3 / 4 课13 个步骤

星号投影与 * 的使用时机 是 CoddyKit 上的免费 Kotlin Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Kotlin Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Kotlin Academy 课程共包含 4 节课。

什么是星号投影

星号投影(*)是 Kotlin 表示“类型参数未知的泛型类型”的方式。它是 Java 原始类型或无界通配符 <?> 的安全等价形式。

fun printList(list: List<*>) {
    for (item in list) println(item)
}
fun main() {
    printList(listOf(1, 2, 3))
    printList(listOf("a", "b"))
    printList(listOf(true, 42, "mixed"))
}

从星号投影类型中读取

从 List<*> 中读取元素时,元素类型可以视为 Any?。由于实际类型未知,您不能添加元素。

fun process(list: List<*>) {
    val first: Any? = list.firstOrNull()
    println("First: $first, Size: ${list.size}")
    // list.add("x") // compile error: can't write to List<*>
}
fun main() {
    process(listOf(10, 20, 30))
}

星号投影与 out Any 的区别

List<*> 与 List<out Any?> 等价。星号投影是带上界的协变投影的简写形式。

fun sumStars(list: List<*>): Int = list.size
fun sumOut(list: List<out Any?>): Int = list.size
fun main() {
    val l = listOf(1, "two", true)
    println(sumStars(l))  // 3
    println(sumOut(l))    // 3
}

函数参数中的星号投影

当您需要接收任意泛型变体,但只调用那些不要求知道 T 的方法时,可以在函数签名中使用 *。

fun containerInfo(map: Map<*, *>): String {
    return "keys=${map.keys.size}, values=${map.values.size}"
}
fun main() {
    val m = mapOf(1 to "one", 2 to "two")
    println(containerInfo(m))
}

星号投影与 MutableList

对于 MutableList<*>,您可以将元素读取为 Any?,但完全不能写入——编译器会阻止这种操作。

fun clearFirstElement(list: MutableList<*>) {
    if (list.isNotEmpty()) {
        list.removeAt(0)  // OK: remove by index doesn't need T
        // list.add("x") // Error: can't write unknown type
    }
}
fun main() {
    val m = mutableListOf(1, 2, 3)
    clearFirstElement(m)
    println(m) // [2, 3]
}

带星号投影的类

在运行时检查泛型类的类型时,请使用星号投影,因为类型参数会被擦除。

fun isListOfSomething(obj: Any): Boolean {
    return obj is List<*>  // OK with star
    // obj is List<String> // Warning: cannot check erased type
}
fun main() {
    println(isListOfSomething(listOf(1, 2)))  // true
    println(isListOfSomething("not a list"))  // false
}

多个星号参数

处理未知类型的二元组或映射时,一个函数可以拥有多个使用星号投影的参数。

fun describeMap(map: Map<*, *>) {
    map.forEach { (k, v) ->
        println("${k?.javaClass?.simpleName} -> ${v?.javaClass?.simpleName}")
    }
}
fun main() {
    describeMap(mapOf("key" to 42, 1 to true))
}

不应使用星号投影的情况

当您需要向集合写入,或可以改用有界类型参数时,应避免使用星号投影——有界类型参数能够提供更多信息。

// Bad: loses type information
fun addToStar(list: MutableList<*>, item: Any?) {
    // list.add(item) // compile error
}
// Better: use bounded type parameter
fun <T : Any> addTyped(list: MutableList<T>, item: T) {
    list.add(item)
}
fun main() {
    val l = mutableListOf("a")
    addTyped(l, "b")
    println(l)
}

星号投影与类型检查和转换

使用星号投影对泛型类执行 instanceof 检查,然后转换为具体类型以进行实际操作。

fun handleResponse(response: Any) {
    if (response is List<*>) {
        val list = response as List<*>
        println("List with ${list.size} elements")
        val first = list.firstOrNull()
        if (first is String) println("First string: $first")
    }
}
fun main() {
    handleResponse(listOf("hello", "world"))
    handleResponse(42)
}

实践:泛型事件分发器

一种可以接收任意 Handler<*>,同时安全地分发带类型事件的分发器。

interface Handler<in T> { fun handle(event: T) }
class LogHandler : Handler<Any> {
    override fun handle(event: Any) = println("Event: $event")
}
fun dispatch(handler: Handler<*>, event: Any) {
    @Suppress("UNCHECKED_CAST")
    (handler as Handler<Any>).handle(event)
}
fun main() {
    dispatch(LogHandler(), "click")
    dispatch(LogHandler(), 42)
}

具体化上下文中的星号投影

星号投影在反射和序列化代码中尤其常见,因为这些代码会通过 KClass<*> 检查运行时类型。

fun printClassName(klass: kotlin.reflect.KClass<*>) {
    println(klass.simpleName)
}
fun main() {
    printClassName(String::class)     // String
    printClassName(List::class)       // List
    printClassName(Int::class)        // Int
}

快速检查

从 List<*> 中读取元素时,得到的是什么类型?

回顾

星号投影(*)允许以只读方式接收任意泛型变体。请将其用于类型检查、泛型工具和反射。当您需要向集合写入,或有界类型参数能够提供更好的类型安全性时,应避免使用它。

免费开始

用 AI 导师学习 Kotlin — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
51
课程
203

常见问题解答

「星号投影与 * 的使用时机」课时是免费的吗?

是的 — 「星号投影与 * 的使用时机」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Kotlin Academy 课程的其余内容,请升级到 CoddyKit PRO。 Kotlin Academy 课程共包含 4 节课。

「星号投影与 * 的使用时机」这节课中我会学到什么?

对未知泛型类型应用星号投影,并理解其限制。 你通过在浏览器中直接运行的动手代码来练习 Kotlin Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Kotlin Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Kotlin Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「星号投影与 * 的使用时机」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Kotlin Academy 课中编写并运行代码吗?

能。每节 Kotlin Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 where 的泛型函数与类型约束
  2. 声明处型变:in 与 out
  3. 星号投影与 * 的使用时机
  4. 类型擦除与 reified 类型参数
← 返回 Kotlin Academy