Kotlin之泛型的實化、協(xié)變、逆變

1、泛型的實化

Java中泛型是在JDK1.5引入的,是一個偽泛型,它是通過泛型擦除機制來實現(xiàn)的。泛型只存在編譯時期,運行時泛型就會被擦除,所以我們無法在運行時獲取泛型的類型信息。
Kotlin最終也會編譯生成和Java相同規(guī)格的class文件,所以Kotlin中的泛型也會被擦除,所以我們無法使用a is T 和 T::class.java但是Kotlin提供了一個內(nèi)聯(lián)函數(shù),在編譯時就會將內(nèi)聯(lián)函數(shù)的代碼替換到實際調(diào)用的地方,所以對于內(nèi)聯(lián)函數(shù)來說是不存在泛型的擦除的。
所以我們是可以將內(nèi)聯(lián)函數(shù)中的泛型進行實化的,泛型實化的前提有2個:

  • 1、必須泛型函數(shù)且是內(nèi)聯(lián)函數(shù)
  • 2、聲明泛型的地方加上關(guān)鍵字reified修飾(reified只能用于內(nèi)聯(lián)泛型函數(shù))
    具體代碼如下:
inline fun <reified T> getType() {}

通過泛型實化可以獲取泛型類型的功能,創(chuàng)建Reified.kt,并在其中定義一個頂層函數(shù)

inline fun <reified T> getType() = T::class.java

調(diào)用getType()

fun kotlinTest() {
        val stringType:Class<String> = getType<String>()
        val intType:Class<Int> = getType<Int>()
        println("stringType:${stringType.name} , intType:${intType.name}")
    }

打印結(jié)果如下:

stringType:java.lang.String , intType:java.lang.Integer

可以看到我們在運行時獲取到了泛型的類型。

泛型實化的使用場景

先看下打開Activity的代碼

 fun openActivity(){
        val intent=Intent(this,MainActivity::class.java)
        startActivity(intent)
    }

下面看下如何通過泛型的實化來實現(xiàn)這個功能

inline fun <reified T> openActivity(context: Context){
       val intent=Intent(context,T::class.java)
       context.startActivity(intent)
   }

但是問題又來了,怎么使用Intent進行數(shù)據(jù)的傳輸呢?其實很簡單,我們可以使用高階函數(shù)來實現(xiàn),具體傳輸?shù)臄?shù)據(jù)由調(diào)用者來完成。

inline fun <reified T> openActivity(context: Context, block: Intent.() -> Unit) {
        val intent = Intent(context, T::class.java)
        intent.block()
        context.startActivity(intent)
    }

調(diào)用就很簡單了

openActivity<MainActivity>(this) {
                putExtra("name", "LiLei")
                putExtra("age", 12)
            }

這樣我們就完成了打開頁面通用的方法,可以把openActivity()函數(shù)定義成頂層函數(shù),這樣我們就可以在任意位置調(diào)用了,不過泛型的實化是無法在Java中調(diào)用的。下面看下在Java中如何調(diào)用openActivity()

 ReifiedKt.<MainActivity>openActivity(context, new Function1<Intent, Unit>() {
            @Override
            public Unit invoke(Intent intent) {
                intent.putExtra("name", "LiLei");
                intent.putExtra("age", 12);
                return null;
            }
        });

報錯信息如下:

openActivity(android.content.Context, kotlin.jvm.functions.Function1<? super android.content.Intent,kotlin.Unit>)' has private access in 'com.example.abu.serviceproject.ReifiedKt

可以看到在Java中無權(quán)訪問該方法的。

2、泛型的協(xié)變

一個泛型類或泛型接口中的方法,方法的參數(shù)列表是接收數(shù)據(jù)的,稱為in位置,方法的返回值是返回數(shù)據(jù)的,稱為out位置。
在講解泛型的協(xié)變之前,先看個例子:創(chuàng)建一個Person類、Student類、Teacher類,讓Student和Teacher繼承Person類。

open class Person(var name: String, var age: Int)  {}
class Student(name: String, age: Int) : Person(name, age) 
class Teacher(name: String, age: Int) : Person(name, age)

創(chuàng)建一個方法,接收的參數(shù)是Person對象

fun handleData(person: Person){}

該方法接收Person對象,那么能不能向其中傳入Student對象或Teacher對象?
Student和Teacher都是Person的子類,所以是肯定可以的。下面我們修改下handleData()接收的參數(shù)為List<Person>對象

fun handleData(person: List<Person>) {}

那么handleData()能接收List<Student>/List<Teacher>嗎?
在Java中是不允許的,List<Student>/List<Teacher>并不是 List<Person>的子類,否則存在類型轉(zhuǎn)換異常的隱患的。下面通過例子來解析下原因:
創(chuàng)建泛型類SimpleData

class SimpleData<T>() {

    private var t: T? = null

    fun set(t: T) {
        this.t = t
    }

    fun get(): T? = t
}

創(chuàng)建handleData()函數(shù)接收SimpleData<Person>參數(shù)

fun test() {
        val stu = Student("張", 12)
        val stuData = SimpleData<Student>()
        stuData.set(stu)
        handleSimpleData(stuData) //編譯失敗
        val result: Student? = stuData.get()
    }

    fun handleSimpleData(data: SimpleData<Person>) {
        val teacher = Teacher("李", 40)
        data.set(teacher)
    }

由于SimpleData<Student>并不是SimpleData<Person>子類,所以handleSimpleData(stuData)肯定是編譯失敗的,這里假設編譯通過,那么在handleSimpleData()中創(chuàng)建Teacher對象并通過data.set(teacher)替換Student對象,然后通過stuData.get()獲取的是Student對象,實際上返回的是Teacher對象,這就造成了類型轉(zhuǎn)換異常。所以這種寫法是不合法的。
如果不允許修改SimpleData中的數(shù)據(jù),是不是就能解決類型轉(zhuǎn)換異常的問題了。
Kotlin中提供了out關(guān)鍵字來保證泛型只能定義在返回值的位置,不能定義在接收參數(shù)的位置,使用out修改SimpleData類,代碼如下:

class SimpleData<out T>(val t: T) {
    fun get(): T = t
}

不是說T不能出現(xiàn)在接收參數(shù)的位置嗎?為什么能出現(xiàn)在構(gòu)造函數(shù)中,這是因為val修飾的t是不允許修改,這也是符合安全規(guī)范的。同理,我們也可以使用private var t:T來代替val t:T。
此時handleSimpleData(data: SimpleData<Person>)中就能接收SimpleData<Student>

fun test() {
    val stu = Student("張", 12)
    val stuData = SimpleData<Student>(stu)
    handleSimpleData(stuData) //編譯成功
    val student: Student = stuData.get()
    Log.e(tag,"name is ${student.name} , age is ${student.age}。")
}

fun handleSimpleData(data: SimpleData<Person>) {
    val person = data.get()
    person.name = "李"
    person.age = 40
}

handleSimpleData(data: SimpleData<Person>)中就能接收SimpleData<Student>說明SimpleData<Student>就是SimpleData<Person>的子類,而Student又是Person的子類,這就是Kotlin中泛型的協(xié)變。

3、泛型的逆變

定義一個 MyClass<T> 的泛型類,其中 A 是 B 的子類型,同時 MyClass<B> 又是 MyClass<A> 的子類型,就稱 MyClass 在 T 這個泛型上逆變的。
下面舉個例子來看下:

interface Transform<T> {
    fun transform(t: T): String
}

fun main() {
    val tranform = object : Transform<Person> {
        override fun transform(t: Person): String {
            return "name is ${t.name}, age is ${t.age}"
        }
    }
    transformData(tranform)//編譯失敗
}

fun transformData(transform: Transform<Student>) {
    val student = Student("張", 12)
    transform.transform(student)
}

上面代碼是無法正常的編譯的,我們可以使用逆變來解決上面問題。修改Transform接口

interface Transform<in T> {
    fun transform(t: T): String
}

只要加上關(guān)鍵字in就可以實現(xiàn)泛型的逆變了,Transform<Person>也就變成了Transform<Student>的子類了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容