Kotlin?泛型邊界型變及星投影使用詳解
1.泛型
Android項(xiàng)目開(kāi)發(fā)過(guò)程中普遍會(huì)遇到一個(gè)問(wèn)題:adapter的樣式、業(yè)務(wù)邏輯很相似,但是需要的數(shù)據(jù)源不是來(lái)自一個(gè)接口,常規(guī)情況下就要定義多個(gè)構(gòu)造函數(shù)但是這樣就要更改構(gòu)造函數(shù)的傳參順序或者增加傳參要么就是將他們統(tǒng)一成一個(gè)類(lèi)。但是用泛型就可以這樣解決:
class CommonAdapter<T>(val list: List<T>) : RecyclerView.Adapter<CommonAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
TODO("Not yet implemented")
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
TODO("Not yet implemented")
}
override fun getItemCount() = list.size
}
//調(diào)用CommonAdapter
mBinding.rvTab.adapter = CommonAdapter(listOf("周一","周二","周三"))
mBinding.rvTab.adapter = CommonAdapter(listOf(CommonData("張三"),CommonData("李四"),CommonData("王五")))
可以看到泛型可以很好的解決這個(gè)問(wèn)題。
再舉個(gè)例子,電子產(chǎn)品的充電器,在以前充電接口沒(méi)有統(tǒng)一的時(shí)候每個(gè)設(shè)備都需要一個(gè)單獨(dú)的充電器,這樣就很麻煩,下面的代碼就是對(duì)這個(gè)問(wèn)題的一種表示
// 手機(jī)
class Phone {
fun charging() {}
}
// 平板
class Pad {
fun charging() {}
}
//無(wú)線耳機(jī)
class Headset {
fun charging() {}
}
//便攜音箱
class Speakers {
fun charging() {}
}
統(tǒng)一充電器接口后就只需要保留一個(gè)充電器即可,這個(gè)概念就需要用到泛型了
//統(tǒng)一接口
class UnifiedInterface<T> {
fun charging(device: T) {}
}
val phone = UnifiedInterface<Phone>()
phone.charging()
val pad = UnifiedInterface<Pad>()
pad.charging()
在統(tǒng)一接口UnifiedInterface中傳入要充電的電子設(shè)備就可以了。T代表的就是各種電子設(shè)備。
這里要注意的一點(diǎn)是在使用泛型的時(shí)候還可以加上邊界
class UnifiedInterface<T: Phone> {
fun charging(device: T) {}
}
上面的代碼中Phone就是邊界,用【:】這個(gè)邊界的聲明就是說(shuō)只能傳入Phone類(lèi)或者它的子類(lèi),傳入Pad或者Headset都是不可以的。
2.型變
fun main() {
func(mutableListOf<Phone>(Phone())) //報(bào)錯(cuò) 這里應(yīng)該傳入Device類(lèi)型的集合
}
fun func(list: MutableList<Device>) {
list.add(Pad())
}
open class Device {
}
class Phone : Device() {
}
class Pad {
}
這里定義了一個(gè)方法,方法中的傳參是Device類(lèi)型的集合,調(diào)用的時(shí)候傳入的Phone類(lèi)型的集合,而Device和Phone是繼承關(guān)系但是卻無(wú)法傳入Phone類(lèi)型的集合。這是因?yàn)樵谀J(rèn)情況下MutableList<Device>和MutableList<Phone>之間不存在任何繼承關(guān)系,他們也無(wú)法互相替代,這就是泛型的不變性。
那么什么是型變?型變就是為了解決泛型的不變性問(wèn)題。
3.型變—逆變
以手機(jī)為例,Android是手機(jī)類(lèi),還有XioMi和HuaWei兩個(gè)子類(lèi),它倆和Android是繼承關(guān)系,那么Charger<XiaoMi>、Charger<HuaWei>和Charger<Android>之間有什么關(guān)系?
class Charger<T> {
fun charging(device: T) {}
}
open class Android {
open fun charging() {}
}
class XiaoMi : Android() {
override fun charging() {
}
}
class HuaWei() : Android() {
override fun charging() {
super.charging()
}
}
假設(shè),現(xiàn)在手機(jī)都沒(méi)電了,需要用充電器充電,那么給XiaoMi手機(jī)充電就是這樣
fun xiaoMiCharger(charger: Charger<XiaoMi>) {
val xiaomi = XiaoMi()
charger.charging(xiaomi)
}
但是還有一個(gè)HuaWei手機(jī)也要充電,是否可以用一個(gè)充電器?就像下面這樣
fun main() {
val charger = Charger<Android>()
xiaoMiCharger(charger) //報(bào)錯(cuò):類(lèi)型不匹配
huaWeiCharger(charger) //報(bào)錯(cuò):類(lèi)型不匹配
}
都是Android手機(jī)為什么不能充電?這主要是編譯器不認(rèn)為XiaoMi和HuaWei是Android手機(jī),也就是說(shuō)它們?nèi)咧g沒(méi)有關(guān)系,這就是上面講的不可變性, 此時(shí)逆變踩著歡快的腳步到來(lái)了。
- 使用處型變
// 修改處
// ↓
fun xiaoMiCharger(charger: Charger<in XiaoMi>) {
val xiaomi = XiaoMi()
charger.charging(xiaomi)
}
// 修改處
// ↓
fun huaWeiCharger(charger: Charger<in HuaWei>) {
val huaWei = HuaWei()
charger.charging(huaWei)
}
class Charger<T> {
fun charging(device: T) {}
}
fun main() {
val charger = Charger<Android>()
xiaoMiCharger(charger)
huaWeiCharger(charger)
}
- 聲明處型變
// 修改處
// ↓
class Charger<in T> {
fun charging(device: T) {}
}
fun xiaoMiCharger(charger: Charger<XiaoMi>) {
val xiaomi = XiaoMi()
charger.charging(xiaomi)
}
fun huaWeiCharger(charger: Charger<HuaWei>) {
val huaWei = HuaWei()
charger.charging(huaWei)
}
fun main() {
val charger = Charger<Android>()
xiaoMiCharger(charger)
huaWeiCharger(charger)
}
加上in關(guān)鍵字之后就報(bào)錯(cuò)就消失了
為什么被叫做逆變?
上面的代碼其實(shí)是將父子關(guān)系顛倒了,以使用處型變?yōu)槔?,我們把代碼放到一起看看
fun main() {
val charger = Charger<Android>()
xiaoMiCharger(charger)
huaWeiCharger(charger)
}
//Charger<Android> → Charger<XiaoMi> 成了顛倒關(guān)系
fun xiaoMiCharger(charger: Charger<in XiaoMi>) {
val xiaomi = XiaoMi()
charger.charging(xiaomi)
}
fun huaWeiCharger(charger: Charger<in HuaWei>) {
val huaWei = HuaWei()
charger.charging(huaWei)
}

這種父子顛倒的關(guān)系被稱(chēng)為逆變。
4.型變—協(xié)變
假設(shè)我要去商場(chǎng)買(mǎi)一個(gè)Android手機(jī),它屬于Phone類(lèi)
open class Phone {
}
class Android : Phone() {
}
//商場(chǎng)什么都賣(mài)
class Shop<T> {
fun buy(): T {
TODO("Not yet implemented")
}
}
//去商場(chǎng)買(mǎi)手機(jī)
fun buy(shop: Shop<Phone>) {
val phone = shop.buy()
}
fun buy(shop: Shop<Phone>) {
val phone = shop.buy()
}
fun main() {
val android = Shop<Android>()
buy(android) //報(bào)錯(cuò)了,類(lèi)型不匹配
}
Android是Phone的子類(lèi),但是Shop<Android>和Shop<Phone>卻沒(méi)有關(guān)系,這依舊是Kotlin的不可變性,前面講過(guò)通過(guò)in實(shí)現(xiàn)逆變, 但是它的父子關(guān)系就被顛倒了,那么這里的目的就是維持正確的父子關(guān)系——協(xié)變。
- 使用處協(xié)變
class Shop<T> {
fun buy(): T {
TODO("Not yet implemented")
}
}
// 修改處
// ↓
fun buy(shop: Shop<out Phone>) {
val phone = shop.buy()
}
fun main() {
val android = Shop<Android>()
buy(android) //報(bào)錯(cuò)消失
}
- 聲明處協(xié)變
// 修改處
// ↓
class Shop<out T> {
fun buy(): T {
TODO("Not yet implemented")
}
}
fun buy(shop: Shop<Phone>) {
val phone = shop.buy()
}
fun main() {
val android = Shop<Android>()
buy(android) //報(bào)錯(cuò)消失
}
通過(guò)out就實(shí)現(xiàn)了協(xié)變, 父子關(guān)系也沒(méi)有顛倒,關(guān)系圖如下

Kotlin的型變的逆變、協(xié)變到這里就講完了,Java中也有型變,但是只有使用處沒(méi)有聲明處
| Kotlin | Java | |
|---|---|---|
| 逆變 | Charger | Charger<? super XiaoMi> |
| 協(xié)變 | Shop | Shop<? extends Phone> |
//RxJava#ObservableAnySingle
public ObservableAnySingle(ObservableSource<T> source, Predicate<? super T> predicate) {
this.source = source;
this.predicate = predicate;
}
//String#join
public static String join(CharSequence delimiter, Iterable<? extends CharSequence> elements) {
Objects.requireNonNull(delimiter);
Objects.requireNonNull(elements);
StringJoiner joiner = new StringJoiner(delimiter);
for (CharSequence cs: elements) {
joiner.add(cs);
}
return joiner.toString();
}
逆變和協(xié)變有什么區(qū)別?怎么用?
//聲明處逆變
class Charger<in T> {
fun charging(device: T) {}
}
//聲明處協(xié)變
// 修改處
// ↓
class Shop<out T> {
fun buy(): T {
TODO("Not yet implemented")
}
}
對(duì)比可以發(fā)現(xiàn)逆變主要用于傳參,協(xié)變主要用于返回值,Kotlin的官方文檔也有這么一句話: ****消費(fèi)者 in, 生產(chǎn)者 out!
5.泛型邊界
在講協(xié)變的例子時(shí)我們要去商場(chǎng)Shop買(mǎi)手機(jī),但是商場(chǎng)什么手機(jī)都賣(mài),現(xiàn)在我想買(mǎi)Android手機(jī),怎么保證我買(mǎi)的就是Android手機(jī)?加上一個(gè)邊界就好了
// 變化在這里,加了一個(gè)邊界,類(lèi)似于var x: Int = 0
// ↓
class Shop<out T: Android> {
fun buy(): T {
TODO("Not yet implemented")
}
}
fun main() {
val android = Shop<Android>()
buy(android)
val ios = Shop<IOS>() //報(bào)錯(cuò):類(lèi)型不匹配
buy(ios)
}
}
6.星投影
星投影就是用【】作為泛型的實(shí)參,當(dāng)我們使用【】作為泛型的實(shí)參時(shí)也就意味著我們對(duì)具體的參數(shù)是什么并不感興趣或者說(shuō)不知道具體的參數(shù)是什么。
舉例:還是買(mǎi)手機(jī)的案例,現(xiàn)在我不挑品牌了,只要能用就好,既然這樣那就隨便找家店鋪好了
// 不指定具體參數(shù)
// ↓
fun findShop(): Shop<*> {
TODO("Not yet implemented")
}
fun main(){
val shop = findShop()
val product: Any? = shop.buy()
}
這里的product什么手機(jī)都可以,甚至是其他物品都行,這里還定義了一個(gè)Any?也說(shuō)明了可能是空手而歸。
那么我只想買(mǎi)個(gè)手機(jī),怎么才能避免買(mǎi)錯(cuò)成其他物品呢?添加邊界
//只找Phone的店鋪
// ↓ 這是邊界
class Shop<out T: Phone> {
fun buy(): T {
TODO("Not yet implemented")
}
}
fun findShop(): Shop<*> {
TODO("Not yet implemented")
}
fun main() {
val shop = findShop()
//只要返回值是Phone的商品
val product: Phone = shop.buy()
}
添加邊界后就可以達(dá)到我只想買(mǎi)個(gè)手機(jī)的要求了。
泛型這一塊比較抽象,一定要多看幾遍,思考在項(xiàng)目中這個(gè)東西的應(yīng)用場(chǎng)景在哪里。

以上就是Kotlin 泛型邊界型變及星投影使用詳解的詳細(xì)內(nèi)容,更多關(guān)于Kotlin 泛型型變星投影的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開(kāi)發(fā)No Focused Window ANR產(chǎn)生原理解析
這篇文章主要為大家介紹了Android開(kāi)發(fā)No Focused Window ANR產(chǎn)生原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Flutter WillPopScope攔截返回事件原理示例詳解
這篇文章主要為大家介紹了Flutter WillPopScope攔截返回事件原理示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
flutter實(shí)現(xiàn)更新彈窗內(nèi)容例子(親測(cè)有效)
Flutter是一款移動(dòng)應(yīng)用程序SDK,包含框架、widget和工具,這篇文章給大家介紹flutter實(shí)現(xiàn)更新彈窗內(nèi)容例子,親測(cè)可以使用,需要的朋友參考下吧2021-04-04
Android自定義Spinner下拉列表(使用ArrayAdapter和自定義Adapter實(shí)現(xiàn))
這篇文章主要介紹了Android自定義Spinner下拉列表(使用ArrayAdapter和自定義Adapter實(shí)現(xiàn))的相關(guān)資料,需要的朋友可以參考下2015-10-10
Kotlin基礎(chǔ)學(xué)習(xí)之位運(yùn)算
一提起位運(yùn)算,人們往往想到它的高效性,無(wú)論是嵌入式編程還是優(yōu)化系統(tǒng)的核心代碼,適當(dāng)?shù)倪\(yùn)用位運(yùn)算總是一種迷人的手段,下面這篇文章主要給大家介紹了關(guān)于Kotlin基礎(chǔ)學(xué)習(xí)之位運(yùn)算的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下。2017-11-11
AndroidX下使用Activity和Fragment的變化詳解
這篇文章主要介紹了AndroidX下使用Activity和Fragment的變化詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04
Android 安全加密:數(shù)字簽名和數(shù)字證書(shū)詳解
本文主要介紹Android 安全加密數(shù)字簽名和數(shù)字證書(shū)的資料,這里整理詳細(xì)的資料及數(shù)字簽名和數(shù)字證書(shū)應(yīng)用詳解,有需要的小伙伴可以參考下2016-09-09

