国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

對(duì)比Java泛型中的extends/super和Kotlin的out/in

LittleLiByte / 3349人閱讀

摘要:使用強(qiáng)轉(zhuǎn)的話,只能強(qiáng)轉(zhuǎn)成和它的基類,如果強(qiáng)轉(zhuǎn)成的子類的話,有可能會(huì)報(bào)運(yùn)行時(shí)異常。擁有類型,它是的子類型因此,我們可以將賦給類型為的變量在聲明處設(shè)置后,就可以和或它的子類進(jìn)行比較了。

歡迎關(guān)注我的博客:songjhh"s blog

原文連接:對(duì)比Java泛型中的extends/super和Kotlin的out/in

在 Java 泛型中,有一個(gè)叫做通配符上下界 bounded wildcard 的概念。

:指的是上界通配符 (Upper Bounded Wildcards)

:指的是下界通配符 (Lower Bounded Wildcards)

相對(duì)應(yīng)在 Kotlin 泛型中,有 outin 兩個(gè)關(guān)鍵字

下面我將會(huì)以工位分配的例子解釋它可以用來(lái)解決什么問(wèn)題,并且對(duì)比 Java 來(lái)說(shuō),Kotlin 作了什么改進(jìn)。

解決的問(wèn)題

這里有4個(gè)實(shí)體,分別是 Employee (員工基類),Manager (經(jīng)理), DevManager (開(kāi)發(fā)經(jīng)理),WorkStation 工位。

它們的關(guān)系如下:

@Data
public class Employee {
    private String name;
    public Employee(String name) {
        this.name = name;
    }
}

@Data
public class Manager extends Employee {
    private Integer level;
    public Manager(String name) {
        super(name);
    }
}

@Data
public class DevManager extends Manager {
    private String language;
    public DevManager(String name) {
        super(name);
    }
}

其中一個(gè)工位可以坐一個(gè)員工, 這里用泛型抽象出員工來(lái):

@Data
public class WorkStation {
    private T employee;
    public WorkStation(T employee) {
        this.employee = employee;
    }
}

按照邏輯,一個(gè)經(jīng)理的工位,當(dāng)然也是一個(gè)員工的工位,但事實(shí)真的如此嗎?

// 創(chuàng)建一個(gè)經(jīng)理工位
WorkStation managerWorkStation = new WorkStation<>(new Manager("John"));
// 將經(jīng)理工位賦給員工工位
WorkStation employWorkStation = managerWorkStation; // error

但這里會(huì)報(bào) incompatible types: WorkStation cannot be converted to WorkStation,意思是兩個(gè)類型不能相互轉(zhuǎn)化。雖然 Manager 繼承于 Employee ,但是兩個(gè)類型的工位并沒(méi)有繼承關(guān)系,所以不能直接將經(jīng)理工位的引用傳給員工工位。

造成這個(gè)現(xiàn)象的原因,是因?yàn)镴ava 的參數(shù)類型是不型變的 invariant而通配符上下界正是為了繞過(guò)這個(gè)問(wèn)題。

ps: 型變?cè)谟?jì)算機(jī)編程中,特別是面向?qū)ο缶幊蹋侵匾幕梢栽跍y(cè)試階段幫助程序員發(fā)現(xiàn)很多的錯(cuò)誤,這里不展開(kāi)討論。
有界限的通配符(Bounded Wildcards)

為了幫助理解和記憶,在講通配符上下界之前,這里先講一講PECS原則

PECS stands for producer-extends, consumer-super 

From: Effective Java Third Edition - Item 31

這里引用的是 Effective Java Third Edition 關(guān)于如何利用 bounded wildcards 來(lái)提升 API 靈活性章節(jié)一個(gè)助記詞。

簡(jiǎn)單來(lái)說(shuō),生產(chǎn)者適合用 ,而消費(fèi)者適合用 ,這里生產(chǎn)者指的是能用來(lái)讀取的對(duì)象,消費(fèi)者指的是用來(lái)寫(xiě)入的對(duì)象,下面將會(huì)詳細(xì)解釋這兩個(gè)概念。

上界通配符(extends)

還是接著上面的例子,員工的工位為了獲得經(jīng)理工位的引用,這里使用上界通配符

// 創(chuàng)建一個(gè)經(jīng)理工位
WorkStation managerWorkStation = new WorkStation<>(new Manager("John"));
// 將經(jīng)理工位的引用賦給一個(gè)繼承于員工對(duì)象的工位
WorkStation exWorkStation = managerWorkStation;

可以看到使用了上界通配符,我們將經(jīng)理工位和員工工位關(guān)聯(lián)起來(lái)了,使得 Java 泛型的靈活性大大增加。

但是上面介紹了 PECS原則 , 它指出上界通配符只適合用于生產(chǎn)者中,下面我?guī)Т蠹襾?lái)看看這句話如何理解:

WorkStation managerWorkStation = new WorkStation<>(new Manager("John"));
WorkStation exWorkStation = managerWorkStation;

// 只可以獲取它和它的基類
Object a = exWorkStation.getEmployee();
Employee b = exWorkStation.getEmployee();
DevManager d = exWorkStation.getEmployee(); // error

// 不可以存儲(chǔ)
exWorkStation.setEmployee(new Employee("Sam")); // error, incompatible types: Manager cannot be coverted to capture#1 of ? extends Employee
exWorkStation.setEmployee(new DevManager("James")); // error, incompatible types: DevManager cannot be coverted to capture#1 of ? extends Employee

上面的例子可以看到,使用了上界通配符只能用 get() 方法取出工位占位的類型和其基類,但是不能再用 set() 方法存對(duì)象到工位中,所以說(shuō)上界通配符只適合用于生產(chǎn)者中

原因也很好理解,因?yàn)榫幾g器只知道工位坐的人是 Employee 對(duì)象或它的派生類,但不知道具體是哪個(gè)對(duì)象(編譯器用 capture#1 標(biāo)記占位,指這里捕獲 Employee 和它的子類),所以不能夠判斷存入的對(duì)象是不是這個(gè)工位能夠匹配的:

坐在 exWorkStation 的人一定是一個(gè)員工,所以可以取出 Employee

exWorkStation 可能是 Manager 的工位,所以這里存取 TestManager 是沒(méi)問(wèn)題的。但問(wèn)題在于它也可能是 DevManager 的工位,那么 TestManager 就不能坐在這個(gè)工位里了,編輯器無(wú)法判斷,所以上界通配符不能用 set() 方法

簡(jiǎn)而言之,上界通配符 Upper Bounded Wildcards 使得參數(shù)類型是協(xié)變的covariant

下界通配符

上界通配符恰恰相反,下界通配符 適合存儲(chǔ)對(duì)象的場(chǎng)景。

WorkStation supWorkStation = new WorkStation<>(new Manager("James"));

// 可以存儲(chǔ)它和它的子類
supWorkStation.setEmployee(new DevManager("Sam"));
supWorkStation.setEmployee(new Manager("Sam"));
supWorkStation.setEmployee(new Employee("Sam")); // error

// 只可以獲取所有類的基類 - Object
Object o = supWorkStation.getEmployee();
Employee e = supWorkStation.getEmployee(); // error
Manager e = supWorkStation.getEmployee(); // error
DevManager e = supWorkStation.getEmployee(); // error

// 只能安全強(qiáng)轉(zhuǎn)成它和它的基類
Employee employee = (Employee) o;
Manager manager = (Manager) o;

WorkStation w = new WorkStation<>(new Manager("Sam"));
// ClassCastException: Manager cannot be cast to DevManager
DevManager devManager = (DevManager) w.getEmployee();

上面的例子可以看到,使用下界通配符可以用 set() 方法儲(chǔ)存 Manager 和其子類,但只能用 get() 方法獲得所有類的基類 Object 對(duì)象。使用強(qiáng)轉(zhuǎn)的話,只能強(qiáng)轉(zhuǎn)成 Manager 和它的基類,如果強(qiáng)轉(zhuǎn)成 Manager 的子類的話,有可能會(huì)報(bào) ClassCastException 運(yùn)行時(shí)異常。

因?yàn)榇嫒敕奖悖〕鰯?shù)據(jù)比較麻煩,所以說(shuō)下界通配符適合使用在消費(fèi)者中。

究其原因,可以簡(jiǎn)單理解為,下界通配符標(biāo)記了該工位至少Manager 的工位,所以這里無(wú)論是坐 DevManager 還是 TestManager 都沒(méi)有問(wèn)題。

這個(gè)就叫做逆變性(contravariance

在Kotlin的世界里是怎么樣的?

是 Java 世界是用通配符上下界來(lái)覺(jué)得泛型不型變的,那在 Kotlin 是怎么樣的呢?

val managerWorkStation: WorkStation = WorkStation(Manager("John"))
val station: WorkStation = managerWorkStation // error, type mismatch

由此看到在 Kotlin 里對(duì)泛型也是有限制的。相對(duì)于 Java 提供的 ,Kotlin 相對(duì)應(yīng)提供了 outin 關(guān)鍵字。

在 Kotlin 中 out 相當(dāng)于 in 相當(dāng)于 ,這里看看用法。

out 關(guān)鍵字:

val managerWorkStation: WorkStation = WorkStation(Manager("John"))
val outStation: WorkStation = managerWorkStation

// 只可以獲取它和它的基類
val a: Any = outStation.employee
val b: Employee = outStation.employee
val c: Employee = managerWorkStation.employee
val d: DevManager = managerWorkStation.employee // error, type mismatch

// 不可以存儲(chǔ)
outStation.employee = DevManager("Sam") // Setter for "employee" is removed by type projection

in關(guān)鍵字:

val inStation: WorkStation = WorkStation()

// 可以存儲(chǔ)它和它的子類
inStation.employee = Manager("James")
inStation.employee = DevManager("James")
inStation.employee = Employee("James") // error, type mismatch

// 只可以獲得Any
val any: Any? = inStation.employee

// 只能安全強(qiáng)轉(zhuǎn)成它和它的基類
val employee: Employee = any as Employee
val manager:Manager = any as Manager

由以上兩個(gè)例子可以看到,Kotlin 和 Java 非常相似,只是相關(guān)的關(guān)鍵字有所不同而已。但畢竟 Kotlin 是號(hào)稱要解決 Java 的,那么會(huì)不會(huì)哪里有所不同呢?

Kotlin 和 Java 的異同 使用處型變

在 Java 中,上下界通配符只能用在參數(shù)、屬性、變量或者返回值中,不能在泛型聲明處使用,所以才叫做使用處型變

以上的 Kotlin 例子也用的是使用處型變,被稱為類型投影

所以 Java 和 Kotlin 都提供使用處型變

聲明處型變

但不同的是,Kotlin 還提供 Java 所不具備的聲明處型變

顧名思義,Kotlin 提供的 outin 兩個(gè)型變關(guān)鍵字還可以用于泛型聲明的時(shí)候。

public interface Collection : Iterable {
    ...
}

// 錯(cuò)誤,這里只能用val,不能用var
class Source(var t: T) {
    ...
}

在聲明處設(shè)置 out 后,使得了在 Kotlin 中,Collection 安全的作為 Collection 的父類使用,但 E 被標(biāo)記為 out 后,E 只能被輸出而不能寫(xiě)入。

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

Comparable 在聲明處設(shè)置 in 后,x 就可以和 Number 或它的子類進(jìn)行比較了。

總結(jié)

以上就是 Java 和 Kotlin 關(guān)于泛型型變的內(nèi)容,其中 Kotlin 對(duì)比 Java,多加了聲明處型變的方式。

Java Java示例代碼 Kotlin示例代碼
使用處型變 void example(List list) fun example(list: List)
使用處逆變 void example(List) fun example(list: List)
聲明處型變 - interface Collection : Iterable
聲明處逆變 - interface Comparable

為了幫助記憶,上文引用了PECS原則:producer-extends, consumer-super

最后這里再引用Effective Java - 31 | Use bounded wildcards to increase API flexibilty里面對(duì)通配符的幾個(gè)意見(jiàn):

If an input parameter is both a producer and a consumer, then wildcard types will do you no good.

如果輸入?yún)?shù)同時(shí)是生產(chǎn)者和消費(fèi)者, 那么通配符對(duì)你來(lái)說(shuō)不是一個(gè)好的選擇。

Do not use bounded wildcard types as return types, if the user of a class has to think about wildcard types, there is probably something wrong with its API.

不要用界限通配符作為你的返回類型,如果類的用戶必須考慮通配符類型,類的 API 或許就會(huì)出錯(cuò)。

If a type parameter appears only once in a method declaration, replace it with a wildcard.

如果類型參數(shù)只在方法聲明中出現(xiàn)一次,就可以用通配符取代它。

謝謝閱讀
版權(quán)聲明:歡迎轉(zhuǎn)載 (http://songjhh.top/2019/03/13...

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/73735.html

相關(guān)文章

  • Java知識(shí)點(diǎn)總結(jié)(Java泛型

    摘要:知識(shí)點(diǎn)總結(jié)泛型知識(shí)點(diǎn)總結(jié)泛型泛型泛型就是參數(shù)化類型適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼泛型中的類型在使用時(shí)指定泛型歸根到底就是模版優(yōu)點(diǎn)使用泛型時(shí),在實(shí)際使用之前類型就已經(jīng)確定了,不需要強(qiáng)制類型轉(zhuǎn)換。 Java知識(shí)點(diǎn)總結(jié)(Java泛型) @(Java知識(shí)點(diǎn)總結(jié))[Java, Java泛型] [toc] 泛型 泛型就是參數(shù)化類型 適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼 泛型中的類型在使用時(shí)指定 泛...

    linkin 評(píng)論0 收藏0
  • 小馬哥Java項(xiàng)目實(shí)戰(zhàn)訓(xùn)練營(yíng) 極客大學(xué)

    摘要:百度網(wǎng)盤(pán)提取碼一面試題熟練掌握是很關(guān)鍵的,大公司不僅僅要求你會(huì)使用幾個(gè),更多的是要你熟悉源碼實(shí)現(xiàn)原理,甚至要你知道有哪些不足,怎么改進(jìn),還有一些有關(guān)的一些算法,設(shè)計(jì)模式等等。 ??百度網(wǎng)盤(pán)??提取碼:u6C4?一、java面試題熟練掌握java是很關(guān)鍵的,大公司不僅僅要求你會(huì)使用幾個(gè)api,更多的是要你熟悉源碼實(shí)現(xiàn)原理,甚...

    不知名網(wǎng)友 評(píng)論0 收藏0
  • 第12章 元編程與注解、反射 《Kotlin 項(xiàng)目實(shí)戰(zhàn)開(kāi)發(fā)》

    摘要:第章元編程與注解反射反射是在運(yùn)行時(shí)獲取類的函數(shù)方法屬性父類接口注解元數(shù)據(jù)泛型信息等類的內(nèi)部信息的機(jī)制。本章介紹中的注解與反射編程的相關(guān)內(nèi)容。元編程本質(zhì)上是一種對(duì)源代碼本身進(jìn)行高層次抽象的編碼技術(shù)。反射是促進(jìn)元編程的一種很有價(jià)值的語(yǔ)言特性。 第12章 元編程與注解、反射 反射(Reflection)是在運(yùn)行時(shí)獲取類的函數(shù)(方法)、屬性、父類、接口、注解元數(shù)據(jù)、泛型信息等類的內(nèi)部信息的機(jī)...

    joyqi 評(píng)論0 收藏0
  • Java泛型

    摘要:虛擬機(jī)中并沒(méi)有泛型類型對(duì)象,所有的對(duì)象都是普通類。其原因就是泛型的擦除。中數(shù)組是協(xié)變的,泛型是不可變的。在不指定泛型的情況下,泛型變量的類型為該方法中的幾種類型的同一個(gè)父類的最小級(jí),直到。 引入泛型的主要目標(biāo)有以下幾點(diǎn): 類型安全 泛型的主要目標(biāo)是提高 Java 程序的類型安全 編譯時(shí)期就可以檢查出因 Java 類型不正確導(dǎo)致的 ClassCastException 異常 符合越早出...

    woshicixide 評(píng)論0 收藏0
  • Java泛型通配符

    摘要:好了,有了這樣的背景知識(shí),我們可以來(lái)看一下上界通配了,在中,可以使用來(lái)界定一個(gè)上界,的意思是所有屬于的子類,是上界,不能突破天界啊,我們具體化一下,的意思就是,所有的子類都可以匹配這個(gè)通配符。 1、上界通配符 首先,需要知道的是,Java語(yǔ)言中的數(shù)組是支付協(xié)變的,什么意思呢?看下面的代碼: static class A extends Base{ void f(...

    sunny5541 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<