網上找的一段協變、逆變比較正式的定義:java
逆變與協變用來描述類型轉換後的繼承關係,其定義:若是
A、B
表示類型,f(⋅)
表示類型轉換,≦
表示繼承關係(好比,A≦B
表示A
是由B
派生出來的子類): 當A ≦ B
時,若是有f(A) ≦ f(B)
,那麼f
是協變的; 當A ≦ B
時,若是有f(B) ≦ f(A)
,那麼f
是逆變的; 若是上面兩種關係都不成立,即(A)
與f(B)
相互之間沒有繼承關係,則叫作不變的。安全
java
中能夠經過以下泛型通配符以支持協變和逆變:markdown
? extends
來使泛型支持協變。修飾的泛型集合只能讀取不能修改,這裏的修改僅指對泛型集合添加元素,若是是 remove(int index)
以及 clear
固然是能夠的。? super
來使泛型支持逆變。修飾的泛型集合只能修改不能讀取,這裏說的不能讀取是指不能按照泛型類型讀取,你若是按照 Object
讀出來再強轉固然也是能夠的。以動物舉例,看代碼。ide
abstract class Animal {
void eat() {
System.out.println("我是" + myName() + ", 我最喜歡吃" + myFavoriteFood());
}
abstract String myName();
abstract String myFavoriteFood();
}
class Fish extends Animal {
@Override
String myName() {
return "魚";
}
@Override
String myFavoriteFood() {
return "蝦米";
}
}
class Cat extends Animal {
@Override
String myName() {
return "貓";
}
@Override
String myFavoriteFood() {
return "小魚乾";
}
}
public static void extendsFun() {
List<Fish> fishList = new ArrayList<>();
fishList.add(new Fish());
List<Cat> catList = new ArrayList<>();
catList.add(new Cat());
List<? extends Animal> animals1 = fishList;
List<? extends Animal> animals2 = catList;
animals2.add(new Fish()); // 報錯
Animal animal1 = animals1.get(0);
Animal animal2 = animals2.get(0);
animal1.eat();
animal2.eat();
}
//輸出結果:
我是魚, 我最喜歡吃蝦米
我是貓, 我最喜歡吃小魚乾
複製代碼
協變就比如有多個集合,每一個集合存儲的是某中特定動物(extends Animal
),可是不告訴你那個集合裏存儲的是魚,哪一個是貓。因此你雖然能夠從任意一個集合中讀取一個動物信息,沒有問題,可是你沒辦法將一條魚的信息存儲到魚的集合裏,由於僅從變量 animals一、animals2
的類型聲明上來看你不知道哪一個集合裏存儲的是魚,哪一個集合裏是貓。 假如報錯的代碼不報錯了,那不就說明把一條魚塞進了一堆貓裏,這屬於給貓加菜啊,因此確定是不行的。? extends
類型通配符所表達的協變就是這個意思。學習
那逆變是什麼意思呢?仍是以上面的動物舉例:spa
public static void superFun() {
List<Fish> fishList = new ArrayList<>();
fishList.add(new Fish());
List<Animal> animalList = new ArrayList<>();
animalList.add(new Cat());
animalList.add(new Fish());
List<? super Fish> fish1 = fishList;
List<? super Fish> fish2 = animalList;
fish1.add(new Fish());
Fish fish = fish2.get(0); //報錯
}
複製代碼
從變量 fish一、fish2
的類型聲明上只能知道里面存儲的都是魚的父類,若是這裏也不報錯的話可就從 fish2
的集合裏拿出一隻貓賦值給一條魚了,這屬於謀殺親魚。因此確定也是不行。? super
類型通配符所表達的逆變就是這個意思。code
kotlin
中對於協變和逆變也提供了兩個修飾符:orm
out
:聲明協變;in
:聲明逆變。它們有兩種使用方式:對象
java
同樣在使用處聲明;當和 java
同樣在使用處聲明時,將上面 java
示例轉換爲 kotlin
:繼承
fun extendsFun() {
val fishList: MutableList<Fish> = ArrayList()
fishList.add(Fish())
val catList: MutableList<Cat> = ArrayList()
catList.add(Cat())
val animals1: MutableList<out Animal> = fishList
val animals2: MutableList<out Animal> = catList
animals2.add(Fish()) // 報錯
val animal1 = animals1[0]
val animal2 = animals2[0]
animal1.eat()
animal2.eat()
}
fun superFun() {
val fishList: MutableList<Fish> = ArrayList()
fishList.add(Fish())
val animalList: MutableList<Animal> = ArrayList()
animalList.add(Cat())
animalList.add(Fish())
val fish1: MutableList<in Fish> = fishList
val fish2: MutableList<in Fish> = animalList
fish1.add(Fish())
val fish: Fish = fish2[0] //報錯
}
複製代碼
能夠看到在 kotlin
代碼中除了將 ? extends
替換爲了 out
,將 ? super
替換爲了 in
,其餘地方並無發生變化,而產生的結果是同樣的。那在類或接口的定義處聲明 in、out
的做用是什麼呢。
假設有一個泛型接口 Source<T>
,該接口中不存在任何以 T
做爲參數的方法,只是方法返回 T
類型值:
// Java
interface Source<T> {
T nextT();
}
複製代碼
那麼,在 Source <Object>
類型的變量中存儲 Source <String>
實例的引用是極爲安全的——沒有消費者-方法能夠調用。可是 Java
並不知道這一點,而且仍然禁止這樣操做:
// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!!在 Java 中不容許
// ……
}
複製代碼
爲了修正這一點,咱們必須聲明對象的類型爲 Source<? extends Object>
,但這樣的方式很複雜。而在 kotlin
中有一種簡單的方式向編譯器解釋這種狀況。咱們能夠標註 Source
的類型參數 T
來確保它僅從 Source<T>
成員中返回(生產),並從不被消費。爲此咱們使用 out
修飾符修飾泛型 T
:
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // 這個沒問題,由於 T 是一個 out-參數
// ……
}
複製代碼
還記得開篇協變的定義嗎?
當
A ≦ B
時,若是有f(A) ≦ f(B)
,那麼f
是協變的; 當A ≦ B
時,若是有f(B) ≦ f(A)
,那麼f
是逆變的;
也就是說:
當一個類
C
的類型參數T
被聲明爲out
時,那麼就意味着類C
在參數T
上是協變的;參數T
只能出如今類C
的輸出位置,不能出如今類C
的輸入位置。
一樣的,對於 in
修飾符來講
當一個類
C
的類型參數T
被聲明爲in
時,那麼就意味着類C
在參數T
上是逆變的;參數T
只能出如今類C
的輸如位置,不能出如今類C
的輸出位置。
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0 擁有類型 Double,它是 Number 的子類型
// 所以,咱們能夠將 x 賦給類型爲 Comparable <Double> 的變量
val y: Comparable<Double> = x // OK!
}
複製代碼
總結以下表:
你們有其餘見解的能夠留言一塊兒交流學習!點個讚唄!