在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Android/ Kotlin泛型
Kotlin內(nèi)聯(lián)函數(shù)
Kotlin開發(fā)環(huán)境設置(Eclipse)
Kotlin調(diào)用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數(shù)據(jù)類型
Kotlin是面向?qū)ο筮€是函數(shù)式語言?
Kotlin動態(tài)類型
Kotlin協(xié)程
Kotlin操作符符重載
Kotlin使用Gradle
Kotlin密封類
Kotlin兼容性
Kotlin集合
Kotlin調(diào)用JavaScript
Kotlin null值安全
Kotlin函數(shù)
Kotlin開發(fā)環(huán)境設置(IntelliJ IDEA)
Kotlin嵌套類
Kotlin控制流程
Kotlin和Java語言比較
Kotlin 與 Java 語言兼容嗎?
Kotlin教程
Kotlin類和繼承
Kotlin對象表達式和對象聲明
JavaScript中調(diào)用Kotlin
Kotlin區(qū)間/范圍
Kotlin數(shù)據(jù)類
Kotlin lambda表達式
Kotlin是免費的嗎?
Kotlin包
使用Kotlin進行Android開發(fā)
在Java中調(diào)用Kotlin代碼
Kotlin this表達式
使用Kotlin進行JavaScript開發(fā)
Kotlin擴展
Kotlin解構聲明
Kotlin注解
Kotlin使用Maven

Kotlin泛型

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

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

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

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

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

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 的數(shù)組好到哪去,因為如下代碼會通過編譯然后導致運行時異常:

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

因此,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)先于數(shù)組)

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

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

通配符類型參數(shù) ? 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> 你只能調(diào)用接受 String 作為參數(shù)的方法
(例如,你可以調(diào)用 add(String) 或者 set(int, String)),當然
如果調(diào)用函數(shù)返回 List<T> 中的 T,你得到的并非一個 String 而是一個 Object。

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

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

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

聲明處型變

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

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

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

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

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

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

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

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

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

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

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

另外除了 out,Kotlin 又補充了一個型變注釋:in。它使得一個類型參數(shù)逆變:只可以被消費而不可以
被生產(chǎn)。逆變類的一個很好的例子是 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 兩詞是自解釋的(因為它們已經(jīng)在 C# 中成功使用很長時間了),
因此上面提到的助記符不是真正需要的,并且可以將其改寫為更高的目標:

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

類型投影

使用處型變:類型投影

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

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

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

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

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

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 的數(shù)組,一段時間后將會拋出一個 ClassCastException 異常。

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

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

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

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

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

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

星投影

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

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

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

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

泛型函數(shù)

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

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

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

要調(diào)用泛型函數(shù),在調(diào)用處函數(shù)名之后指定類型參數(shù)即可:

val l = singletonList<Int>(1)

泛型約束

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

上界

最常見的約束類型是與 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?。在尖括號中只能指定一個上界。
如果同一類型參數(shù)需要多個上界,我們需要一個單獨的 where-子句:

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