[著作權申明] 非商業目的注明出處可自由轉載
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/108759370
出自:shusheng007
文章首發于個人博客
文章目錄
- 概述
- 泛型型變
- 協變(out)
- 逆變(in)
- 總結
概述
本文承接于上一篇:秒懂Kotlin之協變(Covariance)逆變(Contravariance)與抗變(Invariant),一定要先閱讀這一篇文章,再閱讀本文,不然看不懂!
上篇講到Java中泛型是抗變的,但是陣列卻是協變的,Kotlin做的更徹底,不僅泛型是抗變的就連陣列也變成抗變的了,
下面的代碼是編譯不過的
val strArray:Array<String> = arrayOf("shu","sheng","007")
val array:Array<Any> = strArray
報錯:
Type mismatch: inferred type is Array<String> but Array<Any> was expected
泛型型變
官方檔案見:Variance
Kotlin中沒有通配符,取而代之的是 Declaration-site variance和Use-site variance ,其通過兩個關鍵字out和in來實作Java中的? extends 與? super的功能.
假設我們有如下兩個類和一個介面
open class Animal
class Dog : Animal()
interface Box<T> {
fun getAnimal(): T
fun putAnimal(a: T)
}
協變(out)
我們要定義一個方法,引數型別為Box<Animal>,但是我們希望可以傳入Box<Dog>即希望可以發生協變,
Java實作
private Animal getOutAnimalFromBox(Box<? extends Animal> box) {
Animal animal = box.getAnimal();
// box.putAnimal(? ) 沒有辦法呼叫修改方法,因為我們不知道?究竟是一個什么型別,沒辦法傳入
return animal;
}
Kotlin對應的實作為:
fun getAnimalFromBox(b: Box<out Animal>) : Animal {
val animal: Animal = b.getAnimal()
// b.putAnimal(Nothing) 無法呼叫,因為方法需要一個Nothing型別的物件,但是在kotlin中無法獲取
return animal
}
此方法可以接受Box<Dog>型別的引數了,
可見此處使用out 代替了? extends,從結果來看確實更合適一點,因為傳入的引數只能提供值,而不能消費值,由于out是在方法呼叫的引數中標記的,處于使用端,所以叫Use-site variance與Use-site variance對應的就是Declaration-site variance了,
我們發現介面Box<T>中既有消費值的方法fun putAnimal(a: T),又有提供值的方法fun getAnimal(): T,導致我們必須在使用側告訴編譯器我們要使用哪一類方法,那我們可以在宣告介面的時候告訴編譯器嗎?答案是肯定的,但是就需要將介面拆分為只包含提供值的方法的介面producer與只包含消費值的方法的介面consumer,
//producer
interface ReadableBox<out T> {
fun getAnimal(): T
}
//consumer
interface WritableBox<in T> {
fun putAnimal(a: T)
}
拆分完介面并做了相應的宣告后,就可以不在使用端使用out或者in了,
fun getAnimalFromReadableBox(b: ReadableBox<Animal>){
val a: Animal = b.getAnimal()
}
上面的方法可以直接接受ReadableBox<Dog>型別的引數,給人的感覺好像是Kotlin使得泛型協變了,
getAnimalFromReadableBox(object :ReadableBox<Dog>{
override fun getAnimal(): Dog {
return Dog()
}
})
此種情況下out和in是在宣告時候使用的,所以叫Declaration-site variance了,
逆變(in)
我們要定義一個方法,引數型別為Box<Dog>,但是我們希望可以傳入Box<Animal>,即希望可以發生逆變,
Java實作
private void putAnimalInBox(BoxJ<? super Dog> box){
box.putAnimal(new Dog());
Object animal= box.getAnimal();// 可以呼叫讀取方法,但是回傳的型別確實Object,因為我們只能確定?的大基類是Object
}
Kotlin對應實作
fun putAnimalInBox(b: Box<in Dog>){
b.putAnimal(Dog())
val animal:Any? = b.getAnimal()// 可以呼叫讀取方法,但是回傳的型別確實Any?,因為我們只能確定?的大基類是Any?
}
此方法可以接受Box<Animal>型別的引數了
可見此處使用in 代替了? super,從結果來看確實更合適一點,因為傳入的引數只適合消費值,而不適合獲取值,獲取到的值失去了有用的型別資訊,由于in是在方法呼叫的引數中標記的,處于使用端,所以叫Use-site variance
讓我們來看一下使用Declaration-site variance實作逆變
fun putAnimalToWritableBox(b:WritableBox<Dog>){
b.putAnimal(Dog())
}
上面的方法可以直接接受WritableBox<Animal>型別的引數,給人的感覺好像是Kotlin使得泛型逆變了,
putAnimalToWritableBox(object :WritableBox<Animal>{
override fun putAnimal(a: Animal) {
}
})
總結
kotlin中令初學者費解的out與in關鍵字就徹底將完了,相信你應該秒懂了,如果還是不懂,說明你太早看到這篇文章拉,建議你先收藏,以后等水平提高了再回來看看,
對了,記得點贊,分享,
暮云收盡溢清寒,銀漢無聲轉玉盤,此生此夜不長好,明月明年何處看,《陽關曲 中秋月》蘇軾
參考文章:
- Generics
- The Ins and Outs of Generic Variance in Kotlin
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/131728.html
標籤:AI
