Kotlin泛型筆記

Kotlin語言基礎(chǔ)筆記

Kotlin流程控制語句筆記

Kotlin操作符重載與中綴表示法筆記

Kotlin擴(kuò)展函數(shù)和擴(kuò)展屬性筆記

Kotlin空指針安全(null-safety)筆記

Kotlin類型系統(tǒng)筆記

Kotlin面向?qū)ο缶幊坦P記

Kotlin委托(Delegation)筆記

Kotlin泛型型筆記

Kotlin函數(shù)式編程筆記

Kotlin與Java互操作筆記

Kotlin協(xié)程筆記

Kotlin中的泛型跟Java中的泛型基本上一樣,都是在編譯的時(shí)候才有效果,在運(yùn)行期是擦除的。我們可以在類,方法中使用泛型:

class Box<T>(var value: T){
    fun get(): T {
        return value
    }

    fun set(x: T){
        this.value = x;
    }
}

fun <T> newBox(value: T) = Box(value)

fun main(args: Array<String>) {
    val box1:Box<String> = Box<String>("Denny")
    val box2:Box<String> = Box("Denny")
    val box3 = Box("Denny")

    val box5 = newBox(true)

    val box4 = Box(1)
    box4.set("Jack") //編譯錯(cuò)誤。Type mismatch, Required: Int, Found String
}

泛型類的定義是在類名稱之后主構(gòu)造函數(shù)之前,泛型函數(shù)聲明跟Java一樣,類型參數(shù)要放在函數(shù)明之前。類型如果可以推斷出來的話,可以省略泛型類型。
泛型函數(shù)類型參數(shù)可以使用:冒號(hào)指定上界:

fun <T : Comparable<T>> sort(list: List<T>){...}

如果有多個(gè)上界,可以使用where語句。

fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
  where T : Comparable, Cloneable {
  return list.filter(it > threshold).map(it.clone()) 
}

1. 協(xié)變(covariant)和逆變(contravariant)

Number[] numbers = new Integer[]{1, 2, 3};  //works
List<Number> numberList = new ArrayList<Integer>();  //編譯錯(cuò)誤,暴類型不兼容錯(cuò)誤。

Java中,數(shù)組是協(xié)變的,而泛型不是協(xié)變的,也就是說Integer是Number的子類,數(shù)組類型Integer[]也是Number[]的子類,但是List<Integer>不是List<Number>的子類。

Animal類型是Dog類型的父類,假如Animal類型標(biāo)記為F(father),Dog類型標(biāo)記為C(child),我們用這個(gè)表示父子類型關(guān)系:F <| C。而對(duì)應(yīng)的泛型List<Animal>,List<Dog>,我們標(biāo)記為f<F>, f<C>。那么當(dāng) F <| C時(shí):

  • 如果f<F> <| f<C>成立,那么f叫做協(xié)變;
  • 如果f<C> <| f<F>成立,那么f叫著逆變;
  • 如果以上兩種情況都不成立,那么叫著不可變;

Java的泛型是不變的。如果要實(shí)現(xiàn)協(xié)變和逆變的話,那么我們就需要使用通配符?

<? extends T>實(shí)現(xiàn)了泛型的協(xié)變

        List<? extends Number> list1 = new ArrayList<Integer>();
        List<? extends Number> list2 = new ArrayList<Float>();

以上便實(shí)現(xiàn)了協(xié)變,但是你要注意的是,這里的list1和list2只能夠add(null),不能夠添加其他任意對(duì)象。如下:

image.png

為什么會(huì)這樣呢?因?yàn)槿绻鸏ist<? extends Number> 可以添加Integer,F(xiàn)loat,Double等數(shù)字類型的話,那么從這個(gè)List獲取到的值可能會(huì)有各種類型,也就會(huì)引發(fā)潛在的錯(cuò)誤,所以Java為了保護(hù)類型一致性,就禁止向List<? extends Number> 添加任意對(duì)象。

<? super T>實(shí)現(xiàn)了泛型的逆變

        List<? super Number> list3 = new ArrayList<Number>();
        List<? super Number> list4 = new ArrayList<Object>();
        list3.add(new Integer(3));
        list4.add(new Float((3.3)));

我們可以向List<? super Number>添加Number以及Number的子類,但是不能添加Number的父類。


image.png

PECS

PECS: producer-extends,consumer-super

也就是說生產(chǎn)者使用extends,消費(fèi)者使用super。具體可以看看java.util.Collections#copy方法:

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();  // in T, 寫入dest數(shù)據(jù)
            ListIterator<? extends T> si=src.listIterator();  // out T, 讀取src數(shù)據(jù)
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }

2. Kotlin中的泛型

Kotlin中的泛型使用的正是前面說的PECS(生產(chǎn)者消費(fèi)者)概念,在上面說到的java.util.Collections#copy方法中:

  • List<? extends T> src是生產(chǎn)者,也就是數(shù)據(jù)提供者,在Kotlin中叫out T。生產(chǎn)者不能寫入數(shù)據(jù),只能讀取。
  • List<? super T> dest是消費(fèi)者,也就是吞掉一些數(shù)據(jù),或者說會(huì)有數(shù)據(jù)寫入到該對(duì)象中,在Kotlin中叫in T,消費(fèi)者可以寫入T或者T的子類。

在Kotlin中,我們把那些只能保證讀取數(shù)據(jù)時(shí)類型安全的對(duì)象叫做生產(chǎn)者,用 out T標(biāo)記;把那些只能保證寫入數(shù)據(jù)安全時(shí)類型安全的對(duì)象叫做消費(fèi)者,用 in T標(biāo)記。

2.1 聲明處型變

Kotlin對(duì)Java泛型最大的改動(dòng)就是增加了聲明處型變,下面的例子:

interface Source<T> { 
  T nextT(); 
} 

void demo(Source<String> str) { 
  // Java 中這種寫法是不允許的 
  Source<Object> obj = str;
   /*...*/ 
}

因?yàn)镴ava泛型不可型變的,所以Source<String>不是Source<Object>的子類。但是在Kotlin中你就能做到,用Kotin改寫下:

interface Source<out T>{
     fun nextT() : T
}

fun demo(str: Source<String>) {
     val obj:Source<Any> = str  //works
}

因?yàn)镾ource這個(gè)接口只有一個(gè)讀取數(shù)據(jù)的方法,可以視為生產(chǎn)者,所以把這個(gè)接口的類型參數(shù)聲明為生產(chǎn)者后,就可以實(shí)現(xiàn)安全的類型協(xié)變了。Kotlin中有大量的聲明處協(xié)變,比如:Iterable接口聲明:

public interface Iterable<out T> {
    /**
     * Returns an iterator over the elements of this object.
     */
    public operator fun iterator(): Iterator<T>
}

因?yàn)镃ollection和Map接口都繼承了Iterable接口,而Iterable被聲明成了生產(chǎn)者(out T)。所以Collection和Map以及子類都可以實(shí)現(xiàn)安全的類型協(xié)變:

val c: List<Number> = listOf(1, 2, 3)

2.2 類型投影

類似Java中的無界類型通配符?, Kotlin 也有對(duì)應(yīng)的星投影語法*
例如,如果類型被聲明為 interface Function <in T, out U>,我們有以下星投影:

  • Function<*, String> 表示 Function<in Nothing, String>
  • Function<Int, *> 表示 Function<Int, out Any?>
  • Function<*, *> 表示 Function<in Nothing, out Any?>

*投影跟 Java 的原始類型類似,不過是安全的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。