Java & Groovy & Scala & Kotlin - 27.泛型

Overview

泛型使類型參數(shù)化變得可能。在聲明類或接口時(shí)康聂,可以使用自定義的占位符來表示類型贰健,在運(yùn)行時(shí)由傳入的具體類型進(jìn)行替換。泛型的引入讓集合變得更加好用恬汁,使很多錯(cuò)誤在編譯時(shí)就能被發(fā)現(xiàn)伶椿,也省去了一些強(qiáng)制轉(zhuǎn)換的麻煩。

Java 篇

泛型是 Java 1.5才引進(jìn)的特性氓侧。沒有泛型的時(shí)候使用一個(gè)持有特定類型的值的類的時(shí)候是非常麻煩的

例:

public class ObjectCapture {
    private Object object;
    public ObjectCapture(Object o) {
        this.object = o;
    }
    public void set(Object object) {
        this.object = object;
    }
    public Object get() {
        return object;
    }
}

使用以上類

ObjectCapture integerObjectCapture = new ObjectCapture(10);
assert 10 == (Integer) integerObjectCapture.get();

沒有泛型的時(shí)候在取數(shù)據(jù)時(shí)必須進(jìn)行強(qiáng)制轉(zhuǎn)換脊另,但是此時(shí)根本無法保證 之前使用的 ObjectCapture 保存的是 Integer 類型的值,如果是其它類型的話约巷,程序就會(huì)直接掛掉偎痛,而且這種錯(cuò)誤只有運(yùn)行時(shí)才能發(fā)現(xiàn)。

創(chuàng)建泛型

類型參數(shù)使用 <類型參數(shù)名> 作為類型的占位符独郎。

public class Capture<T> {
    private T t;
    public Capture(T t) {
        this.t = t;
    }
    public void set(T t) {
        this.t = t;
    }
    public T get() {
        return t;
    }
}

Java 中最常用的占位符為通用的 "T"踩麦,表示 Key 的 "K", 表示 Value 的 "V" 和表示異常的 "E"囚聚。

使用泛型

Capture<Integer> integerCapture = new Capture<>(10);
assert 10 == integerCapture.get();
Capture<String> stringCapture = new Capture<>("Hi");
assert "Hi".equals(stringCapture.get());

以上分別用 IntegerString 作為傳入的類型參數(shù)靖榕,如果向這兩個(gè)對(duì)象傳入不符合的類型時(shí)編譯器就會(huì)理解報(bào)錯(cuò),此外取數(shù)據(jù)時(shí)也不用進(jìn)行強(qiáng)制轉(zhuǎn)換顽铸,比起沒有泛型時(shí)要方便很多茁计。

類型擦除

Java 的泛型是在編譯器層次實(shí)現(xiàn)的,所以運(yùn)行時(shí)有關(guān)泛型的信息都會(huì)被丟失,這被稱作類型擦除星压。也就是說上節(jié)例子中的 Capture<Integer>Capture<String> 在運(yùn)行時(shí)都是 Capture 類型践剂,沒有任何區(qū)別。

協(xié)變與逆變

如果 Capture<String> 被看做是 Capture<Object> 的子類型娜膘,則稱這種特性為協(xié)變逊脯。相反情況則稱為逆變。

協(xié)變

在 Java 中竣贪,協(xié)變是默認(rèn)支持军洼,所以可以寫出以下例子:

Integer[] integers = new Integer[2];
Object[] objects = integers;

但是這樣會(huì)造成以下的問題

Date[] dates = new Date[2];
Object[] objects2 = dates;
objects2[0] = "str";

這種代碼在編寫時(shí)完全沒有問題,但是運(yùn)行時(shí)會(huì)拋出異常演怎。所以引進(jìn)泛型時(shí)就不支持協(xié)變匕争,所以以下代碼在編譯時(shí)就會(huì)報(bào)錯(cuò)。

List<Date> dateList = new ArrayList<>();
List<Object> objectList = dateList;

逆變

Java 不支持逆變爷耀。

類型通配符

由于泛型不支持協(xié)變甘桑,所以在使用泛型作為參數(shù)傳遞時(shí)會(huì)非常麻煩。

private static void foo(List<Object> list) {}

以上例子中是無法將 dateList 傳入 foo() 方法中的歹叮。解決方法是使用通配符 ?跑杭。

private static void foo(List<?> list) {}

以上例子中就能正常傳入 dateList 了。

注意:List<?> 和 List 并不是一個(gè)概念

List 是原生類型咆耿,表示不對(duì) List 的類型進(jìn)行限制德谅,可以進(jìn)行各種操作,錯(cuò)誤使用在運(yùn)行時(shí)才能發(fā)現(xiàn)票灰。

List<?> 表示 List 中存放的是某種類型的數(shù)據(jù)女阀,只是類型本身并不確定宅荤。所以無法建立 List<?> 的實(shí)例屑迂,也無法向 List 中追加任何數(shù)據(jù)。

類型參數(shù)邊界

上節(jié)說過無法向 List<?> 中追加任何數(shù)據(jù)冯键,這一做法會(huì)讓程序變得非常麻煩惹盼,解決方法就是使用類型參數(shù)邊界。類型參數(shù)邊界分為上邊界和下邊界惫确。

上邊界用于限定類型參數(shù)一定是某個(gè)類的子類手报,使用關(guān)鍵字 extends 指定。下邊界用于限定類型參數(shù)一定是某個(gè)類的超類改化,使用關(guān)鍵字 super 指定掩蛤。

上邊界無法確定容器中保存的真實(shí)類型,所以無法向其中追加數(shù)據(jù)陈肛,但是可以獲得邊界類型的數(shù)據(jù)

private static void foo3(List<? extends Num> list) {
    //        list.add(new Num(4));
    Num num = list.get(0);
}

下邊界可以追加邊界類型的數(shù)據(jù)揍鸟,但是獲得數(shù)據(jù)都只能是 Object 類型

private static void foo4(List<? super Num> list) {
    list.add(new Num(4));
    Object object = list.get(0);
}

上下邊界在這里實(shí)際是起到了協(xié)變和逆變的作用,具體可以對(duì)比 Kotlin 的例子句旱。

Groovy 篇

Groovy 中使用的就是 Java 的泛型阳藻,所以參考 Java 就行了晰奖。但是要注意的是由于 Groovy 的動(dòng)態(tài)特性,所以有些Java 會(huì)報(bào)的編譯錯(cuò)誤在 Groovy 中只有運(yùn)行時(shí)才會(huì)發(fā)現(xiàn)腥泥。

例如以下代碼在 Java 中是非法的匾南,在 Groovy 中雖然編譯通過,但運(yùn)行時(shí)會(huì)報(bào)錯(cuò)

List<Date> dateList = new ArrayList<>()
dateList.add(1)
dateList.add(new Date())

Scala 篇

創(chuàng)建泛型

類型參數(shù)使用 [類型參數(shù)名] 作為類型的占位符蛔外,而 Java 用的是 <>蛆楞。

class Capture[A](val a: A) {
}

Scala 中最常用的占位符為 "A"。

使用泛型

val integerCapture = new Capture[Int](10)
val nint10:Int = integerCapture.a
val stringCapture = new Capture[String]("Hi")
val strHi:String = stringCapture.a
println(strHi)

以上分別用 IntString 作為傳入的類型參數(shù)夹厌,如果向這兩個(gè)對(duì)象傳入不符合的類型時(shí)編譯器就會(huì)理解報(bào)錯(cuò)臊岸,此外取數(shù)據(jù)時(shí)也不用進(jìn)行強(qiáng)制轉(zhuǎn)換,比起沒有泛型時(shí)要方便很多尊流。

協(xié)變與逆變

如果 Capture<String> 被看做是 Capture<Object> 的子類型帅戒,則稱這種特性為協(xié)變。相反情況則稱為逆變崖技。
在 Scala 中逻住,這兩種特性都是默認(rèn)不支持的。

注意迎献,函數(shù)的參數(shù)是逆變的瞎访,函數(shù)的返回值是協(xié)變的。

使用協(xié)變

使用協(xié)變需要在類型前加上 +吁恍。

定義一個(gè)支持協(xié)變的類扒秸,協(xié)變類型參數(shù)只能用作輸出,所以可以作為返回值類型但是無法作為入?yún)⒌念愋?/p>

class CovariantHolder[+A](val a: A) {
  def foo(): A = {
    a
  }
}

使用該類

var strCo = new CovariantHolder[String]("a")
var intCo = new CovariantHolder[Int](3)
var anyCo = new CovariantHolder[AnyRef]("b")

//  Wrong!! Int 不是 AnyRef 的子類
// anyCo = intCo
anyCo = strCo

使用逆變

使用逆變需要在類型前加上 -冀瓦。

定義一個(gè)支持逆變的類伴奥,逆變類型參數(shù)只能用作輸入,所以可以作為入?yún)⒌念愋偷菬o法作為返回值類型

class ContravarintHolder[-A]() {
  def foo(p: A): Unit = {
  }
}

使用該類

var strDCo = new ContravarintHolder[String]()
var intDCo = new ContravarintHolder[Int]()
var anyDCo = new ContravarintHolder[AnyRef]()

//  Wrong!! AnyRef 不是 Int 的超類
// strDCo = anyDCo
strDCo = anyDCo

類型通配符

概念與 Java 基本一致翼闽,只是 Scala 使用 _ 作為通配符拾徙。

def foo2(capture: Capture[_]): Unit = {
}

類型參數(shù)邊界

上邊界用于限定類型參數(shù)一定是某個(gè)類的子類,使用符號(hào) <: 指定感局。下邊界用于限定類型參數(shù)一定是某個(gè)類的超類尼啡,使用符號(hào) >: 指定。

上邊界無法確定容器中保存的真實(shí)類型询微,所以無法向其中追加數(shù)據(jù)崖瞭,但是可以獲得邊界類型的數(shù)據(jù)

def foo3(list: collection.mutable.MutableList[_ <: Num]): Unit = {
    //    list += new Num(4)
    val num = list.head
    println(num.number)
}

下邊界可以追加邊界類型的數(shù)據(jù),但是獲得數(shù)據(jù)都只能是 Any 類型

def foo4(list: collection.mutable.MutableList[_ >: Num]): Unit = {
    list += new Num(4)
    val num = list.head
    println(num.asInstanceOf[Num].number)
}

最小類型

Scala 中在表示邊界時(shí)可以使用 Nothing 表示最小類型撑毛,即該類為所有類型的子類书聚,所有可以寫出以下代碼。

def foo5(capture: Capture[_ >: Nothing]): Unit = {
}

Kotlin 篇

創(chuàng)建泛型

同 Java。

class Capture<T>(val t: T)

使用泛型

val integerCapture = Capture(10)
val nint10 = integerCapture.t
val stringCapture = Capture("Hi")
val str = stringCapture.t

協(xié)變與逆變

在 Kotlin 中寺惫,這兩種特性都是默認(rèn)不支持的疹吃。

注意,函數(shù)的參數(shù)是逆變的西雀,函數(shù)的返回值是協(xié)變的萨驶。

使用協(xié)變

使用協(xié)變需要在類型前加上 out,相比較 Scala 使用的 + 可能更能讓人理解艇肴。

定義一個(gè)支持協(xié)變的類腔呜,協(xié)變類型參數(shù)只能用作輸出,所以可以作為返回值類型但是無法作為入?yún)⒌念愋?/p>

class CovariantHolder<out A>(val a: A) {
    fun foo(): A {
        return a
    }
}

使用該類

var strCo: CovariantHolder<String> = CovariantHolder("a")
var anyCo: CovariantHolder<Any> = CovariantHolder<Any>("b")
anyCo = strCo

使用逆變

使用逆變需要在類型前加上 in再悼。

定義一個(gè)支持逆變的類核畴,逆變類型參數(shù)只能用作輸入,所以可以作為入?yún)⒌念愋偷菬o法作為返回值的類型

class ContravarintHolder<in A>(a: A) {
    fun foo(a: A) {
    }
}

使用該類

var strDCo = ContravarintHolder("a")
var anyDCo = ContravarintHolder<Any>("b")
strDCo = anyDCo

類型通配符

Kotlin 使用 * 作為通配符冲九,而 Java 是 谤草?,Scala 是 _莺奸。

fun foo2(capture: Capture<*>) {
}

類型參數(shù)邊界

Kotlin 并沒有上下邊界這種說法丑孩。但是可以通過在方法上使用協(xié)變和逆變來達(dá)到同樣的效果。

使用協(xié)變參數(shù)達(dá)到上邊界的作用灭贷,這里的 out 很形象地表示了協(xié)變參數(shù)只能用于輸出

fun foo3(list: MutableList<out Num>) {
    val num: Num = list.get(0)
    println(num)
}

使用逆變參數(shù)達(dá)到下邊界的作用温学,這里的 in 很形象地表示了逆變參數(shù)只能用于輸入

fun foo4(list: MutableList<in Num>) {
    list.add(Num(4))
    val num: Any? = list.get(0)
    println(num)
}

Summary

  • Java 和 Groovy 的用法完全一致,都只支持逆變
  • Scala 和 Kotlin 支持逆變和協(xié)變甚疟,但是都需要顯示指定
  • Java 使用 ? 作為通配符仗岖,Scala 使用 _,Kotlin 使用 *

文章源碼見 https://github.com/SidneyXu/JGSK 倉庫的 _27_generics 小節(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末览妖,一起剝皮案震驚了整個(gè)濱河市轧拄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌黄痪,老刑警劉巖紧帕,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盔然,死亡現(xiàn)場(chǎng)離奇詭異桅打,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)愈案,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門挺尾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人站绪,你說我怎么就攤上這事遭铺。” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵魂挂,是天一觀的道長(zhǎng)甫题。 經(jīng)常有香客問我,道長(zhǎng)涂召,這世上最難降的妖魔是什么坠非? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮果正,結(jié)果婚禮上炎码,老公的妹妹穿的比我還像新娘。我一直安慰自己秋泳,他們只是感情好潦闲,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著迫皱,像睡著了一般歉闰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卓起,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天新娜,我揣著相機(jī)與錄音,去河邊找鬼既绩。 笑死概龄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的饲握。 我是一名探鬼主播私杜,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼救欧!你這毒婦竟也來了衰粹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤笆怠,失蹤者是張志新(化名)和其女友劉穎铝耻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹬刷,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓢捉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了办成。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泡态。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖迂卢,靈堂內(nèi)的尸體忽然破棺而出某弦,到底是詐尸還是另有隱情桐汤,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布靶壮,位于F島的核電站怔毛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏腾降。R本人自食惡果不足惜馆截,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜂莉。 院中可真熱鬧蜡娶,春花似錦、人聲如沸映穗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚁滋。三九已至宿接,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辕录,已是汗流浹背睦霎。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留走诞,地道東北人副女。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蚣旱,于是被迫代替她去往敵國和親碑幅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 前言 人生苦多塞绿,快來 Kotlin 沟涨,快速學(xué)習(xí)Kotlin! 什么是Kotlin异吻? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,163評(píng)論 9 118
  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分诀浪,“神秘”的通配符棋返,讓我看了幾遍《Java...
    珞澤珈群閱讀 7,795評(píng)論 12 51
  • object 變量可指向任何類的實(shí)例,這讓你能夠創(chuàng)建可對(duì)任何數(shù)據(jù)類型進(jìn)程處理的類笋妥。然而懊昨,這種方法存在幾個(gè)嚴(yán)重的問題...
    CarlDonitz閱讀 910評(píng)論 0 5
  • 本文大量參考Thinking in java(解析,填充)春宣。 定義:多態(tài)算是一種泛化機(jī)制酵颁,解決了一部分可以應(yīng)用于多...
    谷歌清潔工閱讀 457評(píng)論 0 2
  • 一個(gè)女人在打電話 她的太陽鏡下藏了什么? 傷心的眼淚月帝? 失望的淚水躏惋? 兇狠的目光? 她的包里又藏了什么嚷辅? 手槍簿姨? ...
    上官血櫻閱讀 226評(píng)論 0 1