1. 程式人生 > >Kotlin-泛型

Kotlin-泛型

概述

一般類和函式,只能使用具體的型別:要麼是基本型別,要麼是自定義的類。如果要編寫可以應用於多種型別的程式碼,這種刻板的約束對程式碼的限制很大。而OOP的多型採用了一種泛化的機制,在SE 5種,Java引用了泛型。泛型,即“引數化型別”。一提到引數,最熟悉的就是定義方法時有形參,然後呼叫此方法時傳遞實參。那麼引數化型別怎麼理解呢?顧名思義,就是將型別由原來的具體的型別引數化,類似於方法中的變數引數,此時型別也定義成引數形式(可以稱之為型別形參),然後在使用/呼叫時傳入具體的型別(型別實參)。

在Kotlin中,依然可以使用泛型,解耦類與函式與所用型別之間的約束,甚至是使用方法都與Java一致。

泛型類

宣告一個泛型類

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

通常, 要建立這樣一個類的例項, 我們需要指定型別引數:

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

但是, 如果型別引數可以通過推斷得到, 比如, 通過構造器引數型別, 或通過其他手段推斷得到, 此時允許省略型別引數:

val box = Box(1) // 1 的型別為 Int, 因此編譯器知道我們建立的例項是 Box&lt;Int> 型別

泛型函式

泛型函式與其所在的類是否是泛型沒有關係。泛型函式使得該函式能夠獨立於其所在類而產生變化。在<Thinking in Java>有這麼一句話:無論何時只要你能做到,你就應該儘量使用泛型方法,也就是說如果使用泛型方法可以取代將整個類泛型化,那麼就應該只使用泛型方法,因為它可以使事情更明白。這種泛型使用思想,在Kotlin中依然可以延續。

下面我們聲明瞭一個泛型函式doPrintln,當T是一個Int型別時,列印其個位的值;如果T是String型別,將字母全部大寫輸出;如果是其他型別,列印“T is not Int and String”。

fun main(args: Array<String>) {
    val age = 23
    val name = "Jone"
    val person = true

    doPrintln(age) // 列印:3
    doPrintln(name) // 列印:JONE
    doPrintln(person) // 列印:T is not Int and String
}

fun <T> doPrintln(content: T) {

    when (content) {
        is Int -> println(content % 10) 
        is String -> println(content.toUpperCase())
        else -> println("T is not Int and String")
    }
}

注:

  1. 型別引數放在函式名稱之前。
  2. 如果在呼叫處明確地傳入了型別引數, 那麼型別引數應該放在函式名稱 之後。如果不傳入引數型別,編譯器會根據傳入的值自動推斷引數型別。

擦除的神祕之處

下面我們先看一段程式碼:

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


fun main(args: Array<String>) {
    var boxInt = Box<Int>(10)
    var boxString = Box<String>("Jone")

    println(boxInt.javaClass) // 列印:class com.teaphy.generic.Box
    println(boxString.javaClass) // 列印:class com.teaphy.generic.Box
}

現聲明瞭一個泛型類Box<T>,在不同的型別的型別在行為方面肯定不一樣,但是在我們獲取其所在類時,我們只是得到了“class com.teaphy.generic.Box”。在這裡我們不得不面對一個殘酷的現實:在泛型內部,無法獲得任何有關泛型引數型別的資訊。

不管是Java還是Kotlin,泛型都是使用擦除來實現的,這意味著當你在使用泛型時,任務具體的型別資訊都被擦除的,你唯一知道的就是你再使用一個物件。比如,Box<String>和Box<Int>在執行時是想的型別,都是Box的例項。在使用泛型時,具體型別資訊的擦除是我們不不懂得不面對的,在Kotlin中也為我們提供了一些可供參考的解決方案:

  • 型別協變
  • 型別投射
  • 泛型約束

型別協變

在型別宣告時,使用協變註解修飾符(in或者out)。於這個註解出現在型別引數的宣告處, 因此我們稱之為宣告處的型別變異。如果在使用泛型時,使用了該型別編譯了會有什麼效果呢?

假設我們有一個泛型介面Source<in T, out R>, 其中T由協變註解in修飾,R由協變註解Out修飾.

internal interface Source<in T, out R> {
    fun mapT(t: T): Unit
    fun nextR(): R
}
  • in T: 來確保Source的成員函式只能消費T型別,而不能返回T型別
  • out R:來確保Source的成員函式只能返回R型別,而不能消費R型別

從上面的解釋中,我們可以清楚的知道了協變註解in和out的用意,其實際上是定義了型別引數在該類或者介面的用途,是用來消費的還是用來返回的,對其做了相應的限定。

型別投射

上面我們已經瞭解到了協變註解in和out的用意,下面我們將會用in和out,做一件有意義的事,看下面程式碼

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

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

對於copy函式中中,from的泛型引數使用了協變註解out修飾,意味著該引數不能在該函式中消費,也就是說在該函式中禁止對該引數進行任何操作。

對於fill函式中,dest的泛型引數使用了協變註解in修飾,Array<in String>與Java的 Array<? super String> 相同, 也就是說, 你可以使用CharSequence陣列,或者 Object 陣列作為 fill() 函式的引數

這種宣告在Kotlin中稱為型別投射(type projection),型別投射的主要用於對引數做了相對因的限定,避免了對該引數類的不安全操作。

星號投射

有些時候, 你可能想表示你並不知道型別引數的任何資訊, 但是仍然希望能夠安全地使用它. 這裡所謂”安全地使用”是指, 對泛型型別定義一個型別投射, 要求這個泛型型別的所有的實體例項, 都是這個投射的子型別.

對於這個問題, Kotlin 提供了一種語法, 稱為 星號投射(star-projection):

  • 假如型別定義為 Foo<out T> , 其中 T 是一個協變的型別引數, 上界(upper bound)為 TUpper ,Foo<> 等價於 Foo<out TUpper> . 它表示, 當 T 未知時, 你可以安全地從 Foo<> 中 讀取TUpper 型別的值.
  • 假如型別定義為 Foo<in T> , 其中 T 是一個反向協變的型別引數, Foo<> 等價於 Foo<inNothing> . 它表示, 當 T 未知時, 你不能安全地向 Foo<> 寫入 任何東西.
  • 假如型別定義為 Foo<T> , 其中 T 是一個協變的型別引數, 上界(upper bound)為 TUpper , 對於讀取值的場合, Foo<*> 等價於 Foo<out TUpper> , 對於寫入值的場合, 等價於 Foo<in Nothing> .

如果一個泛型型別中存在多個型別引數, 那麼每個型別引數都可以單獨的投射. 比如, 如果型別定義為interface Function<in T, out U> , 那麼可以出現以下幾種星號投射:

  1. Function<*, String> , 代表 Function<in Nothing, String> ;
  2. Function<Int, *> , 代表 Function<Int, out Any?> ;
  3. Function<, > , 代表 Function<in Nothing, out Any?> .

注意: 星號投射與 Java 的原生型別(raw type)非常類似, 但可以安全使用

泛型約束

對於一個給定的型別引數, 所允許使用的型別, 可以通過泛型約束(generic constraint) 來限制。

上界

最常見的約束是 上界(upper bound):

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

冒號之後指定的型別就是型別引數的 上界(upper bound): 對於型別引數 T , 只允許使用 Comparable<T>的子型別. 比如:

sort(listOf(1, 2, 3)) // 正確: Int 是 Comparable&lt;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() }
}

相關推薦

kotlin 中型別投射

fun main(arg: Array<String>) { var ints:Array<Int> = arrayOf(1, 2, 3) val any =Array<Any>(3){} copy(ints,any)//編譯錯誤,因為A

kotlin中星號投射

如果一個泛型型別中存在多個型別的引數,那麼每個型別的引數都可以單獨投射,例如:如果型別定義為:"interface Function<in T,out>",那麼可以出現以下的幾種星號投射: Function<*,String>,代表Function<in Nothing,

Kotlin-

概述 一般類和函式,只能使用具體的型別:要麼是基本型別,要麼是自定義的類。如果要編寫可以應用於多種型別的程式碼,這種刻板的約束對程式碼的限制很大。而OOP的多型採用了一種泛化的機制,在SE 5種,Java引用了泛型。泛型,即“引數化型別”。一提到引數,最熟悉的

Kotlin 的協變和逆變

Kotlin 泛型Kotlin 泛型的基本語法類似於 Java ,不過出於型變安全,不支援 Java 中的<? extends T>,<?super T> 萬用字元型變約束,而是採用類似 C# 的 in,out 用於支援協變和逆變,這同時避免了處理子型

Kotlin 中的 in 和 out

價值 | 思考 | 共鳴簡評:在 Kotlin 中使用泛型你會注意到其中引入了 in 和 out

kotlin 學習筆記

泛型是什麼: 1.泛型是函式定義的時候未指定具體型別,而在使用的時候可以傳遞多種具體型別。 2.泛型是java和kotlin中的概念,在其他類似程式語言中叫做引數多型(parametric polymorphism)或者模板。 注:本文翻譯自英文書《Pro

Kotlin

any 生產 數根 out bool 給定 如果 實體 bound 泛型,即 "參數化類型",將類型參數化,可以用在類,接口,方法上。 與 Java 一樣,Kotlin 也提供泛型,為類型安全提供保證,消除類型強轉的煩惱。 聲明一個泛型類: class Box<

Kotlin基礎-

-- 大於 包含 取出 占位符 art list font main /** 泛型Generics* 讓一個類型能被“廣泛”使用,即通用化,稱之為“泛型”* 一般用於函數的參數類型定義,讓函數更通用** */fun main(args: Array<String>

Kotlin中的

部落格地址:sguotao.top/Kotlin-2018… 一個生產環境問題引發的思考。 在JDK1.5之前,生產環境中總是會出現這樣類似的問題: List list = new ArrayList (); list.add ("foo"); list.add (new Integer (42));

教你如何攻克Kotlin變的難點(上篇)

簡述: Kotlin中泛型相關的文章也幾乎接近尾聲,但到後面也是泛型的難點和重點。相信有很多初學者對Kotlin中的泛型型變都是一知半解,比如我剛開始接觸就是一臉懵逼,概念太多了,而且每個概念和後面都是相關的,只要前面有一個地方未理解後面的難點更是越來越看不懂。Kotlin的泛型比Java中的泛型多了一些新的

教你如何攻克Kotlin變的難點(下篇)

簡述: 前幾天我們一起為Kotlin中的泛型型變做了一個很好的鋪墊,深入分析下型別和類,子型別和子類之間的關係、什麼是子型別化關係以及型變存在的意義。那麼今天將會講點更刺激的東西,也就是Kotlin泛型型變中最為難理解的地方,那就是Kotlin中的協變、逆變、不變。雖然很難理解,但是有了上篇文章基礎教你如何攻

教你如何攻克Kotlin變的難點(應用篇)

簡述: 這是泛型型變最後一篇文章了,也是泛型介紹的最後一篇文章。順便再扯點別的,上週去北京參加了JetBrains 2018開發者日,主要是參加Kotlin專場。個人感覺收穫還是挺多的,bennyHuo和彥偉老師精彩演講確實傳遞很多幹貨啊,當然還有Hali佈道師大佬帶來了的Kotlin1

教你如何攻克Kotlin變的難點(實踐篇)

簡述: 這是泛型型變最後一篇文章了,也是泛型介紹的最後一篇文章。順便再扯點別的,上週去北京參加了JetBrains 2018開發者日,主要是參加Kotlin專場。個人感覺收穫還是挺多的,bennyHuo和彥偉老師精彩演講確實傳遞很多幹貨啊,當然還有Hali佈道師大佬帶來了的Kotlin1.3版本的新特性以及G

Kotlin自學之旅(十)

文章目錄宣告泛型類型變型變的概念不變協變逆變小結宣告處型變使用處型變星投影總結 宣告泛型函式 泛型允許你定義帶型別形參的型別。當這種型別的例項被創建出來的時候,型別形參被替換成稱為型別實參的具體型別。Kotlin中泛型的使用與宣告和Java很相似,我們宣告一個泛

Kotlin 中關鍵字out和in和Java中的的比較學習

由Kotlin 中關鍵字out和in聯想到Java中的泛型 最近在學習kotlin語法,發現了kotlin中的關鍵字out和in,感覺甚是新穎,就細細琢磨了一下,發現這兩個關鍵字和Java中的泛型邊界有著千絲萬縷的聯絡。那麼接下來我們就先談談Java中的泛型,

kotlin的使用

泛型 我們最先了解到的泛型應該是來自於Java,在Java SE 1.5的時候,首次提出了泛型的概念,泛型的本質是引數化的型別,也就是說傳遞操作的資料型別被指定為一個引數,泛型可以被應用於類(泛型類)、介面(泛型介面)、方法(泛型方法)。Java

掃盲:Kotlin

# 引子 相信總是有很多同學,總是在抱怨泛型無論怎麼學習,都只是停留在一個簡單使用的水平,所以一直為此而備受苦惱。 Kotlin 作為一門能和 Java 相互呼叫的語言,自然也支援泛型,不過 Kotlin 的新關鍵字 `in` 和 `out` 卻總能繞暈一部分人,歸根結底,還是因為 Java 的泛型基本功沒

Mooc軟件工程-02面向對象

組成 ima 固然是 name 成員 專註 對象 指示 類的繼承 1 內聚   模塊     語義上連續的一系列語句, 由邊界符界定, 並且有一個聚集標識符   常見的模塊     在面向對象中的 類, 方法     在面向過程的 函數   模塊功能單一, 內部

集合的使用

column func prot 技術分享 empty 創建 sha 循環 sdn 對於機房收費系統的重構。從大的方面來看。無非就是對於數據庫的四個操作。增刪改查。而且我們用的是三層架構進行重構。D層用來和數據庫打交道。進行這四個操作就須要有返回值,增刪改在這裏

Java擦除

類型信息 png ive over tool 創建 edit sid 註意點 Java泛型擦除: 什麽是泛型擦除? 首先了解一下什麽是泛型?我個人的理解:因為集合中能夠存儲隨意類型的對象。可是集合中最先存儲的對象類型一旦確定後,就不能在存儲其它類型的