在使用kotlin的過程中,遇到了一些泛型上的問題,索性統一研究下;
關於通配
Java裏有?、extends、super;
Kotlin裏有*、out、in;
雖然表示方法不同,但其實可以認爲是分別對應且等價的;
需要明確的是其特性,比如:
通配符類型參數 ? extends E 表示此方法接受 E 或者 E 的 一些子類型對象的集合,而不只是 E 自身。 這意味着我們可以安全地從其中(該集合中的元素是 E 的子類的實例)讀取 E,但不能寫入, 因爲我們不知道什麼對象符合那個未知的 E 的子類型。 反過來,該限制可以讓Collection表示爲Collection<? extends Object>的子類型。 簡而言之,帶 extends 限定(上界)的通配符類型使得類型是協變的(covariant)。
這段內容來自Kotlin中文網-泛型,主要是說明兩個特性:
? extends E表示數據不能安全寫入,但可以安全讀出;(協變)
? super E表示數據可以安全寫入,但無法安全讀出;(逆變)
這裏的“安全”當然是指類型安全,因爲類型擦除的原因,泛型的安全檢查全部在編譯時進行。
概念什麼的太模糊,動手就知道了:
static void testOutType() {
List<Integer> list1 = new ArrayList<>();
List<? extends Number> list2 = new ArrayList<>();
list1.add(1);
list1.add(2);
// list2.add(1);//不允許
list2 = list1;
//取出來的數據可以確定是Number的子類
Number n1 = list2.get(0);
Number n2 = list2.get(1);
}
聲明爲? extends Number的list2是無法添加Integer的,編譯直接無法通過;
雖然Integer是Number的子類,但編譯器認爲不能確定添加(寫入)的數據是否安全,有可能是Integer也有可能是其他Number子類,所以並不能安全寫入;
但讀取的時候是可以確定是Number的子類,所以認爲是安全的;
static void testInType() {
List<Number> list1 = new ArrayList<>();
List<? super Integer> list2 = new ArrayList<>();
list2 = list1;
list2.add(1);//允許
list2.add(2);//允許
//取出來的數據只能視爲Object
Integer a = (Integer) list2.get(0);
Object b = list2.get(1);
}
而聲明爲? super Integer的list2是可以添加Integer的,相比之下寬鬆一些;
因爲確定了添加的數據肯定是Integer的父類,所以編譯器認爲是安全的;
相反讀取的時候,只能將讀取的數據視爲Object,並顯示轉換,這樣明顯是不安全的,所以認爲是讀取不安全的;
static void testType(){
List<Integer> list1 = new ArrayList<>();
List<?> list2 = new ArrayList<>();
list1.add(1);
// list2.add(2);//不允許
list2 = list1;
Object a = list1.get(0);
}
聲明爲?的list2則兼顧了上面兩種特性,一是不允許添加數據,因爲不確定類型安全,二是讀取數據只能爲Object;
?默認實現爲? extends Object,所以會具有這樣的特性;
那麼,對應kotlin中:
? extends E 等價於out E;
? super E等價於in E;
?等價於*;
問題
現在有這麼3個接口,
interface Gesture {
fun onMove()
fun setGestureListener(listener: GestureListener<out Gesture>)
}
interface Fling : Gesture {
fun onFling()
}
interface GestureListener<F> {
fun done(gesture: F)
}
並且有這麼一個實現類,用Java寫出來是這樣的:
public class FlingImpl implements Fling {
private GestureListener listener;//可以規避
@Override
public void onMove() {
listener.done(this);//因爲沒有聲明F,規避了extends無法寫入的問題
}
@Override
public void setGestureListener(GestureListener<? extends Gesture> listener) {
this.listener = listener;
}
@Override
public void onFling() {
System.out.println("invoke onFling");
}
}
這裏的GestureListener允許不聲明類型參數,但kotlin中這樣是不行的,kotlin不允許使用沒有指定類型實參的泛型類型;
比如像下面這樣寫是會報錯的:
class FlingImpl : Fling {
private var listener: GestureListener<out Gesture>? = null
override fun onMove() {
listener?.done(this)//編譯出錯
}
override fun setGestureListener(listener: GestureListener<out Gesture>) {
this.listener = listener
}
override fun onFling() {
println("invoke onFling")
}
}
這裏的GestureListener就規避不了了,因爲必須要指定類型參數;
而指定了類型參數,因爲out Gesture的特性,這個listener是無法寫入的,所以listener?.done方法是無法通過編譯的,會報這麼一個錯誤:
Kotlin: Out-projected type 'GestureListener<*>?' prohibits the use of 'public abstract fun done(gesture: F): Unit defined in com.xter.callback.kotlin.GestureListener'
解決
最簡單也是最實用的解決辦法,還得是強制轉換,因爲調用的類或接口有可能並不是能夠改動的;
那麼解決辦法就是把out改爲in,使用as進行強制轉換:
class FlingImpl : Fling {
private var listener: GestureListener<in Fling>? = null
override fun onMove() {
listener?.done(this)
}
override fun setGestureListener(listener: GestureListener<out Gesture>) {
this.listener = listener as? GestureListener<in Fling>?
}
override fun onFling() {
println("invoke onFling")
}
}
這樣就能正常運行了。
同樣的思路在Java裏也是適用的,只是Java相對寬鬆一些,所以在某些場景可以不使用轉換就完成。