0. 引子
前几天推送了一篇文章:你绝对想不到 Kotlin 泛型给反射留下了怎样的坑!,受到一位朋友的评论的启发,这篇文章就承接前文,探讨一下 T::class
和 this::class
区别。java
感谢这位朋友的支持!微信
1. 类继承的例子
咱们先看个例子:ide
open class Person(val name: String, val age: Int)
class Coder(val language: String, age: Int, name: String): Person(name, age)
inline fun <reified T : Any> T.description()
= T::class.memberProperties
.map {
"${it.name}: ${it.get(this@description)}"
}
.joinToString(separator = ";")
这实际上就是前面文章的例子,我将 this::class.memberProperties
改为了 T::class.memberProperties
,同时,我为 Person
实现了一个子类 Coder
,它多了一个 language
字段,表示它编写代码使用的程序语言。测试
测试程序以下:ui
fun main(args: Array<String>) {
val person = Coder("kotlin", 30, "benny")
println(person.description())
}
这时候输出的结果没有问题:this
language: kotlin;age: 30;name: benny
那么稍微修改一下测试程序:url
fun main(args: Array<String>) {
val person: Person = Coder("java", 30, "benny")
println(person.description())
}
这时候的结果呢?spa
age: 30;name: benny
原本这个 discription
方法是想要输出对象对应的属性,结果却按照 Person
进行了输出。有人可能会说你这不是搞事情吗,明明 person
这个变量的类型就是 Coder
,干吗非要用 Person
类型呢?这问题我想不须要回答吧。.net
2. 泛型参数的例子
其实问题是很清楚的,this::class
表示的是对象的类型,而 T::class
则取决于 T
被如何推断。具体用哪一个,取决于你的需求。咱们再给你们看个例子:设计
abstract class A<T>{
val t: T = ...
}
A
有个属性是 T
类型的,而这个属性呢,须要在内部初始化。咱们在定协议时要求类型 T
有默认构造方法,以便于咱们经过反射实例化它。
咱们知道 Kotlin 的泛型也是伪泛型,T
在这里不能直接用于获取其具体的类型,若是咱们想要初始化 t
,该怎么作呢?
abstract class A<T>{
val t: T by lazy{
(this@A::class
.supertypes.first() // 类 A 的 KType
.arguments.first() // T 的泛型实参
.type!!.classifier as KClass<*>)
.primaryConstructor!!.call() as T
}
}
首先咱们拿到 this@A::class
,这实际上并非 A::class
,这一点必定要注意,咱们这段代码其实是运行在子类实例化的过程当中的,this
是一个子类类型的引用,指向子类实例。也正是由于这一点,咱们想要获取泛型参数 T
的实参,还须要先拿到 super type 也就是 A
的 KType
实例了。
其次,获取泛型实参,并拿到实参类型的 KClass
实例。
最后,调用主构造器构造对象 T
。
下面看下测试程序:
class B: A<C>()
class C{
override fun toString(): String {
return "C()"
}
}
fun main(args: Array<String>) {
val b = B()
println(b.t)
}
结果可想而知了。
C()
3. 衍生话题:编译期类型绑定
咱们再回头看下第一个例子,它实际上还涉及到一个编译期类型绑定的问题。咱们直接看例子:
open class Employee{
fun raise(amount: Number){
println("Got raise: $amount")
}
}
class Manager: Employee(){
fun raise(amount: BigDecimal){
println("Got big raise: $amount")
}
}
fun main(args: Array<String>) {
val employee = Employee()
employee.raise(BigDecimal(31))
val managerA = Manager()
managerA.raise(BigDecimal(31000))
val managerB: Employee = Manager()
managerB.raise(BigDecimal(31000000))
}
咱们很容易就能想到结果:
Got raise: 31 Got big raise: 31000 Got raise: 31000000
这个结果彷佛就好像,尽管 managerB
是 Manager
岗位,享受着经理的待遇,不过他尚未被正式任命,因此在系统中与普通员工是同样同样滴。
相比之下,Groovy 的结果可能会有些不同:
class Employee {
void raise(Number amount) {
println("Got raise: $amount")
}
}
class Manager extends Employee {
void raise(BigDecimal amount) {
println("Got big raise: $amount")
}
}
Employee employee = new Employee()
employee.raise(new BigDecimal(31))
Manager managerA = new Manager()
managerA.raise(new BigDecimal(31000))
Employee managerB = new Manager()
managerB.raise(new BigDecimal(31000000))
Groovy 是动态类型的语言,在运行时根据对象的类型肯定调用的方法,这一点与 Kotlin 不同:
Got raise: 31 Got big raise: 31000 Got big raise: 31000000
这里我还想要告诉你们的是,Java 跟 Kotlin 的结果是同样的。
注:本例来自 《Groovy 程序设计》3.6 多方法 一节的讨论。
4. 小结
本文从 this::class
和 T::class
的异同出发,探讨了 this::class
的两种应用场景,并衍生出了编译期绑定的问题,上述讨论的结果也一样适用于 Java 中的 this.getClass()
以及 T.class
。
本文分享自微信公众号 - Kotlin(KotlinX)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。