關於Kotlin泛型遇到的問題

在使用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相對寬鬆一些,所以在某些場景可以不使用轉換就完成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章