Kotlin系列 - 進(jìn)階深入泛型從java到Kotlin(四)

Kotlin細(xì)節(jié)文章筆記整理更新進(jìn)度:
Kotlin系列 - 基礎(chǔ)類型結(jié)構(gòu)細(xì)節(jié)小結(jié)(一)
Kotlin系列 - 函數(shù)與類相關(guān)細(xì)節(jié)小結(jié)(二)
Kotlin系列 - 高階函數(shù)與標(biāo)準(zhǔn)庫(kù)中的常用函數(shù)(三)

目錄.png

前言

本篇文章從java開(kāi)始講泛型,后面再切換到kotlin,重點(diǎn)java的泛型掌握住,koltin的泛型就會(huì)很快掌握。(可自行選取節(jié)段食用,碼字不易看完覺(jué)得還可以的,麻煩給贊,本人能力有限,有錯(cuò)誤或者有問(wèn)題在評(píng)論區(qū)留言,感激~~)

總結(jié)

  • 虛擬機(jī)沒(méi)有泛型,只有普通方法和類。
  • 所有的類型參數(shù)都用它們的限定類型替換。
  • 橋方法被合成用于保持多態(tài)。
  • 為保持類型安全性,必要時(shí)插入強(qiáng)制類型轉(zhuǎn)換。

一、泛型基礎(chǔ)

1. 定義

泛型,也稱參數(shù)化類型。可以使代碼應(yīng)用多種類型。使用類型參數(shù),用尖括號(hào)括住,放在類名后面。在使用該類的時(shí)候用實(shí)際的類型替換該類型參數(shù)。
示例:

//這里的參數(shù)T可以自由命名
ClassA<T>{}

2. 存在的意義

如果在程序中只能使用具體的類型、具體的基本類型,或者自定義的類,在編寫多種類型的代碼,這種限制會(huì)對(duì)代碼有很大的約束。我們需要一種在可在運(yùn)行是才確定類型的一種方法來(lái)實(shí)現(xiàn)代碼更加通用。
示例:

public class PrintClass {
        static  public void printInt(Integer a, Integer b) {
            System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
        }

        static   public void printFloat(Float a, Float b) {
            System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
        }

        static   public void printDouble(Double a, Double b) {
            System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
        }
    }

改成泛型函數(shù):

public class PrintClass1 {
        static  public <T> void printMultiply(T a, T b) {
            System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
        }
    }

使用:

    public static void main(String[] args) {
        PrintClass.printDouble(10.0,10.0);
        PrintClass.printInt(10,10);
        PrintClass.printFloat(10f,10f);

        PrintClass1.printMultiply(10.0,10.0);
        PrintClass1.printMultiply(10,10);
        PrintClass1.printMultiply(10f,10f);
        PrintClass1.printMultiply("100","100");
    }
-----------------------打印的Log---------------------------
參數(shù)a=10.0參數(shù)b=10.0
參數(shù)a=10參數(shù)b=10
參數(shù)a=10.0參數(shù)b=10.0

參數(shù)a=10.0參數(shù)b=10.0
參數(shù)a=10參數(shù)b=10
參數(shù)a=10.0參數(shù)b=10.0
參數(shù)a=100參數(shù)b=100

通過(guò)上面的展示,大家對(duì)泛型有個(gè)最基本的了解。

二、java的泛型使用

1. 接口泛型

  • 接口泛型定義:
interface Animal<T> {
    void name();
    void cry();
    void mysteryData(T t);
}
  • 接口泛型實(shí)現(xiàn)一
public class Cat implements Animal<String> {
    @Override
    public void name() {
        System.out.println("貓");
    }
    @Override
    public void cry() {
        System.out.println("喵喵");
    }
    @Override
    public void mysteryData(String s) {
        System.out.println("假設(shè)它擁有一種數(shù)據(jù)類型"+ s.getClass().getName());
    }
}
  • 接口泛型實(shí)現(xiàn)二
public class Dog<T> implements Animal<T> {
    @Override
    public void name() {
        System.out.println("狗");
    }
    @Override
    public void cry() {
        System.out.println("汪汪汪");
    }
    @Override
    public void mysteryData(T t) {
        System.out.println("假設(shè)它擁有一種數(shù)據(jù)類型"+t.getClass().getName());
    }
}

使用:

    public static void main(String[] args) {
        Dog<Integer> dog = new Dog();
        dog.name();
        dog.cry();
        dog.mysteryData(10);

        Cat cat =new Cat();
        dog.name();
        cat.cry();
        cat.mysteryData("String");
    }
------------------------------log日志
狗
汪汪汪
假設(shè)它擁有一種數(shù)據(jù)類型java.lang.Integer
狗
喵喵
假設(shè)它擁有一種數(shù)據(jù)類型java.lang.String

2. 類泛型

上面接口泛型實(shí)現(xiàn)二中就是用了類泛型的實(shí)現(xiàn)了。

3. 方法泛型

public class PrintClass1 {
        static  public <T> void printMultiply(T a, T b) {
            System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
        }
    }

三、泛型類型變量的限定

有時(shí)候類、方法需要對(duì)類型變量進(jìn)行約束。

例如:增加一個(gè)類,用于專門打印各種動(dòng)物的叫聲
(這里泛型雖然可以替換為Animal,但是這個(gè)演示案例我就使用泛型替代,這個(gè)不是重點(diǎn))

class AnimalCry{
  public static <T> void cry(T a){
    a.cry();
    }
}

這里你單純這樣子寫,它不會(huì)有識(shí)別到cry這個(gè)方法,因?yàn)檫@個(gè)方法是Animal接口的Cat、Dog等持有的方法,所以我們要給它個(gè)類型變量的限定。

 class AnimalCry{
  public static <T extends Animal> void cry(T a){
    a.cry();
  }
}
--------------------調(diào)用
  public static void main(String[] args) {
            AnimalCry.cry(new Dog());
    }
--------------------打印的Log
汪汪汪

格式 <T extends BoundingType> extends后面可以跟接口或者類名
T:表示為綁定類型的子類型。
多個(gè)限定類型用&進(jìn)行多個(gè)限定,例如
<T extends BoundingType & BoundingType1 & BoundingType2 & ... >

四、泛型類型擦拭

java的虛擬機(jī)中沒(méi)有泛型類型對(duì)象,所有的對(duì)象都是普通類,無(wú)論何時(shí)定義一個(gè)泛型類型,都會(huì)自動(dòng)體用一個(gè)對(duì)應(yīng)的原始類型。原始類型的名字就是擦拭后的類型參數(shù)的類型名。如果沒(méi)有限定的類型變量則用Object代替,如果有限定則以限定的為代替。

public class PrintClass1 {
        public static  <T> void printMultiply(T a, T b) {
            System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
        }
    }
--------------編譯后為
public class PrintClass1 {
        public static void printMultiply(Object a, Object  b) {
            System.out.println("參數(shù)a=" + a + "參數(shù)b=" + b);
        }
    }
class AnimalCry{
  public static <T extends Animal> void cry(T a){
    a.cry();
  }
}
--------------編譯后為
class AnimalCry{
  public static void cry(Animal a){
    a.cry();
  }
}

五、約束與局限性

大多數(shù)的限制都是由類型擦拭帶來(lái)的。

  1. 不能用基本類型實(shí)例化類型參數(shù)。
    沒(méi)有Pair<double>只有Pair<Double>,因?yàn)榉盒筒潦煤螅?code>Pair類含有Object類型的域,而Object不能存儲(chǔ)double值。
  2. 運(yùn)行時(shí)類型查詢只適用于原始類型
    虛擬機(jī)的對(duì)象總是一個(gè)非泛型類型。所以,所有的類型查詢只產(chǎn)生原始類型。
Pair p = new Pair("str","str1");
Pair i = new Pair(10,20);
// illegal generic type for instanceof 無(wú)法使用instanceof關(guān)鍵字判斷泛型類的類型
 if (p instanceof Pair<String,String>)
//比較結(jié)果都是true,因?yàn)閮纱握{(diào)用都是返回Pair.class
if (p.getClass() == i.getClass()){} 

  1. 不能創(chuàng)建參數(shù)化類型的數(shù)組
Pair<String,String>[] pairs = new Pair[10];
pairs[0] = new Pair(10,20);
//雖然能賦值但是會(huì)導(dǎo)致類型錯(cuò)誤。
  1. 不能實(shí)例化類型變量或者實(shí)例化泛型數(shù)組
    不能使用類似 new T(...)、new T[]T.class等表達(dá)式的變量
  2. 泛型類的靜態(tài)上下文類型變量無(wú)效
public class Singleton<T>{
      private static T singleInstance;  //ERROR

      public static T getSingleInstance(){ //ERROR
          if(singleInstance == null) 
              return singleInstance;
      }
}
------------------------------------類型擦除后被替換成Object具體類
public class Singleton{
      private static Object singleInstance; 

      public static Obejct getSingleInstance(){
          if(singleInstance == null) 
              return singleInstance;
      }
}

----------------------------------------------調(diào)用的時(shí)候
錯(cuò)誤,返回Object類型
AType a = Singleton.getSingleInstance(); 
錯(cuò)誤,這種用法是不允許的,只能在調(diào)用方法或構(gòu)造方法時(shí)傳遞泛型參數(shù)
AType a = Singleton<AType>.getSingleInstance();
  1. 不能繼承Exception或者Throwable,不能拋出或捕獲泛型類的實(shí)例,但可以將泛型限定的異常拋出
public class Pair<T,Q> extends Exception{} // 報(bào)錯(cuò),不能繼承Exception或者Throwable
public static <T extends Throwable> void doWork(Class<T> t){
  try{
  ....
  }catch(T e){ //報(bào)錯(cuò) 這里不能拋出泛型類型
  }
}
//正確。
public static <T extends Throwable> void doWork(T t) throws T{
  try{
  ....
  }catch(Throwable t){ 
  }
}

六、泛型類型繼承規(guī)則

  1. 兩個(gè)泛型參數(shù)是繼承關(guān)系,但是對(duì)應(yīng)的兩個(gè)泛型沒(méi)有一點(diǎn)關(guān)系!
interface Animal{}
public class Cat extends Animal{}
public class Dog extends Animal{}
public class Cry<T>{}
------------------------------------------------
Cry<Animal> 與 Cry<Cat> 不是繼承關(guān)系,也沒(méi)有什么關(guān)系。
  1. 泛型類可以擴(kuò)展或?qū)崿F(xiàn)其他的泛型類
public class ArrayList<E> extends AbstractList<E>

七、通配符類型(重點(diǎn)!?。。?/h3>

小結(jié):

協(xié)變: <? extends Class> 指定泛型類型的上限,只能讀取不能修改(修改是指對(duì)泛型集合添加元素,如果是 remove(int index)以及 clear當(dāng)然是可以的)

逆變: <? super Class> 指定泛型類型的下線,只能修改不能讀取,(不能讀取是指不能按照泛型類型讀取,你如果按照 Object 讀出來(lái)再?gòu)?qiáng)轉(zhuǎn)也可以)

<?> 相當(dāng)于< ? extends Object>指定沒(méi)有限制的泛型類型

Demo圖

以上面的圖為例子:

1. 協(xié)變: <? extends Class> 指定泛型類型的上限

它的限定范圍為上限本身(上限可以是接口)以及所有直接跟間接的子類。

  • 應(yīng)用場(chǎng)景
Animal animal = new Dog();//  java的多態(tài)
List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs; //這里會(huì)報(bào)錯(cuò) incompatible types: List<Dog> cannot be converted to List<Animal>

上面的例子因?yàn)榘l(fā)生了類型擦拭,為了保證類型安全所以不允許這樣子賦值。
這個(gè)時(shí)候就可以使用協(xié)變的寫法<? extends Class>限制參數(shù)類型的上界,也就是泛型類型必須滿足這個(gè) extends 的限制條件。

List<? extends Animal> animals = new ArrayList<Animal>(); // 本身
List<? extends Animal> animals = new ArrayList<Cat>(); //  直接子類
List<? extends Animal> animals = new ArrayList<ShamoDog>(); //  間接子類
  • 限制

只能夠向外提供數(shù)據(jù)被消費(fèi),類似生產(chǎn)者。

List<? extends Animal> animals = new ArrayList<Dog>();
Animal animal= animals.get(0); //get 出來(lái)的是 Animal 類型的
animals.add(textView);//報(bào)錯(cuò),no suitable method found for add(TextView)

2. 逆變: <? super Class> 指定泛型類型的下限

它的限定范圍為下限本身(下限可以是接口)以及所有直接跟間接的父類。

  • 應(yīng)用場(chǎng)景
List<? super ShamoDog> shamoDogs= new ArrayList<shamoDogs>(); // 本身
List<? super ShamoDog> shamoDogs= new ArrayList<WailaiDog>();//直接接父類
List<? super ShamoDog> shamoDogs= new ArrayList<Animal>();//間接父類
  • 限制

只能讀取到Object對(duì)象,通常也只拿它來(lái)添加數(shù)據(jù),也就是消費(fèi)已有的 List<? super ShamoDog>,往里面添加 Dog,因此這種泛型類型聲明相對(duì)協(xié)變 可以稱為消費(fèi)者

List<? super ShamoDog> shamoDogs = new ArrayList<Animal>();
Object object = shamoDogs.get(0); // get 出來(lái)的是 Object 類型
Dog dog = ...
shamoDogs.add(dog); // add 操作是可以的

八、Kotlin的泛型

1. 格式

java泛型一樣的格式

interface AnimalKot<T> { } //接口泛型
class DogKot<Q> { } //類泛型
fun <T>TestLooperManager(t:T): Unit { }//方法泛型

2. 關(guān)鍵字out、in*、where

  • out:協(xié)變、與java的上限通配符<? extends BoundType>對(duì)應(yīng)
  • in:逆變,與java的下限通配符<? super BoundType>對(duì)應(yīng)
  • *: 與 java<?>,不過(guò)java的是<? extends Object>,kotlin的是<out Any>
  • where : 與java<T extends Animal & Person >&符號(hào)對(duì)應(yīng)
//where的示例
//java中多個(gè)限定泛型定義
public class Cry <T extends Animal & Person>{ }
//kotlin的對(duì)應(yīng)寫法
class Cry<T> where T:Animal,T:Person{ }

重點(diǎn):
kotlin提供另外一種附加功能,在聲明類的時(shí)候,給泛型類型加上in關(guān)鍵字,表明泛型參數(shù) T 只會(huì)用來(lái)輸入,在使用的時(shí)候就不用額外加 in 。對(duì)應(yīng)out,則是表明泛型參數(shù)T只會(huì)用來(lái)輸出,使用時(shí)不需要額外加out。

例子

//koltin的 List
public interface List<out E> : Collection<E> {
}

var animalKot:List<Animal<String>> = ArrayList<Dog<String>>()// 不報(bào)錯(cuò)
var animalKot:List<out Animal<String>> = ArrayList<Dog<String>>()//寫了out,不報(bào)錯(cuò)
var animalKot:List<in Dog<String>> = ArrayList<Animal<String>>()//報(bào)錯(cuò),不能寫in

//定義一個(gè) in泛型類型
class All<in T>{
    fun p(t:T){
    }
}

var all:All<Dog<String>> = All()// 不報(bào)錯(cuò)
var all:All<in Dog<String>> = All()//寫了in,不報(bào)錯(cuò)
var all:All<out Dog<String>> = All()//報(bào)錯(cuò),不能寫in

3. 關(guān)鍵字reified

關(guān)于java泛型存在擦拭的情況下,在上面五、約束性與局限性中第二點(diǎn)中提到的
運(yùn)行時(shí)類型查詢只適用于原始類型

<T> void println(Object obj) {
    if (obj instanceof T) { // IDE提示錯(cuò)誤,illegal generic type for instanceof
    }
}
kotlin也是如此---------------------------------------------------------
fun <T> println(any: Any) {
    if (any is T) { // IDE提示錯(cuò)誤,Cannot check for instance of erased type: T
    }
}

java的解決方法:額外傳遞一個(gè) Class<T>類型的參數(shù),然后通過(guò) Class#isInstance 方法來(lái)檢查

<T> void println(Object obj, Class<T> type) {
    if (type.isInstance(obj )) { 
    }
}

Kotlin的解決方法,就是reified關(guān)鍵字,但是 reified只能在inline表示的方法中使用,所以,要使用inline方法。

inline fun <reified T> println(any: Any) {
    if (any is T) {
        println(item)
    }
}

inline內(nèi)聯(lián)函數(shù),當(dāng)方法在編譯時(shí)會(huì)拆解方法的調(diào)用為語(yǔ)句的調(diào)用,進(jìn)而減少創(chuàng)建不必要的對(duì)象。在kotlin中一個(gè)inline可以被具體化reified,這意味著我們可以得到使用泛型類型的Class。

  • 項(xiàng)目中使用的自定義擴(kuò)展Gson
//定義`Gson`的擴(kuò)展函數(shù)
inline fun <reified T> Gson.fromJson(json:String):T = fromJson(json,T::class.java)

4. 注解 @UnsafeVariance

public interface List<out E> : Collection<E> {
    // Query Operations

    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator<E>

    // Bulk Operations
    override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
    ....

上面是kotlinList源碼,可以看到定義類泛型限定的時(shí)候?yàn)?code><out E>為協(xié)變,只用來(lái)輸出,但是這里面的方法override fun contains(element: @UnsafeVariance E): Boolean為了支持輸入,則使用@UnsafeVariance避免IDE的檢查

5. Kotlin 泛型與 Java 泛型不一致的地方

  • Java 里的數(shù)組是支持協(xié)變的,而 Kotlin 中的數(shù)組 Array 不支持協(xié)變。
    Kotlin中數(shù)組是用 Array 類來(lái)表示的,這個(gè) Array 類使用泛型就和集合類一樣,所以不支持協(xié)變。

  • Java 中的 List 接口不支持協(xié)變,而 Kotlin 中的 List 接口支持協(xié)變。
    Kotlin 中,實(shí)際上 MutableList 接口才相當(dāng)于 JavaList。Kotlin 中的 List 接口實(shí)現(xiàn)了只讀操作,沒(méi)有寫操作,所以不會(huì)有類型安全上的問(wèn)題,自然可以支持協(xié)變。

感謝

Kotlin 的泛型
Java核心技術(shù) 卷一

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 8,127評(píng)論 12 51
  • 泛型 泛型(Generic Type)簡(jiǎn)介 通常情況的類和函數(shù),我們只需要使用具體的類型即可:要么是基本類型,要么...
    Tenderness4閱讀 1,519評(píng)論 4 2
  • Java為什么引入泛型 眾所周知,Java 5才最大的亮點(diǎn)就是引入泛型,那么Java引入泛型的目的是什么?這就需要...
    大棋17閱讀 1,918評(píng)論 0 3
  • 太陽(yáng)早就落山了殘留的紅霞是一堆爐底將熄的碳火 風(fēng)會(huì)來(lái),捎來(lái)某些消息讓長(zhǎng)河里的蘆葦花不停搖擺倒影會(huì)被寄走天空的眼角,...
    一團(tuán)菌閱讀 368評(píng)論 13 17
  • 臺(tái)風(fēng)溫比亞帶了的大雨已經(jīng)連續(xù)下了兩天了。淄博的很多街道已經(jīng)被水淹了。由于下雨的原因,今天來(lái)現(xiàn)場(chǎng)共學(xué)同學(xué)并不多。我意...
    bdec4e2f6612閱讀 696評(píng)論 0 49

友情鏈接更多精彩內(nèi)容