欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Kotlin與Java 泛型缺陷和應用場景詳解

 更新時間:2022年12月21日 15:01:51   作者:程序員DHL  
這篇文章主要為大家介紹了Kotlin與Java 泛型缺陷和應用場景詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

全文分為 視頻版文字版

  • 文字版: 文字側重細節(jié)和深度,有些知識點,視頻不好表達,文字描述的更加準確
  • 視頻版: 視頻會更加的直觀,看完文字版,在看視頻,知識點會更加清楚

視頻版 bilibili 地址:https://b23.tv/AdLtUGf

泛型對于每個開發(fā)者而言并不陌生,平時在項目中會經常見到,但是有很多小伙伴們,每次見到通配符 ? extends 、 ? super 、 out 、 in 都傻傻分不清楚它們的區(qū)別,以及在什么情況下使用。

通過這篇文章將會學習的到以下內容。

  • 為什么要有泛型
  • Kotlin 和 Java 的協(xié)變
  • Kotlin 和 Java 的逆變
  • 通配符 ? extends? super 、 out 、 in 的區(qū)別和應用場景
  • Kotlin 和 Java 數組協(xié)變的不同之處
  • 數組協(xié)變的缺陷
  • 協(xié)變和逆變的應用場景

為什么要有泛型

在 Java 和 Kotlin 中我們常用集合( List 、 Set 、 Map 等等)來存儲數據,而在集合中可能存儲各種類型的數據,現在我們有四種數據類型 Int 、 FloatDouble 、 Number,假設沒有泛型,我們需要創(chuàng)建四個集合類來存儲對應的數據。

class IntList{ }
class Floatlist{}
class DoubleList{}
class NumberList{}
......
更多

如果有更多的類型,就需要創(chuàng)建更多的集合類來保存對應的數據,這顯示是不可能的,而泛型是一個 "萬能的類型匹配器",同時有能讓編譯器保證類型安全。

泛型將具體的類型( Int 、 FloatDouble 等等)聲明的時候使用符號來代替,使用的時候,才指定具體的類型。

// 聲明的時候使用符號來代替
class List<E>{
}
// 在 Kotlin 中使用,指定具體的類型
val data1: List<Int> = List()
val data2: List<Float> = List()
// 在 Java 中使用,指定具體的類型
List<Integer> data1 = new List();
List<Float> data2 = new List();

泛型很好的幫我們解決了上面的問題,但是隨之而來出現了新的問題,我們都知道 Int 、 FloatDoubleNumber 子類型, 因此下面的代碼是可以正常運行的。

// Kotlin
val number: Number = 1
// Java
Number number = 1;

我們花三秒鐘思考一下,下面的代碼是否可以正常編譯。

List<Number> numbers = new ArrayList<Integer>();

答案是不可以,正如下圖所示,編譯會出錯。

這也就說明了泛型是不可變的,IDE 認為 ArrayList<Integer> 不是 List<Number> 子類型,不允許這么賦值,那么如何解決這個問題呢,這就需要用到協(xié)變了,協(xié)變允許上面的賦值是合法的。

Kotlin 和 Java 的協(xié)變

  • 在 Java 中用通配符 ? extends T 表示協(xié)變,extends 限制了父類型 T,其中 ? 表示未知類型,比如 ? extends Number,只要聲明時傳入的類型是 Number 或者 Number 的子類型都可以
  • 在 Kotlin 中關鍵字 out T 表示協(xié)變,含義和 Java 一樣

現在我們將上面的代碼修改一下,在花三秒鐘思考一下,下面的代碼是否可以正常編譯。

// kotlin
val numbers: MutableList<out Number> = ArrayList<Int>()
// Java
List<? extends Number> numbers = new ArrayList<Integer>();

答案是可以正常編譯,協(xié)變通配符 ? extends Number 或者 out Number 表示接受 Number 或者 Number 子類型為對象的集合,協(xié)變放寬了對數據類型的約束,但是放寬是有代價的,我們在花三秒鐘思考一下,下面的代碼是否可以正常編譯。

// Koltin
val numbers: MutableList<out Number> = ArrayList<Int>()
numbers.add(1)
// Java
List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(1)

調用 add() 方法會編譯失敗,雖然協(xié)變放寬了對數據類型的約束,可以接受 Number 或者 Number 子類型為對象的集合,但是代價是 無法添加元素,只能獲取元素,因此協(xié)變只能作為生產者,向外提供數據。

為什么無法添加元素

因為 ? 表示未知類型,所以編譯器也不知道會往集合中添加什么類型的數據,因此索性不允許往集合中添加元素。

但是如果想讓上面的代碼編譯通過,想往集合中添加元素,這就需要用到逆變了。

Kotlin 和 Java 的逆變

逆變其實是把繼承關系顛倒過來,比如 IntegerNumber 的子類型,但是 Integer 加逆變通配符之后,Number? super Integer 的子類,如下圖所示。

  • 在 Java 中用通配符 ? super T 表示逆變,其中 ? 表示未知類型,super 主要用來限制未知類型的子類型 T,比如 ? super Number,只要聲明時傳入是 Number 或者 Number 的父類型都可以
  • 在 Kotlin 中關鍵字 in T 表示逆變,含義和 Java 一樣

現在我們將上面的代碼簡單修改一下,在花三秒鐘思考一下是否可以正常編譯。

// Kotlin
val numbers: MutableList<in Number> = ArrayList<Number>()
numbers.add(100)
// Java
List<? super Number> numbers = new ArrayList<Number>();
numbers.add(100);

答案可以正常編譯,逆變通配符 ? super Number 或者關鍵字 in 將繼承關系顛倒過來,主要用來限制未知類型的子類型,在上面的例子中,編譯器知道子類型是 Number,因此只要是 Number 的子類都可以添加。

逆變可以往集合中添加元素,那么可以獲取元素嗎?我們花三秒鐘時間思考一下,下面的代碼是否可以正常編譯。

// Kotlin
val numbers: MutableList<in Number> = ArrayList<Number>()
numbers.add(100)
numbers.get(0)
// Java
List<? super Number> numbers = new ArrayList<Number>();
numbers.add(100);
numbers.get(0);

無論調用 add() 方法還是調用 get() 方法,都可以正常編譯通過,現在將上面的代碼修改一下,思考一下是否可以正常編譯通過。

// Kotlin
val numbers: MutableList<in Number> = ArrayList<Number>()
numbers.add(100)
val item: Int = numbers.get(0)
// Java
List<? super Number> numbers = new ArrayList<Number>();
numbers.add(100);
int item = numbers.get(0);

調用 get() 方法會編譯失敗,因為 numbers.get(0) 獲取的的值是 Object 的類型,因此它不能直接賦值給 int 類型,逆變和協(xié)變一樣,放寬了對數據類型的約束,但是代價是 不能按照泛型類型讀取元素,也就是說往集合中添加 int 類型的數據,調用 get() 方法獲取到的不是 int 類型的數據。

對這一小節(jié)內容,我們簡單的總結一下。

關鍵字(Java/Kotlin)添加讀取
協(xié)變? extends / out??
逆變? super  / in??

Kotlin 和 Java 數組協(xié)變的不同之處

無論是 Kotlin 還是 Java 它們協(xié)變和逆變的含義的都是一樣的,只不過通配符不一樣,但是他們也有不同之處。

Java 是支持數組協(xié)變,代碼如下所示:

Number[] numbers = new Integer[10];

但是 Java 中的數組協(xié)變有缺陷,將上面的代碼修改一下,如下所示。

Number[] numbers = new Integer[10];
numbers[0] = 1.0;

可以正常編譯,但是運行的時候會崩潰。

因為最開始我將 Number[] 協(xié)變成 Integer[],接著往數組里添加了 Double 類型的數據,所以運行會崩潰。

而 Kotlin 的解決方案非常的干脆,不支持數組協(xié)變,編譯的時候就會出錯,對于數組逆變 Koltin 和 Java 都不支持。

協(xié)變和逆變的應用場景

協(xié)變和逆變應用的時候需要遵循 PECS(Producer-Extends, Consumer-Super)原則,即 ? extends 或者 out 作為生產者,? super 或者 in 作為消費者。遵循這個原則的好處是,可以在編譯階段保證代碼安全,減少未知錯誤的發(fā)生。

協(xié)變應用

  • 在 Java 中用通配符 ? extends 表示協(xié)變
  • 在 Kotlin 中關鍵字 out 表示協(xié)變

協(xié)變只能讀取數據,不能添加數據,所以只能作為生產者,向外提供數據,因此只能用來輸出,不用用來輸入。

在 Koltin 中一個協(xié)變類,參數前面加上 out 修飾后,這個參數在當前類中 只能作為函數的返回值,或者修飾只讀屬性 ,代碼如下所示。

// 正常編譯
interface ProduceExtends<out T> {
    val num: T          // 用于只讀屬性
    fun getItem(): T    // 用于函數的返回值
}
// 編譯失敗
interface ProduceExtends<out T> {
    var num : T         // 用于可變屬性
    fun addItem(t: T)   // 用于函數的參數 
}

當我們確定某個對象只作為生產者時,向外提供數據,或者作為方法的返回值時,我們可以使用 ? extends 或者 out。

  • 以 Kotlin 為例,例如 Iterator#next() 方法,使用了關鍵字 out,返回集合中每一個元素

  • 以 Java 為例,例如 ArrayList#addAll() 方法,使用了通配符 ? extends

傳入參數 Collection<? extends E> c 作為生產者給 ArrayList 提供數據。

逆變應用

  • 在 Java 中使用通配符 ? super 表示逆變
  • 在 Kotlin 中使用關鍵字 in 表示逆變

逆變只能添加數據,不能按照泛型讀取數據,所以只能作為消費者,因此只能用來輸入,不能用來輸出。

在 Koltin 中一個逆變類,參數前面加上 in 修飾后,這個參數在當前類中 只能作為函數的參數,或者修飾可變屬性 。

// 正常編譯,用于函數的參數
interface ConsumerSupper<in T> {
    fun addItem(t: T)
}
// 編譯失敗,用于函數的返回值
interface ConsumerSupper<in T> {
    fun getItem(): T
}

當我們確定某個對象只作為消費者,當做參數傳入時,只用來添加數據,我們使用通配符 ? super 或者關鍵字 in,

  • 以 Kotlin 為例,例如擴展方法 Iterable#filterTo(),使用了關鍵字 in,在內部只用來添加數據

  • 以 Java 為例,例如 ArrayList#forEach() 方法,使用了通配符 ? super

不知道小伙伴們有沒有注意到,在上面的源碼中,分別使用了不同的泛型標記符 TE,其實我們稍微注意一下,在源碼中有幾個高頻的泛型標記符 T 、 E 、 K 、 V 等等,它們分別應用在不同的場景。

標記符應用場景
T(Type)
E(Element)集合
K(Key)
V(Value)

以上就是Kotlin與Java 泛型缺陷和應用場景詳解的詳細內容,更多關于Kotlin Java 泛型缺陷的資料請關注腳本之家其它相關文章!

相關文章

  • Java中的SynchronousQueue阻塞隊列使用代碼實例

    Java中的SynchronousQueue阻塞隊列使用代碼實例

    這篇文章主要介紹了Java中的SynchronousQueue阻塞隊列使用代碼實例,SynchronousQueue是無緩沖區(qū)的阻塞隊列,即不能直接向隊列中添加數據,會報隊列滿異常,需要的朋友可以參考下
    2023-12-12
  • java實現微信App支付服務端

    java實現微信App支付服務端

    這篇文章主要為大家詳細介紹了java實現微信App支付服務端,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • 深入了解Java中循環(huán)結構的使用

    深入了解Java中循環(huán)結構的使用

    Java中有三種主要的循環(huán)結構:while 循環(huán)、do…while 循環(huán)和for 循環(huán)。本文將來和大家一起講講Java中這三個循環(huán)的使用,需要的可以參考一下
    2022-08-08
  • mybatis通過if語句實現增刪改查操作

    mybatis通過if語句實現增刪改查操作

    這篇文章主要介紹了mybatis通過if語句實現增刪改查操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • 使用Java代碼實現RocketMQ的生產與消費消息

    使用Java代碼實現RocketMQ的生產與消費消息

    這篇文章介紹一下其他的小組件以及使用Java代碼實現生產者對消息的生成,消費者消費消息等知識點,并通過代碼示例介紹的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下
    2024-07-07
  • java 解決Eclipse掛掉問題的方法

    java 解決Eclipse掛掉問題的方法

    本篇文章是對在java中解決Eclipse掛掉問題的方法進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • 親手教你IDEA2020.3創(chuàng)建Javaweb項目的步驟詳解

    親手教你IDEA2020.3創(chuàng)建Javaweb項目的步驟詳解

    這篇文章主要介紹了IDEA2020.3創(chuàng)建Javaweb項目的步驟詳解,本文是小編手把手教你,通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-03-03
  • 功能強大的TraceId?搭配?ELK使用詳解

    功能強大的TraceId?搭配?ELK使用詳解

    這篇文章主要為大家介紹了功能強大的TraceId?搭配?ELK使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-09-09
  • 高并發(fā)下如何避免重復數據產生技巧

    高并發(fā)下如何避免重復數據產生技巧

    這篇文章主要為大家介紹了高并發(fā)下如何避免重復數據的產生技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-07-07
  • java前后端使用ajax數據交互問題(簡單demo)

    java前后端使用ajax數據交互問題(簡單demo)

    這篇文章主要介紹了java前后端使用ajax數據交互問題(簡單demo),具有很好的參考價值,希望對大家有所幫助。
    2023-06-06

最新評論