轉向Kotlin——泛型

更多精彩內容,歡迎關注個人微信公衆號——Android機動車數組

不管是Java仍是Kotlin,泛型都是一個很是重要的概念,簡單的泛型應用很容易理解,不過也有理解起來麻煩的時候。安全

泛型基礎

在瞭解Kotlin的泛型以前,先來看看Java中的泛型:bash

舉個栗子:在JDK中,有一類列表對象,這些對象對應的類都實現了List接口。List中能夠保存任何對象:微信

List list=new ArrayList();
list.add(55);
list.add("hello");
複製代碼

上面的代碼中,List中保存了Integer和String兩種類型值。儘管這樣作是能夠保存任意類型的對象,但每一個列表元素就失去了原來對象的特性,由於在Java中任何類都是Object的子類,這樣作的弊端就是原有對象類型的屬性和方法都不能再使用了。數據結構

但在定義List時,能夠指定元素的數據類型,那麼這個List就再也不是通用的了,只能存儲一種類型的數據。JDK1.5以後引入了一個新的概念:泛型。函數

所謂泛型,就是指在定義數據結構時,只指定類型的佔位符,待到使用該數據結構時再指定具體的數據類型:ui

public class Box<T> {
    
    private T t;

    public Box(T t) {
        this.t = t;
    }
}


Box<Integer> box=new Box(2);
複製代碼

在Kotlin中一樣也支持泛型,下面是Kotlin實現上面一樣的功能:this

class Box<T>(t: T) {
    var value = t
}

var box: Box<String> = Box("haha")
複製代碼

類型變異

Java中

Java泛型中有類型通配符這一機制,不過在Kotlin泛型中,沒有通配符。spa

先看一個Java的栗子:code

List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;  // 編譯錯誤
複製代碼

以上代碼編譯錯誤。這裏有兩個List對象,很明顯String是Object的子類,但遺憾的是,Java編譯器並不認爲List < String >和List < Object> 有任何關係,直接將list1賦值給list2是會編譯報錯的,這是因爲List的父接口是Collection:

public interface Collection<E> extends Iterable<E> {..}
複製代碼

爲了解決這個問題,Java泛型提供了問號(?)通配符來解決這個問題。例如Collection接口中的addAll方法定義以下:

boolean addAll(Collection<? extends E> var1);
複製代碼

? extends E 表示什麼呢,表示任何父類是E(或者E的任何子類和本身)都知足條件,這樣就解決了List < String > 給List < Object> 賦值的問題。

出了extend還有super,這裏再也不過多介紹。

Kotlin中

Kotlin泛型並無提供通配符,取而代之的是out和in關鍵字。用out聲明的泛型佔位符只能在獲取泛型類型值得地方,如函數的返回值。用in聲明的泛型佔位符只能在設置泛型類型值的地方,如函數的參數。

咱們習慣將只能讀取的對象稱爲生產者,將只能設置的對象稱爲消費者。若是你使用一個生產者對象,將沒法對這個對象調用add或set等方法,但這並不表明這個對象的值是不變的。例如,你徹底能夠調用clear方法來刪除List中的全部元素,由於clear方法不須要任何參數。

通配符類型(或者其餘任何的類型變異),惟一可以確保的僅僅是類型安全

abstract class Source<out T> {
    abstract fun func(): T
}

abstract class Comparable<in T> {
    abstract fun func(t: T)
}
複製代碼

類型投射

若是將泛型類型T聲明爲out,就能夠將其子類化(List < String > 是List < Object> 的子類型),這是很是方便的。若是咱們的類可以僅僅只返回T類型的值,那麼的確能夠將其子類化。但若是在聲明泛型時未使用out聲明T呢?

如今有一個Array類以下:

class Array<T>(val size: Int) {
    fun get(index: Int): T {
    }

    fun set(index: Int, t: T) {
    }
}
複製代碼

此類中的T既是get方法的返回值,又是set方法的參數,也就是說Array類既是T的生產者,也是T的消費者,這樣的類就沒法進行子類化。

fun copy(from: Array<Any>, to: Array<Any>) {
    assert(from.size == to.size)
    for(i in from.indices){
        to[i]=from[i]
    }
}
複製代碼

這個copy方法,就是將一個Array複製到另外一個Array中,如今嘗試使用一下:

val ints: Array<Int> = arrayOf(1, 2, 3)
val any: Array<Any> = Array(3)
copy(ints, any)  // 編譯錯誤,由於Array<Int> 不是Array<Any>的子類型
複製代碼

Array< T > 對於類型參數T是不可變的,所以Array< Int> 和Array< Any>他們沒有任何關係,爲何呢?由於copy可能會進行一些不安全的操做,也就是說,這個函數可能會試圖向from中寫入數據,這樣可能會拋類型轉換異常。

能夠這樣:

fun copy(from: Array<out Any>, to: Array<Any>) {
	...
}
複製代碼

將from的泛型使用out修飾。

這種聲明在Kotlin中稱爲類型投射:from不是一個單純的數組,而是一個被限制(投射)的數組,咱們只能對這個數組調用那些返回值爲類型參數T的函數,在這個例子中,咱們只能調用get方法,這就是咱們事先使用處的類型變異的方案。

in關鍵字也是同理。

泛型函數

不只類能夠有泛型參數,函數同樣能夠有泛型參數。泛型參數放在函數名稱以前

fun <T> getList(item: T): List<T> {
    ...
}
複製代碼

調用泛型函數時,應該在函數名稱以後指定調用端類型參數。

val value = getList<Int>(1)
複製代碼

泛型約束

對於一個給定的泛型參數,所容許使用的類型,能夠經過泛型約束來限制,最多見的約束是上界,與Java中的extends相似。

fun <T : Any> sort(list: List<T>) {

} 
複製代碼

冒號以後指定的類型就是泛型參數的上界:對於泛型參數T,容許使用Any的子類型。若是沒有指定,則默認使用的上界類型是「Any?」,在定義泛型參數的尖括號內,值容許定義惟一一個上界。

小結

Kotlin泛型是在Java泛型的基礎上進行了改進,變得更好用,更安全,儘管上述的泛型技術不必定都用得上,但對於全面瞭解Kotlin泛型會起到很大做用。

更多精彩內容,歡迎關注個人微信公衆號——Android機動車

這裏寫圖片描述
相關文章
相關標籤/搜索