Kotlin系列 - 進階深刻泛型從java到Kotlin(四)

Kotlin細節文章筆記整理更新進度:
Kotlin系列 - 基礎類型結構細節小結(一)
Kotlin系列 - 函數與類相關細節小結(二)
Kotlin系列 - 高階函數與標準庫中的經常使用函數(三)java

前言

本篇文章從java開始講泛型,後面再切換到kotlin,重點java的泛型掌握住,koltin的泛型就會很快掌握。(可自行選取節段食用,碼字不易看完以爲還能夠的,麻煩給贊,本人能力有限,有錯誤或者有問題在評論區留言,感激~~)json

總結數組

  • 虛擬機沒有泛型,只有普通方法和類。
  • 全部的類型參數都用它們的限定類型替換。
  • 橋方法被合成用於保持多態。
  • 爲保持類型安全性,必要時插入強制類型轉換。

1、泛型基礎

1. 定義

泛型,也稱參數化類型。可使代碼應用多種類型。使用類型參數,用尖括號括住,放在類名後面。在使用該類的時候用實際的類型替換該類型參數。 示例:安全

//這裏的參數T能夠自由命名
ClassA<T>{}
複製代碼

2. 存在的意義

若是在程序中只能使用具體的類型、具體的基本類型,或者自定義的類,在編寫多種類型的代碼,這種限制會對代碼有很大的約束。咱們須要一種在可在運行是才肯定類型的一種方法來實現代碼更加通用。 示例:bash

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

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

        static   public void printDouble(Double a, Double b) {
            System.out.println("參數a=" + a + "參數b=" + b);
        }
    }
複製代碼

改爲泛型函數:ide

public class PrintClass1 {
        static  public <T> void printMultiply(T a, T b) {
            System.out.println("參數a=" + a + "參數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---------------------------
參數a=10.0參數b=10.0
參數a=10參數b=10
參數a=10.0參數b=10.0

參數a=10.0參數b=10.0
參數a=10參數b=10
參數a=10.0參數b=10.0
參數a=100參數b=100
複製代碼

經過上面的展現,你們對泛型有個最基本的瞭解。oop

2、java的泛型使用

1. 接口泛型

  • 接口泛型定義:
interface Animal<T> {
    void name();
    void cry();
    void mysteryData(T t);
}
複製代碼
  • 接口泛型實現一
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("假設它擁有一種數據類型"+ s.getClass().getName());
    }
}
複製代碼
  • 接口泛型實現二
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("假設它擁有一種數據類型"+t.getClass().getName());
    }
}
複製代碼

使用:post

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日誌
狗
汪汪汪
假設它擁有一種數據類型java.lang.Integer
狗
喵喵
假設它擁有一種數據類型java.lang.String
複製代碼

2. 類泛型

上面接口泛型實現二中就是用了類泛型的實現了。ui

3. 方法泛型

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

3、泛型類型變量的限定

有時候類、方法須要對類型變量進行約束。

例如:增長一個類,用於專門打印各類動物的叫聲 (這裏泛型雖然能夠替換爲Animal,可是這個演示案例我就使用泛型替代,這個不是重點)

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

這裏你單純這樣子寫,它不會有識別到cry這個方法,由於這個方法是Animal接口的CatDog等持有的方法,因此咱們要給它個類型變量的限定。

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

格式 <T extends BoundingType> extends後面能夠跟接口或者類名T:表示爲綁定類型的子類型。 多個限定類型用&進行多個限定,例如 <T extends BoundingType & BoundingType1 & BoundingType2 & ... >

4、泛型類型擦拭

java的虛擬機中沒有泛型類型對象,全部的對象都是普通類,不管什麼時候定義一個泛型類型,都會自動體用一個對應的原始類型。原始類型的名字就是擦拭後的類型參數的類型名。若是沒有限定的類型變量則用Object代替,若是有限定則以限定的爲代替。

public class PrintClass1 {
        public static  <T> void printMultiply(T a, T b) {
            System.out.println("參數a=" + a + "參數b=" + b);
        }
    }
--------------編譯後爲
public class PrintClass1 {
        public static void printMultiply(Object a, Object  b) {
            System.out.println("參數a=" + a + "參數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();
  }
}
複製代碼

5、約束與侷限性

大多數的限制都是由類型擦拭帶來的。

  1. 不能用基本類型實例化類型參數。 沒有Pair<double>只有Pair<Double>,由於泛型擦拭後,Pair類含有Object類型的域,而Object不能存儲double值。
  2. 運行時類型查詢只適用於原始類型 虛擬機的對象老是一個非泛型類型。因此,全部的類型查詢只產生原始類型。
Pair p = new Pair("str","str1");
Pair i = new Pair(10,20);
// illegal generic type for instanceof 沒法使用instanceof關鍵字判斷泛型類的類型
 if (p instanceof Pair<String,String>)
//比較結果都是true,由於兩次調用都是返回Pair.class
if (p.getClass() == i.getClass()){} 

複製代碼
  1. 不能建立參數化類型的數組
Pair<String,String>[] pairs = new Pair[10];
pairs[0] = new Pair(10,20);
//雖然能賦值可是會致使類型錯誤。
複製代碼
  1. 不能實例化類型變量或者實例化泛型數組 不能使用相似 new T(...)new T[]T.class等表達式的變量
  2. 泛型類的靜態上下文類型變量無效
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;
      }
}

----------------------------------------------調用的時候
錯誤,返回Object類型
AType a = Singleton.getSingleInstance(); 
錯誤,這種用法是不容許的,只能在調用方法或構造方法時傳遞泛型參數
AType a = Singleton<AType>.getSingleInstance();
複製代碼
  1. 不能繼承Exception或者Throwable,不能拋出或捕獲泛型類的實例,但能夠將泛型限定的異常拋出
public class Pair<T,Q> extends Exception{} // 報錯,不能繼承Exception或者Throwable
public static <T extends Throwable> void doWork(Class<T> t){
  try{
  ....
  }catch(T e){ //報錯 這裏不能拋出泛型類型
  }
}
//正確。
public static <T extends Throwable> void doWork(T t) throws T{
  try{
  ....
  }catch(Throwable t){ 
  }
}

複製代碼

6、泛型類型繼承規則

  1. 兩個泛型參數是繼承關係,可是對應的兩個泛型沒有一點關係!
interface Animal{}
public class Cat extends Animal{}
public class Dog extends Animal{}
public class Cry<T>{}
------------------------------------------------
Cry<Animal> 與 Cry<Cat> 不是繼承關係,也沒有什麼關係。
複製代碼
  1. 泛型類能夠擴展或實現其餘的泛型類
public class ArrayList<E> extends AbstractList<E>
複製代碼

7、通配符類型(重點!!!)

小結:

協變: <? extends Class> 指定泛型類型的上限,只能讀取不能修改(修改是指對泛型集合添加元素,若是是 remove(int index)以及 clear固然是能夠的)

逆變: <? super Class> 指定泛型類型的下線,只能修改不能讀取,(不能讀取是指不能按照泛型類型讀取,你若是按照 Object 讀出來再強轉也能夠)

<?> 至關於< ? extends Object>指定沒有限制的泛型類型

Demo圖

以上面的圖爲例子:

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

它的限定範圍爲上限自己(上限能夠是接口)以及全部直接跟間接的子類。

  • 應用場景
Animal animal = new Dog();//  java的多態
List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs; //這裏會報錯 incompatible types: List<Dog> cannot be converted to List<Animal>
複製代碼

上面的例子由於發生了類型擦拭,爲了保證類型安全因此不容許這樣子賦值。 這個時候就可使用協變的寫法<? extends Class>限制參數類型的上界,也就是泛型類型必須知足這個 extends 的限制條件。

List<? extends Animal> animals = new ArrayList<Animal>(); // 自己
List<? extends Animal> animals = new ArrayList<Cat>(); //  直接子類
List<? extends Animal> animals = new ArrayList<ShamoDog>(); //  間接子類
複製代碼
  • 限制

只可以向外提供數據被消費,相似生產者

List<? extends Animal> animals = new ArrayList<Dog>();
Animal animal= animals.get(0); //get 出來的是 Animal 類型的
animals.add(textView);//報錯,no suitable method found for add(TextView)
複製代碼

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

它的限定範圍爲下限自己(下限能夠是接口)以及全部直接跟間接的父類。

  • 應用場景
List<? super ShamoDog> shamoDogs= new ArrayList<shamoDogs>(); // 自己
List<? super ShamoDog> shamoDogs= new ArrayList<WailaiDog>();//直接接父類
List<? super ShamoDog> shamoDogs= new ArrayList<Animal>();//間接父類
複製代碼
  • 限制

只能讀取到Object對象,一般也只拿它來添加數據,也就是消費已有的 List<? super ShamoDog>,往裏面添加 Dog,所以這種泛型類型聲明相對協變 能夠稱爲消費者

List<? super ShamoDog> shamoDogs = new ArrayList<Animal>();
Object object = shamoDogs.get(0); // get 出來的是 Object 類型
Dog dog = ...
shamoDogs.add(dog); // add 操做是能夠的
複製代碼

8、Kotlin的泛型

1. 格式

java泛型同樣的格式

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

2. 關鍵字outin*where

  • out:協變、與java的上限通配符<? extends BoundType>對應
  • in:逆變,與java的下限通配符<? super BoundType>對應
  • *: 與 java<?>,不過java的是<? extends Object>,kotlin的是<out Any>
  • where : 與java<T extends Animal & Person >&符號對應
//where的示例
//java中多個限定泛型定義
public class Cry <T extends Animal & Person>{ }
//kotlin的對應寫法
class Cry<T> where T:Animal,T:Person{ }
複製代碼

重點: kotlin提供另一種附加功能,在聲明類的時候,給泛型類型加上in關鍵字,代表泛型參數 T 只會用來輸入,在使用的時候就不用額外加 in 。對應out,則是代表泛型參數T只會用來輸出,使用時不須要額外加out

例子

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

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

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

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

複製代碼

3. 關鍵字reified

關於java泛型存在擦拭的狀況下,在上面5、約束性與侷限性中第二點中提到的 運行時類型查詢只適用於原始類型

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

java的解決方法:額外傳遞一個 Class<T>類型的參數,而後經過 Class#isInstance 方法來檢查

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

Kotlin的解決方法,就是reified關鍵字,可是 reified只能在inline表示的方法中使用,因此,要使用inline方法。

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

inline內聯函數,當方法在編譯時會拆解方法的調用爲語句的調用,進而減小建立沒必要要的對象。在kotlin中一個inline能夠被具體化reified,這意味着咱們能夠獲得使用泛型類型的Class

  • 項目中使用的自定義擴展Gson
//定義`Gson`的擴展函數
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源碼,能夠看到定義類泛型限定的時候爲<out E>爲協變,只用來輸出,可是這裏面的方法override fun contains(element: @UnsafeVariance E): Boolean爲了支持輸入,則使用@UnsafeVariance避免IDE的檢查

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

  • Java 裏的數組是支持協變的,而 Kotlin 中的數組 Array 不支持協變。 Kotlin中數組是用 Array 類來表示的,這個 Array 類使用泛型就和集合類同樣,因此不支持協變。

  • Java 中的 List 接口不支持協變,而 Kotlin 中的 List 接口支持協變。 在 Kotlin 中,實際上 MutableList 接口才至關於 JavaListKotlin 中的 List 接口實現了只讀操做,沒有寫操做,因此不會有類型安全上的問題,天然能夠支持協變。

感謝

Kotlin 的泛型 Java核心技術 卷一

相關文章
相關標籤/搜索