Kotlin 泛型 型变、逆变、星号投影、reified、泛型上限

本文详细介绍了Kotlin中泛型的使用,包括定义泛型类、类型变、逆变、调用处型变和逆变、星号投影、泛型函数、reified修饰泛型以及泛型上限。通过实例展示了泛型在不同场景下的应用,帮助开发者更好地理解和利用Kotlin泛型提高代码的灵活性和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. kotlin 定义泛型类


// 定义泛型类
open class GenericDemo<T>(open var field: T? = null)

// 继承泛型类
class SubGeneric(param: Float) : GenericDemo<Float>() {
    override var field: Float? = param
}

fun main() {
 	val demo1 = GenericDemo<String>("demo1")
    val demo2 = GenericDemo<Int>(2)
    val demo3 = GenericDemo(true)
    val demo4 = SubGeneric(3.14f)

    println("${demo1.field}, ${demo2.field}, ${demo3.field}, ${demo4.field}") // demo1, 2, true, 3.14
}


2. kotlin 定义类型变

kotlin 使用 out 定义型变。即向外提供的类型,不能向其写入,同 java 的通配符上限


// name 只能用 val 声明,否则会有 setter 方法向其写入
class Fruit<out T>(private val name: T) {
    fun getFruitName(): T {
        return name
    }
}

fun main() {
	val apple = Fruit("apple")
	
	// Fruit<String> 可以被赋值给 Fruit<Any>
	val fruit: Fruit<Any> = apple
	println(fruit.getFruitName())
}


3. kotlin 定义类逆变

kotlin 使用 in 定义逆变。即向里写入的类型,不能向外提供,同 java 通配符下限


class Reverse<in T> {
    fun setValue(value: T) {
        println("value: $value")
    }
}

fun main() {
	var reverse1 = Reverse<Int>()
	val reverse2 = Reverse<Number>()
	reverse2.setValue(3.14f) // value: 3.14
	
	// 可以将 Reverse<Number> 赋值给 Reverse<Int>
	reverse1 = reverse2
}


4. kotlin 定义调用处型变

在调用原本不支持型变的类时,如 ArrayList,可以在使用时用 out 修饰泛型支持型变


fun main() {
 	var arrNumber: ArrayList<out Number> = arrayListOf(1, 0.2, 3f)
    val arrayInt: ArrayList<Int> = arrayListOf(1, 2, 3)
    
    // arrNumber 可以接受元素为 Int 类型集合
    arrNumber = arrayInt
    println(arrNumber) // [1, 2, 3]
}


5. kotlin 定义调用处逆变

在调用原本不支持型变的类时,如 ArrayList,可以在使用时用 in 修饰泛型支持逆变


fun main() {
 	var arrInt: ArrayList<in Int> = arrayListOf(1, 2, 3)
    val arrNum: ArrayList<Number> = arrayListOf(1, 0.2, 3f)
    
    // arrInt 可以接受元素为 Number 类型集合
    arrInt = arrNum
    println(arrInt) // [1, 0.2, 3.0]
}


6. kotlin 星号投影


fun main() {
	// val list : ArrayList = arrayListOf(1, 2, 3) // 编译错误
    // 使用 * 号,等价于 out Any? 可接受任意类型的元素
    val list1: ArrayList<*> = arrayListOf(1, 0.2, "3")
    val list2: ArrayList<out Any?> = arrayListOf(1, 0.2, "3")
    println("list1: $list1, list2: $list2") // list1: [1, 0.2, 3], list2: [1, 0.2, 3]
}


7. kotlin 定义泛型函数


fun <T> printSelf(param: T) {
    println(param)
}  


8. kotlin 使用 reified 修饰泛型

使用 reified 修饰 泛型后,可以将泛型变成具体化的类型(泛型具型化),从而可以使用其相关方法


inline fun <reified T> printSelfName() {
    val name = T::class.simpleName
    println("class simple name: $name")
}

fun main() {
 	printSelfName<Int>() // class simple name: Int
    printSelfName<String>() // class simple name: String
}


9. kotlin 定义泛型上限


// 定义类泛型上限
class Container<T : Number>(var param: T)

// 定义方法泛型上限
fun <T : Number> sum(vararg param: T) {
    var sum = 0.0
    param.forEach {
        sum += it.toDouble()
    }
    println("求和: $sum")
}
    
fun main() {
    val c1 = Container<Int>(1)
    val c3 = Container<Long>(3L)
    val c4 = Container<Float>(3.14f)
    
	sum(1, 2f, 3.0) // 求和: 6.0
}

// 类泛型指定多个上限
class Test<T>() where T : Callable<T>, T : Runnable {}

// 方法泛型指定多个上限
fun <T> func(param: T) where T : Callable<T>, T : Runnable {}
    

附 Github 源码:

TestGeneric.kt

### Kotlin 实化(Reified Generics)详解 #### 什么是实化? 在 Kotlin 中,默认情况下,由于 JVM 的 **擦除** 特性,在运行时无法获取的具体类信息。然而,通过使用 `inline` 函数以及 `reified` 关键字,可以使参数在运行时保留其具体类信息[^4]。 这种特性被称为 **实化 (reified generics)**,它使得开发者能够在运行时访问的实际类参数。 --- #### 如何使用实化? 要使实化生效,需满足以下条件: 1. 方法必须是一个内联函数 (`inline`)。 2. 参数前需要加上 `reified` 关键字。 下面是一个简单的例子: ```kotlin inline fun <reified T> isA(value: Any): Boolean { return value is T } fun main() { println(isA<String>("Hello")) // 输出 true println(isA<Int>("Hello")) // 输出 false } ``` 在这个例子中,`isA<T>` 函数能够判断传入的对象是否属于指定的类 `T`。这是因为在运行时,`T` 实际上被替换为了具体的类,而不是像普通那样被擦除了。 --- #### 运行时类的访问 借助实化,我们还可以直接操作类本身的信息。例如,可以通过反射获取类名或其他元数据: ```kotlin inline fun <reified T> getTypeName(): String { return T::class.java.name } fun main() { println(getTypeName<String>()) // 输出 java.lang.String println(getTypeName<Int>()) // 输出 java.lang.Integer } ``` 在这里,`T::class.java` 提供了对实际类的运行时访问能力。 --- #### 使用场景分析 ##### 场景 1:动态类检查 当需要频繁执行类检查或转换时,实化非常有用。例如: ```kotlin inline fun <reified T> List<*>.filterIsInstance(): List<T> = this.filter { it is T }.map { it as T } fun main() { val mixedList = listOf<Any>(1, "two", 3, "four") val stringsOnly = mixedList.filterIsInstance<String>() println(stringsOnly) // 输出 ["two", "four"] } ``` 上述代码展示了如何过滤列表中的特定类元素。 --- ##### 场景 2:简化工厂模式 假设有一个通用的工厂方法用于创建对象实例,可以利用实化减少冗余代码: ```kotlin inline fun <reified T : Any> createInstance(vararg params: Any?): T? { try { val constructor = T::class.constructors.firstOrNull() ?: throw IllegalArgumentException("No suitable constructor found.") return when (params.size) { 0 -> constructor.call() else -> constructor.call(*params) } } catch (e: Exception) { e.printStackTrace() return null } } data class Person(val name: String) fun main() { val person = createInstance<Person>("Alice") println(person?.name) // 输出 Alice } ``` 此示例演示了如何基于实化动态调用不同类的构造函数。 --- ##### 场景 3:日志记录与调试工具 在开发过程中,有时需要打印量的类以便于调试。此时也可以应用实化技术: ```kotlin inline fun <reified T> logType(variable: T?) { println("${variable?.javaClass?.simpleName} has a value of $variable") } fun main() { logType(42) // 输出 Integer has a value of 42 logType("Kotlin") // 输出 String has a value of Kotlin logType(null) // 输出 null has a value of null } ``` 这段代码实现了自动检测并显示任意量及其对应类的简单功能。 --- #### 注意事项 尽管实化提供了强大的功能,但也存在一定的局限性: - 只能应用于内联函数(`inline`); - 对性能可能有一定影响,因为编译器会在每次调用位置展开该函数体; - 如果滥用可能导致代码膨胀问题。 因此建议仅在确实需要运行时类信息的情况下才考虑采用这种方式。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值