T::class 和 this::class 的區別

0. 引子

前幾天推送了一篇文章:你絕對想不到 Kotlin 泛型給反射留下了怎樣的坑!,受到一位朋友的評論的啓發,這篇文章就承接前文,探討一下 T::classthis::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 也就是 AKType 實例了。

其次,獲取泛型實參,並拿到實參類型的 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

這個結果彷佛就好像,儘管 managerBManager 崗位,享受着經理的待遇,不過他尚未被正式任命,因此在系統中與普通員工是同樣同樣滴。

相比之下,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::classT::class 的異同出發,探討了 this::class 的兩種應用場景,並衍生出了編譯期綁定的問題,上述討論的結果也一樣適用於 Java 中的 this.getClass() 以及 T.class


本文分享自微信公衆號 - Kotlin(KotlinX)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索