更多精彩內容,歡迎關注個人微信公衆號——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泛型中有類型通配符這一機制,不過在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泛型並無提供通配符,取而代之的是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機動車