鍍金池/ 教程/ Android/ Kotlin泛型
Kotlin內聯函數
Kotlin開發(fā)環(huán)境設置(Eclipse)
Kotlin調用Java代碼
Kotlin使用Ant
Kotlin編譯器插件
Kotlin相等性
Kotlin JavaScript模塊
編寫Kotlin代碼文檔
Kotlin返回和跳轉
Kotlin異常處理
Kotlin可見性修飾符
Kotlin委托
Kotlin委托屬性
Kotlin編碼約定/編碼風格
Kotlin基礎語法
使用Kotlin進行服務器端開發(fā)
Kotlin接口
Kotlin反射
Kotlin類型別名
Kotlin枚舉類
Kotlin當前版本是多少?
Kotlin注解處理工具
Kotlin類型的檢查與轉換
Kotlin屬性和字段
Kotlin類型安全的構建器
Kotlin相比Java語言有哪些優(yōu)點?
Kotlin JavaScript反射
Kotlin 是什么?
Kotlin泛型
Kotlin慣用語法
Kotlin與OSGi
Kotlin數據類型
Kotlin是面向對象還是函數式語言?
Kotlin動態(tài)類型
Kotlin協(xié)程
Kotlin操作符符重載
Kotlin使用Gradle
Kotlin密封類
Kotlin兼容性
Kotlin集合
Kotlin調用JavaScript
Kotlin null值安全
Kotlin函數
Kotlin開發(fā)環(huán)境設置(IntelliJ IDEA)
Kotlin嵌套類
Kotlin控制流程
Kotlin和Java語言比較
Kotlin 與 Java 語言兼容嗎?
Kotlin教程
Kotlin類和繼承
Kotlin對象表達式和對象聲明
JavaScript中調用Kotlin
Kotlin區(qū)間/范圍
Kotlin數據類
Kotlin lambda表達式
Kotlin是免費的嗎?
Kotlin包
使用Kotlin進行Android開發(fā)
在Java中調用Kotlin代碼
Kotlin this表達式
使用Kotlin進行JavaScript開發(fā)
Kotlin擴展
Kotlin解構聲明
Kotlin注解
Kotlin使用Maven

Kotlin泛型

與 Java 類似,Kotlin 中的類也可以有類型參數:

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

一般來說,要創(chuàng)建這樣類的實例,我們需要提供類型參數:

val box: Box<Int> = Box<Int>(1)

但是如果類型參數可以推斷出來,例如從構造函數的參數或者從其他途徑,允許省略類型參數:

val box = Box(1) // 1 具有類型 Int,所以編譯器知道我們說的是 Box<Int>。

型變

Java 類型系統(tǒng)中最棘手的部分之一是通配符類型(參見 Java Generics FAQ)。
而 Kotlin 中沒有。 相反,它有兩個其他的東西:聲明處型變(declaration-site variance)與類型投影(type projections)。

首先,讓我們思考為什么 Java 需要那些神秘的通配符。在 Effective Java 解釋了該問題——第28條:利用有限制通配符來提升 API 的靈活性。
首先,Java 中的泛型是不型變的,這意味著 List<String>不是 List<Object> 的子類型。
為什么這樣? 如果 List 不是不型變的,它就沒
比 Java 的數組好到哪去,因為如下代碼會通過編譯然后導致運行時異常:

// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // ?。?!即將來臨的問題的原因就在這里。Java 禁止這樣!
objs.add(1); // 這里我們把一個整數放入一個字符串列表
String s = strs.get(0); // !??! ClassCastException:無法將整數轉換為字符串

因此,Java 禁止這樣的事情以保證運行時的安全。但這樣會有一些影響。例如,考慮 Collection 接口中的 addAll()
方法。該方法的簽名應該是什么?直覺上,我們會這樣:

// Java
interface Collection<E> …… {
  void addAll(Collection<E> items);
}

但隨后,我們將無法做到以下簡單的事情(這是完全安全):

// Java
void copyAll(Collection<Object> to, Collection<String> from) {
  to.addAll(from); // !??!對于這種簡單聲明的 addAll 將不能編譯:
                   //       Collection<String> 不是 Collection<Object> 的子類型
}

(在 Java 中,我們艱難地學到了這個教訓,參見Effective Java,第25條:列表優(yōu)先于數組)

這就是為什么 addAll() 的實際簽名是以下這樣:

// Java
interface Collection<E> …… {
  void addAll(Collection<? extends E> items);
}

通配符類型參數 ? extends E 表示此方法接受 E一些子類型對象的集合,而不是 E 本身。
這意味著我們可以安全地從其中(該集合中的元素是 E 的子類的實例)讀取 E,但不能寫入,
因為我們不知道什么對象符合那個未知的 E 的子類型。
反過來,該限制可以讓Collection<String>表示為Collection<? extends Object>的子類型。
簡而言之,帶 extends 限定(上界)的通配符類型使得類型是協(xié)變的(covariant)。

理解為什么這個技巧能夠工作的關鍵相當簡單:如果只能從集合中獲取項目,那么使用 String 的集合,
并且從其中讀取 Object 也沒問題 。反過來,如果只能向集合中 放入 項目,就可以用
Object 集合并向其中放入 String:在 Java 中有 List<? super String>List<Object> 的一個超類

后者稱為逆變性(contravariance),并且對于 List <? super String> 你只能調用接受 String 作為參數的方法
(例如,你可以調用 add(String) 或者 set(int, String)),當然
如果調用函數返回 List<T> 中的 T,你得到的并非一個 String 而是一個 Object

Joshua Bloch 稱那些你只能從中讀取的對象為生產者,并稱那些你只能寫入的對象為消費者。他建議:“為了靈活性最大化,在表示生產者或消費者的輸入參數上使用通配符類型”,并提出了以下助記符:

PECS 代表生產者-Extens,消費者-Super(Producer-Extends, Consumer-Super)。

注意:如果你使用一個生產者對象,如 List<? extends Foo>,在該對象上不允許調用 add()set()。但這并不意味著
該對象是不可變的:例如,沒有什么阻止你調用 clear()從列表中刪除所有項目,因為 clear()
根本無需任何參數。通配符(或其他類型的型變)保證的唯一的事情是類型安全。不可變性完全是另一回事。

聲明處型變

假設有一個泛型接口 Source<T>,該接口中不存在任何以 T 作為參數的方法,只是方法返回 T 類型值:

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

那么,在 Source <Object> 類型的變量中存儲 Source <String> 實例的引用是極為安全的——沒有消費者-方法可以調用。但是 Java 并不知道這一點,并且仍然禁止這樣操作:

// Java
void demo(Source<String> strs) {
  Source<Object> objects = strs; // !?。≡?Java 中不允許
  // ……
}

為了修正這一點,我們必須聲明對象的類型為 Source<? extends Object>,這是毫無意義的,因為我們可以像以前一樣在該對象上調用所有相同的方法,所以更復雜的類型并沒有帶來價值。但編譯器并不知道。

在 Kotlin 中,有一種方法向編譯器解釋這種情況。這稱為聲明處型變:我們可以標注 Source類型參數 T 來確保它僅從 Source<T> 成員中返回(生產),并從不被消費。
為此,我們提供 out 修飾符:

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

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // 這個沒問題,因為 T 是一個 out-參數
    // ……
}

一般原則是:當一個類 C 的類型參數 T 被聲明為 out 時,它就只能出現在 C 的成員的輸出-位置,但回報是 C<Base> 可以安全地作為
C<Derived>的超類。

簡而言之,他們說類 C 是在參數 T 上是協(xié)變的,或者說 T 是一個協(xié)變的類型參數。
你可以認為 CT生產者,而不是 T消費者。

out修飾符稱為型變注解,并且由于它在類型參數聲明處提供,所以我們講聲明處型變。
這與 Java 的使用處型變相反,其類型用途通配符使得類型協(xié)變。

另外除了 out,Kotlin 又補充了一個型變注釋:in。它使得一個類型參數逆變:只可以被消費而不可以
被生產。逆變類的一個很好的例子是 Comparable

abstract class Comparable<in T> {
    abstract fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    x.compareTo(1.0) // 1.0 擁有類型 Double,它是 Number 的子類型
    // 因此,我們可以將 x 賦給類型為 Comparable <Double> 的變量
    val y: Comparable<Double> = x // OK!
}

我們相信 inout 兩詞是自解釋的(因為它們已經在 C# 中成功使用很長時間了),
因此上面提到的助記符不是真正需要的,并且可以將其改寫為更高的目標:

存在性(The Existential) 轉換:消費者 in, 生產者 out! :-)

類型投影

使用處型變:類型投影

將類型參數 T 聲明為 out 非常方便,并且能避免使用處子類型化的麻煩,但是有些類實際上不能限制為只返回 T
一個很好的例子是 Array:

class Array<T>(val size: Int) {
    fun get(index: Int): T { ///* …… */ }
    fun set(index: Int, value: T) { ///* …… */ }
}

該類在 T 上既不能是協(xié)變的也不能是逆變的。這造成了一些不靈活性。考慮下述函數:

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

這個函數應該將項目從一個數組復制到另一個數組。讓我們嘗試在實踐中應用它:

val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3)
copy(ints, any) // 錯誤:期望 (Array<Any>, Array<Any>)

這里我們遇到同樣熟悉的問題:Array <T>T 上是不型變的,因此 Array <Int>Array <Any> 都不是
另一個的子類型。為什么? 再次重復,因為 copy 可能做壞事,也就是說,例如它可能嘗試一個 String 到 from,
并且如果我們實際上傳遞一個 Int 的數組,一段時間后將會拋出一個 ClassCastException 異常。

那么,我們唯一要確保的是 copy() 不會做任何壞事。我們想阻止它from,我們可以:

fun copy(from: Array<out Any>, to: Array<Any>) {
 // ……
}

這里發(fā)生的事情稱為類型投影:我們說from不僅僅是一個數組,而是一個受限制的(投影的)數組:我們只可以調用返回類型為類型參數
T 的方法,如上,這意味著我們只能調用 get()。這就是我們的使用處型變的用法,并且是對應于 Java 的 Array<? extends Object>
但使用更簡單些的方式。

你也可以使用 in 投影一個類型:

fun fill(dest: Array<in String>, value: String) {
    // ……
}

Array<in String> 對應于 Java 的 Array<? super String>,也就是說,你可以傳遞一個 CharSequence 數組或一個 Object 數組給 fill() 函數。

星投影

有時你想說,你對類型參數一無所知,但仍然希望以安全的方式使用它。
這里的安全方式是定義泛型類型的這種投影,該泛型類型的每個具體實例化將是該投影的子類型。

Kotlin 為此提供了所謂的星投影語法:

  • 對于 Foo <out T>,其中 T 是一個具有上界 TUpper 的協(xié)變類型參數,Foo <*> 等價于 Foo <out TUpper>。 這意味著當 T 未知時,你可以安全地從 Foo <*> 讀取 TUpper 的值。
  • 對于 Foo <in T>,其中 T 是一個逆變類型參數,Foo <*> 等價于 Foo <in Nothing>。 這意味著當 T 未知時,沒有什么可以以安全的方式寫入 Foo <*>。
  • 對于 Foo <T>,其中 T 是一個具有上界 TUpper 的不型變類型參數,Foo<*> 對于讀取值時等價于 Foo<out TUpper> 而對于寫值時等價于 Foo<in Nothing>。

如果泛型類型具有多個類型參數,則每個類型參數都可以單獨投影。
例如,如果類型被聲明為 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 的原始類型,但是安全。

泛型函數

不僅類可以有類型參數。函數也可以有。類型參數要放在函數名稱之前:

fun <T> singletonList(item: T): List<T> {
    // ……
}

fun <T> T.basicToString() : String {  // 擴展函數
    // ……
}

要調用泛型函數,在調用處函數名之后指定類型參數即可:

val l = singletonList<Int>(1)

泛型約束

能夠替換給定類型參數的所有可能類型的集合可以由泛型約束限制。

上界

最常見的約束類型是與 Java 的 extends 關鍵字對應的 上界

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

冒號之后指定的類型是上界:只有 Comparable<T> 的子類型可以替代 T。 例如

sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子類型
sort(listOf(HashMap<Int, String>())) // 錯誤:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子類型

默認的上界(如果沒有聲明)是 Any?。在尖括號中只能指定一個上界。
如果同一類型參數需要多個上界,我們需要一個單獨的 where-子句:

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