星号投影与 * 的使用时机
对未知泛型类型应用星号投影,并理解其限制。
星号投影与 * 的使用时机 是 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 反馈 — 无需本地设置。
此课程中的所有课时
- 使用 where 的泛型函数与类型约束
- 声明处型变:in 与 out
- 星号投影与 * 的使用时机
- 类型擦除与 reified 类型参数