一. 類(lèi)(Class) 與類(lèi)型(Type)
Kotlin 中類(lèi)和類(lèi)型是不一樣的概念陨享。
下圖充分展示了它們的區(qū)別葱淳。
二. 型變
型變是指
類(lèi)型
轉(zhuǎn)換后的繼承關(guān)系。
Kotlin 的型變分為逆變抛姑、協(xié)變和不變赞厕。
2.1 協(xié)變
如果 A 是 B 的子類(lèi)型,并且Generic<A> 也是 Generic<B> 的子類(lèi)型定硝,那么 Generic<T> 可以稱(chēng)之為一個(gè)協(xié)變類(lèi)皿桑。
2.1.1 Java 上界通配符<? extends T>
Java 的協(xié)變通過(guò)上界通配符實(shí)現(xiàn)。
如果 Dog 是 Animal 的子類(lèi)蔬啡,但 List<Dog> 并不是 List<Animal> 的子類(lèi)诲侮。
下面的代碼會(huì)在編譯時(shí)報(bào)錯(cuò):
List<Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
animals = dogs; // incompatible types
而使用上界通配符之后,List<Dog> 變成了 List<? extends Animal> 的子類(lèi)型箱蟆。即 animals 變成了可以放入任何 Animal 及其子類(lèi)的 List沟绪。
因此,下面的代碼編譯是正確的:
List<? extends Animal> animals = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
animals = dogs;
2.1.2 Kotlin 的關(guān)鍵詞 out
上述代碼改成 Kotlin 的代碼:
fun main() {
var animals: List<Animal> = ArrayList()
val dogs = ArrayList<Dog>()
animals = dogs
}
居然沒(méi)有編譯報(bào)錯(cuò)空猜?其實(shí)绽慈,Kotlin 的 List 跟 Java 的 List 并不一樣恨旱。
Kotlin 的 List 源碼中使用了out
,out
相當(dāng)于 Java 上界通配符坝疼。
public interface List<out E> : Collection<E> {
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator<E>
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
public operator fun get(index: Int): E
public fun indexOf(element: @UnsafeVariance E): Int
public fun lastIndexOf(element: @UnsafeVariance E): Int
public fun listIterator(): ListIterator<E>
public fun listIterator(index: Int): ListIterator<E>
public fun subList(fromIndex: Int, toIndex: Int): List<E>
}
類(lèi)的參數(shù)類(lèi)型使用了out
之后搜贤,該參數(shù)只能出現(xiàn)在方法的返回類(lèi)型。
2.1.3 @UnsafeVariance
但是裙士,Kotlin List 的 contains入客、containsAll、indexOf 和 lastIndexOf 方法中腿椎,入?yún)⒕霈F(xiàn)了范型 E桌硫。并且使用 @UnsafeVariance 修飾。
正是由于 @UnsafeVariance 的修飾啃炸,打破了剛才的限制铆隘,否則會(huì)編譯報(bào)錯(cuò)。
2.2 逆變
如果 A 是 B 的子類(lèi)型南用,并且 Generic<B> 是 Generic<A> 的子類(lèi)型膀钠,那么 Generic<T> 可以稱(chēng)之為一個(gè)逆變類(lèi)。
2.2.1 Java 下界通配符<? super T>
Java 的逆變通過(guò)下界通配符實(shí)現(xiàn)肿嘲。
下面的代碼因?yàn)槭菂f(xié)變的,無(wú)法添加新的對(duì)象筑公。編譯器只能知道類(lèi)型是 Animal 的子類(lèi),并不能確定具體類(lèi)型是什么匣屡,因此無(wú)法驗(yàn)證類(lèi)型的安全性。
List<? extends Animal> animals = new ArrayList<>();
animals.add(new Dog()); // compile error
使用下界通配符之后捣作,代碼編譯通過(guò):
List<? super Animal> animals = new ArrayList<>();
animals.add(new Dog());
? super Animal 表示 Animal 及其父類(lèi) 。所以 animals 可以接收所有 Animal 的子類(lèi)添加至該列表中券躁。
Java 的上界通配符和下界通配符符合 PECS 原則惩坑。
PECS 原則即 Producer Extends,Consumer Super 也拜。如果參數(shù)化類(lèi)型是一個(gè)生產(chǎn)者旭贬,則使用 <? extends T>;如果它是一個(gè)消費(fèi)者搪泳,則使用 <? super T>浑吟。
其中贺纲,生產(chǎn)者
表示頻繁往外讀取數(shù)據(jù) T,而不從中添加數(shù)據(jù)备典。消費(fèi)者
表示只往里插入數(shù)據(jù) T,而不讀取數(shù)據(jù)艰赞。
2.2.2 Kotlin 的關(guān)鍵詞 in
in
相當(dāng)于 Java 下界通配符佣谐。
abstract class Printer<in E> {
abstract fun print(value: E): Unit
}
class AnimalPrinter: Printer<Animal>() {
override fun print(animal: Animal) {
println("this is animal")
}
}
class DogPrinter : Printer<Dog>() {
override fun print(dog: Dog) {
println("this is dog")
}
}
fun main() {
val animalPrinter = AnimalPrinter()
animalPrinter.print(Animal())
val dogPrinter = DogPrinter()
dogPrinter.print(Dog())
}
類(lèi)的參數(shù)類(lèi)型使用了in
之后,該參數(shù)只能出現(xiàn)在方法的入?yún)ⅰ?/p>
2.3 不變
默認(rèn)情況下方妖,Kotlin 中的泛型類(lèi)是不變的狭魂。 這意味著它們既不是協(xié)變的也不是逆變的。
例如 MutableList党觅,它可讀可寫(xiě)雌澄,泛型沒(méi)有使用in
、out
杯瞻。
三. 總結(jié)
本文從 Kotlin 的類(lèi)镐牺、類(lèi)型引出了型變。介紹了 Kotlin 的協(xié)變魁莉、協(xié)變和不變的概念和特性睬涧,以及 Java 的上界通配符、下界通配符旗唁。
該系列的相關(guān)文章:
Kotlin 泛型之類(lèi)型擦除