深入理解Java\Kotlin泛型

泛型產(chǎn)生

Java泛型是JDK1.5引入的一個(gè)新特性,是一種參數(shù)化類型。參數(shù)化類型就是在不創(chuàng)建新類型的情況下,通過泛型指定的泛泛類型控制形參限制的類型。允許在編譯期檢測(cè)非法類型。

泛型特點(diǎn)

  • 類型安全。使用泛型定義的參數(shù)進(jìn)行,在編譯期可以對(duì)一個(gè)類型進(jìn)行驗(yàn)證,從而更快的暴露問題
  • 消除強(qiáng)制類型轉(zhuǎn)換。
  • 避免了不必要的裝箱、拆箱操作,提高程序性能
  • 提高代碼的重用性

命名類型參數(shù)

  • E - 元素,主要由Java集合(Collections)框架使用。
  • K - 鍵,主要用于表示映射中的鍵的參數(shù)類型。
  • V - 值,主要用于表示映射中的值的參數(shù)類型。
  • N - 數(shù)字,主要用于表示數(shù)字。
  • T - 類型,主要用于表示第一類通用型參數(shù)。
  • S - 類型,主要用于表示第二類通用類型參數(shù)。
  • U - 類型,主要用于表示第三類通用類型參數(shù)。
  • V - 類型,主要用于表示第四個(gè)通用類型參數(shù)。

泛型定義

泛型類

泛型類的聲明和普通類聲明類似,除了在類名后添加類型參數(shù)聲明。
定義

修飾符 class 類名<聲明自定義泛型> {
    ...
}

實(shí)例

public static void main(String[] args) {
    Container<String ,String> c1 = new Container<>("name", "kevin");
    Container<String, Integer> c2 = new Container<>("age", 29);
    Container<Double, Integer> c3 = new Container<>(1.0, 29);
}
public static class Container<K, V> {
    K key;
    V value;
    Container(K k, V v) {
        key = k;
        value = v;
    }
}

泛型接口

定義

修飾符 interface 接口名<聲明自定義泛型>{

}

實(shí)例

public interface Generator<T> {
    T init();
}

public class GeneratorClass1 implements Generator<String> {
    @Override
    public String init() {
        return "test1";
    }
}

public class GeneratorClass2 implements Generator<Integer> {
    @Override
    public Integer init() {
        return 0;
    }
}

泛型方法

定義

修飾符 返回值類型 接口名<聲明自定義泛型>{

}

實(shí)例

public interface Generator<T> {
    T init();
}

public class GeneratorClass1 implements Generator<String> {
    @Override
    public String init() {
        return "test1";
    }
}

public class GeneratorClass2 implements Generator<Integer> {
    @Override
    public Integer init() {
        return 0;
    }
}

通配符

通配符的產(chǎn)生

任何使用父類的地方可以被它的子類替換,我們?cè)谑褂妙惡蛯?duì)象時(shí)經(jīng)常會(huì)接觸到里式替換原則,其實(shí)在數(shù)組中一樣也符合這種原則
如下:

  class Fruit {}
    class Apple extends Fruit {}
    class Jonathan extends Apple {}
    class Orange extends Fruit {}

    public class CovariantArrays {
        public  void main(String[] args) {
            Fruit[] fruit = new Apple[10]; // OK
            List<Fruit> fruits=new ArrayList<Apple>();//error
            fruit[0] = new Apple(); // OK
            fruit[1] = new Jonathan(); // OK
            // Runtime type is Apple[], not Fruit[] or Orange[]:
            try {
                // Compiler allows you to add Fruit:
                fruit[0] = new Fruit(); // ArrayStoreException
            } catch(Exception e) { System.out.println(e); }
            try {
                // Compiler allows you to add Oranges:
                fruit[0] = new Orange(); // ArrayStoreException
            } catch(Exception e) { System.out.println(e); }
        }
    }

數(shù)組中的這種向上轉(zhuǎn)變稱為數(shù)組協(xié)變,而泛型是不支持的,如下代碼

 List<Fruit> fruits=new ArrayList<Apple>();//error

如上代碼會(huì)產(chǎn)生編譯時(shí)錯(cuò)誤,之所以這么設(shè)計(jì)是因?yàn)閿?shù)組支持運(yùn)行時(shí)檢查而集合不支持運(yùn)行時(shí)檢查。

Java的泛型的這種特性對(duì)于有需要向上轉(zhuǎn)型的需求時(shí)就無能為力,所以 Java 為了滿足這種需求設(shè)計(jì)出了通配符.

上邊界限定通配符[Java]/協(xié)變[Kotln]

Java

Java語言利用 <? extends T> 形式的通配符可以實(shí)現(xiàn)泛型的向上轉(zhuǎn)型:

 static void ccc() {
    Apple apple = new Apple();
    apple.name = "Apple";
    ArrayList<? extends Fruit> fruits = new ArrayList<>();
    fruits.add(apple); //Error

    for (int i = 0; i < fruits.size(); i++) {
        Fruit fruit = (Fruit) fruits.get(i);
        System.out.println("println---" + fruit.name);
    }
}

Kotlin

Kotlin語言利用<out T>形式的通配符實(shí)現(xiàn)泛型的向上轉(zhuǎn)型:

 fun ccc() {
    val apple = Apple()
    apple.name = "Apple"
    val fruits: ArrayList<out Fruit> = ArrayList()
    fruits.add(apple) //Error
    for (i in fruits.indices) {
        println("println---" + fruits[i].name)
    }
}

使用上通配符后編譯器為了保證運(yùn)行時(shí)的安全,會(huì)限定對(duì)其寫的操作,開放讀的操作,因?yàn)榫幾g器只能保證 fruits 集合中存在的是 Fruits 及它的子類,并不知道具體的類型,所以上述代碼fruits.add(apple)會(huì)報(bào)錯(cuò)

下邊界限定通配符[Java]/逆變[Kotln]

Java

Java語言利用 <? super T> 形式的通配符可以實(shí)現(xiàn)泛型的向上轉(zhuǎn)型:

static void ccc() {
    Apple apple = new Apple();
    apple.name = "Apple";
    ArrayList<? super Apple> fruits = new ArrayList<>();
    fruits.add(apple);//OK
    fruits.add(new Fruits()); //Error
    for (int i = 0; i < fruits.size(); i++) {
        Apple fruit = fruits.get(i);//Error
        System.out.println("println---" + fruit.name);
    }
}

Kotlin

Kotlin語言利用<in T>形式的通配符實(shí)現(xiàn)泛型的向上轉(zhuǎn)型:

fun ccc() {
    val apple = Apple()
    apple.name = "Apple"
    val fruits: ArrayList<in Apple> = ArrayList()
    fruits.add(apple) //OK
    fruits.add(new Fruits()) //Error
    for (i in fruits.indices) {
        println("println---" + fruits[i].name) //error
    }
}

與上邊界通配符相反,下邊界通配符通常限定讀的操作,開放寫的操作,對(duì)于如上代碼,它標(biāo)示某種類型的List,這個(gè)類型是Apple的基礎(chǔ)類型。也就是說,我們實(shí)際上并不知道類型是什么,但是這個(gè)類型肯定是Apple的父類型。因此,我們知道向這個(gè)List添加一個(gè)Apple對(duì)象或者其子類型對(duì)象是安全的,這些對(duì)象都可以向上轉(zhuǎn)型為Apple。但是我們不知道加入Fruit對(duì)象是否安全,

無邊界通配符[Java]/星投影[Kotln]

還有一種通配符是無邊界通配符,它的使用形式是一個(gè)單獨(dú)的問號(hào):List<?>,也就是沒有任何限定

Java

<?> 

kotlin

<*>

無邊界通配符或星投影是沒有任何限定的,正是由于其沒任何限定,所以我們并不能確定參數(shù)是哪種類型,此時(shí)我們也是不可以往其中添加對(duì)象的。

MutableList<?>MutableList有什么區(qū)別呢?

如下代碼

Java

  List<?> list1 = new ArrayList<>();
  aaa.add(""); //Error

  List list2 = new ArrayList();
  aaa1.add(""); //Ok

Kotlin


  val list1: MutableList<*> = mutableListOf<Any>()
  fruits.add(Fruit()) //Error

  val list2: MutableList<Any> = mutableListOf<Any>()
  fruits.add(Fruit()) //OK

MutableList<*> list 表示 list 是持有某種特定類型的 MutableList,但是不知道具體是哪種類型。那么我們可以向其中添加對(duì)象嗎?當(dāng)然不可以,因?yàn)椴⒉恢缹?shí)際是哪種類型,所以不能添加任何類型,這是不安全的。而 MutableList<Any> list ,也就是沒有傳入泛型參數(shù),表示這個(gè) list 持有的元素的類型是 Any也就是Object,因此可以添加任何類型的對(duì)象,只不過編譯器會(huì)有警告信息。

泛型參數(shù)約束

單個(gè)泛型參數(shù)約束[Java]/[Kotln]

單個(gè)泛型約束還是很簡單的,Java中我們使用extends進(jìn)行約束,Kotlin中使用

我們想實(shí)現(xiàn)兩個(gè)泛型參數(shù)的比較,使用Comparable進(jìn)行比較,代碼如下:

Java

public <T extends Comparable<T>> T maxOf(T params1, T params2) {
    if (params1.compareTo(params2) > 0) {
        return params1;
    } else {
        return params2;
    }
}

Kotlin

fun <T: Comparable<T>> maxOf(params1: T, params2: T): T {
    return if (params1 > params2) {
        params1
    } else{
        params2
    }
}

單個(gè)約束我們?cè)谥耙呀?jīng)了解情況了,那如果我們想實(shí)現(xiàn)多個(gè)約束該怎么辦呢?

多個(gè)泛型參數(shù)約束[Java]/[Kotlin]

Java中實(shí)現(xiàn)多個(gè)泛型參數(shù)使用& Supplier,而Kotlin中使用where

還是如上代碼,我們改造一下:

Java

<T extends Comparable<T> & Supplier<R>, R extends String> R maxOf(T params1, T params2) {
    if (params1.compareTo(params2) > 0) {
        return params1.get();
    } else {
        return params2.get();
    }
}

Kotlin

fun <T, R> maxOf(params1: T, params2: T): R where T: Comparable<T>, T:() ->R {
    if (params1 > params2) {
        return params1.invoke()
    } else {
        return params2.invoke()
    }
}

類型擦除

我們都知道,Java的泛型是偽泛型,這是因?yàn)镴ava在編譯期間,所有的泛型信息都會(huì)被擦掉,正確理解泛型概念的首要前提是理解類型擦除。Java的泛型基本上都是在編譯器這個(gè)層次上實(shí)現(xiàn)的,在生成的字節(jié)碼中是不包含泛型中的類型信息的,使用泛型的時(shí)候加上類型參數(shù),在編譯器編譯的時(shí)候會(huì)去掉,這個(gè)過程成為類型擦除。

例如定義List<Object>和List<String>等類型,在編譯后都會(huì)變成List,JVM看到的只是List,而由泛型附加的類型信息對(duì)JVM是看不到的

  • 1:原始類型相等
public class Test {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);
        System.out.println(list1.getClass() == list2.getClass());
    }
}

在這個(gè)例子中,我們定義了兩個(gè)ArrayList數(shù)組,不過一個(gè)是ArrayList<String>泛型類型的,只能存儲(chǔ)字符串;一個(gè)是ArrayList<Integer>泛型類型的,只能存儲(chǔ)整數(shù),最后,我們通過list1對(duì)象和list2對(duì)象的getClass()方法獲取他們的類的信息,最后發(fā)現(xiàn)結(jié)果為true。說明泛型類型String和Integer都被擦除掉了,只剩下原始類型。

  • 2:通過反射添加其它類型元素
public class Test {
    public static void main(String[] args) throws Exception {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(1);  //這樣調(diào)用 add 方法只能存儲(chǔ)整形,因?yàn)榉盒皖愋偷膶?shí)例為 Integer
        list.getClass().getMethod("add", Object.class).invoke(list, "asd");
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
}

在程序中定義了一個(gè)ArrayList泛型類型實(shí)例化為Integer對(duì)象,如果直接調(diào)用add()方法,那么只能存儲(chǔ)整數(shù)數(shù)據(jù),不過當(dāng)我們利用反射調(diào)用add()方法的時(shí)候,卻可以存儲(chǔ)字符串,這說明了Integer泛型實(shí)例在編譯之后被擦除掉了,只保留了原始類型。

  • 3:類型擦除后保留的原始類型

原始類型 就是擦除去了泛型信息,最后在字節(jié)碼中的類型變量的真正類型,無論何時(shí)定義一個(gè)泛型,相應(yīng)的原始類型都會(huì)被自動(dòng)提供,類型變量擦除,并使用其限定類型(無限定的變量用Object)替換。

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  duyou
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}

因?yàn)樵赑air<T>中,T 是一個(gè)無限定的類型變量,所以用Object替換,其結(jié)果就是一個(gè)普通的類,如同泛型加入Java語言之前的已經(jīng)實(shí)現(xiàn)的樣子。在程序中可以包含不同類型的Pair,如Pair<String>或Pair<Integer>,但是擦除類型后他們的就成為原始的Pair類型了,原始類型都是Object。

關(guān)于類型擦除的問題,查看大神文章即可

類型擦除引起的問題和解決辦法

因?yàn)榉N種原因[部分原因是因?yàn)閖ava要兼容所有用戶,在Jdk1.5之前是沒有泛型的,但是這個(gè)量級(jí)又比較龐大,所以Sun公司不得已才使用偽泛型],Java不能實(shí)現(xiàn)真正的泛型,只能使用類型擦除的偽泛型,但是這也引發(fā)了一些新的問題。

1:先檢查、再編譯,以及檢查編譯的對(duì)象和引用傳遞問題
先來看一段代碼

ArrayList<String> list1 = new ArrayList<>();
list.add(1)  //編譯錯(cuò)誤
list.add("1") //編譯通過 

以上代碼,我們定義了一個(gè)可讀可寫的list,在使用add傳入一個(gè)Integer對(duì)象時(shí),程序立馬報(bào)錯(cuò)。這就說明程序在編譯之前先進(jìn)行類型檢查。

那么這個(gè)類型檢查到底是針對(duì)誰的呢?再看代碼

ArrayList<String> list1 = new ArrayList<>();
list1.add(1); //編譯錯(cuò)誤
list1.add(""); //編譯通過


ArrayList list2 = new ArrayList<String>();
list2.add(1); //編譯通過 
list2.add(""); //編譯通過 

可以看到,我們使用list2創(chuàng)建對(duì)象時(shí),程序是無錯(cuò)誤的,意思是我們可以傳入任何對(duì)象,那原因是什么呢?

主要原因是new ArrayList()只是在內(nèi)存中開辟了一個(gè)存儲(chǔ)空間,可以存儲(chǔ)任何類型對(duì)象,而真正涉及類型檢測(cè)的是它的引用,因?yàn)槲覀兊膌ist1引用是ArrayList<String>,所以能完成泛型類型的檢測(cè),而list2引用的是ArrayList,沒有使用泛型,所以是不行的。

舉個(gè)更全面的例子:

public static void main(String[] args) {  
    ArrayList<String> arrayList1=new ArrayList();  
    arrayList1.add("1");//編譯通過  
    arrayList1.add(1);//編譯錯(cuò)誤  
    String str1=arrayList1.get(0);//返回類型就是String  
        
    ArrayList arrayList2=new ArrayList<String>();  
    arrayList2.add("1");//編譯通過  
    arrayList2.add(1);//編譯通過  
    Object object=arrayList2.get(0);//返回類型就是Object  
        
    new ArrayList<String>().add("11");//編譯通過  
    new ArrayList<String>().add(22);//編譯錯(cuò)誤  
    String string=new ArrayList<String>().get(0);//返回類型就是String  
}  

2:自動(dòng)類型轉(zhuǎn)換

因?yàn)轭愋筒脸膯栴},所以所有的泛型類型變量最后都會(huì)被替換為原始類型,這樣就有一個(gè)疑問。既然都被替換為原始類型,那么為什么我們?cè)讷@取的時(shí)候,不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換呢?看下ArrayList的get方法:

public E get(int index) {  
    RangeCheck(index);  
    return (E) elementData[index];  
}  

可以看到,在return之前,會(huì)根據(jù)泛型變量進(jìn)行強(qiáng)轉(zhuǎn)。假設(shè)泛型類型變量為Date,雖然泛型信息會(huì)被擦除掉,但是會(huì)將(E) elementData[index],編譯為(Date)elementData[index]。所以我們不用自己進(jìn)行強(qiáng)轉(zhuǎn)。

3:類型擦除引起和多態(tài)的沖突

現(xiàn)在有這樣一個(gè)泛型類:

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T value) {  
        this.value = value;  
    }  
}

然后我們想要一個(gè)子類繼承它

class DateInter extends Pair<Date> {  
    @Override  
    public void setValue(Date value) {  
        super.setValue(value);  
    }  
    @Override  
    public Date getValue() {  
        return super.getValue();  
    }  
}

在這個(gè)子類中,我們?cè)O(shè)定父類的泛型類型為Pair<Date>,在子類中,我們覆蓋了父類的兩個(gè)方法,我們的原意是這樣的:
將父類的泛型類型限定為Date,那么父類里面的兩個(gè)方法的參數(shù)都為Date類型:“

public Date getValue() {  
    return value;  
}  
public void setValue(Date value) {  
    this.value = value;  
}  

所以,我們?cè)谧宇愔兄貙戇@兩個(gè)方法一點(diǎn)問題也沒有,實(shí)際上,從他們的@Override標(biāo)簽中也可以看到,一點(diǎn)問題也沒有,實(shí)際上是這樣的嗎?

分析:

實(shí)際上,類型擦除后,父類的的泛型類型全部變?yōu)榱嗽碱愋蚈bject,所以父類編譯之后會(huì)變成下面的樣子:

class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}

再看子類的兩個(gè)重寫的方法的類型:

@Override  
public void setValue(Date value) {  
    super.setValue(value);  
}  
@Override  
public Date getValue() {  
    return super.getValue();  
}

先來分析setValue方法,父類的類型是Object,而子類的類型是Date,參數(shù)類型不一樣,這如果實(shí)在普通的繼承關(guān)系中,根本就不會(huì)是重寫,而是重載。
我們?cè)谝粋€(gè)main方法測(cè)試一下:

public static void main(String[] args) throws ClassNotFoundException {  
    DateInter dateInter=new DateInter();  
    dateInter.setValue(new Date());                  
    dateInter.setValue(new Object());//編譯錯(cuò)誤  
}  

如果是重載,那么子類中兩個(gè)setValue方法,一個(gè)是參數(shù)Object類型,一個(gè)是Date類型,可是我們發(fā)現(xiàn),根本就沒有這樣的一個(gè)子類繼承自父類的Object類型參數(shù)的方法。所以說,卻是是重寫了,而不是重載了。

為什么會(huì)這樣呢?

原因是這樣的,我們傳入父類的泛型類型是Date,Pair<Date>,我們的本意是將泛型類變?yōu)槿缦拢?/p>

class Pair {  
    private Date value;  
    public Date getValue() {  
        return value;  
    }  
    public void setValue(Date value) {  
        this.value = value;  
    }  
}  

然后再子類中重寫參數(shù)類型為Date的那兩個(gè)方法,實(shí)現(xiàn)繼承中的多態(tài)。
可是由于種種原因,虛擬機(jī)并不能將泛型類型變?yōu)镈ate,只能將類型擦除掉,變?yōu)樵碱愋蚈bject。這樣,我們的本意是進(jìn)行重寫,實(shí)現(xiàn)多態(tài)??墒穷愋筒脸螅荒茏?yōu)榱酥剌d。這樣,類型擦除就和多態(tài)有了沖突。JVM知道你的本意嗎?知道?。?!可是它能直接實(shí)現(xiàn)嗎,不能!?。∪绻娴牟荒艿脑?,那我們?cè)趺慈ブ貙懳覀兿胍腄ate類型參數(shù)的方法啊。

于是JVM采用了一個(gè)特殊的方法,來完成這項(xiàng)功能,那就是橋方法。

首先,我們用javap -c className的方式反編譯下DateInter子類的字節(jié)碼,結(jié)果如下:

class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {  
  com.tao.test.DateInter();  
    Code:  
       0: aload_0  
       1: invokespecial #8                  // Method com/tao/test/Pair."<init>"  
:()V  
       4: return  
  
  public void setValue(java.util.Date);  //我們重寫的setValue方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: invokespecial #16                 // Method com/tao/test/Pair.setValue  
:(Ljava/lang/Object;)V  
       5: return  
  
  public java.util.Date getValue();    //我們重寫的getValue方法  
    Code:  
       0: aload_0  
       1: invokespecial #23                 // Method com/tao/test/Pair.getValue  
:()Ljava/lang/Object;  
       4: checkcast     #26                 // class java/util/Date  
       7: areturn  
  
  public java.lang.Object getValue();     //編譯時(shí)由編譯器生成的巧方法  
    Code:  
       0: aload_0  
       1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去調(diào)用我們重寫的getValue方法  
;  
       4: areturn  
  
  public void setValue(java.lang.Object);   //編譯時(shí)由編譯器生成的巧方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: checkcast     #26                 // class java/util/Date  
       5: invokevirtual #30                 // Method setValue:(Ljava/util/Date;   去調(diào)用我們重寫的setValue方法  
)V  
       8: return  
}  

從編譯的結(jié)果來看,我們本意重寫setValue和getValue方法的子類,竟然有4個(gè)方法,其實(shí)不用驚奇,最后的兩個(gè)方法,就是編譯器自己生成的橋方法??梢钥吹綐蚍椒ǖ膮?shù)類型都是Object,也就是說,子類中真正覆蓋父類兩個(gè)方法的就是這兩個(gè)我們看不到的橋方法。而打在我們自己定義的setvalue和getValue方法上面的@Oveerride只不過是假象。而橋方法的內(nèi)部實(shí)現(xiàn),就只是去調(diào)用我們自己重寫的那兩個(gè)方法。
所以,虛擬機(jī)巧妙的使用了橋方法,來解決了類型擦除和多態(tài)的沖突。

泛型內(nèi)聯(lián)特化Reified [Kotlin獨(dú)有]

我們?cè)谥耙呀?jīng)了解了泛型的類型擦除,類型擦除大致會(huì)帶來一些問題,
比如Kotlin中常用的轉(zhuǎn)換操作符 as

fun <T> Any.asAny(): T? {
    return this as? T
}

上述代碼在進(jìn)行類型轉(zhuǎn)換時(shí),沒有進(jìn)行檢查,可能會(huì)出現(xiàn)因?yàn)轭愋筒灰恢露霈F(xiàn)的運(yùn)行時(shí)崩潰

例如下面的代碼

fun <T> Any.asAny(): T? {
    return this as? T
}

fun main() {
    val res = 1.asAny<String>()?.substring(1)
    println(res)
}

輸出結(jié)果

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
    at com.eegets.javademo.generic.ExtKt.main(Ext.kt:24)
    at com.eegets.javademo.generic.ExtKt.main(Ext.kt)

可以看到出現(xiàn)了ClassCastException異常,是因?yàn)槲覀儧]有進(jìn)行類型檢查,所以為了安全獲取數(shù)據(jù)一般需要顯示傳遞轉(zhuǎn)換結(jié)果的class信息

fun <T> Any.asAny(clazz: Class<T>): T? {
    return if (clazz.isInstance(this)) {
        this as? T
    } else {
        null
    }
}

fun main() {
    val res = 1.asAny<String>(String::class.java)?.substring(1)
    println(res)
}

輸出結(jié)果

null

這樣是能解決問題,但是需要傳遞class方式,這種方式比較笨重,尤其是參數(shù)過多時(shí)。

那有沒有可以排除這種傳遞參數(shù)之外更好的實(shí)現(xiàn)呢?

Reified內(nèi)聯(lián)特化關(guān)鍵字

好在Kotlin有更好的應(yīng)對(duì)方案,Java沒有,這就是Reified[具體化也可以叫特化]關(guān)鍵字

Reified使用非常簡單,主要分兩步(都是必須要加的):

  • 1:在泛型類型前面增加reified修飾符
  • 2:在方法前增加inline內(nèi)聯(lián)

我們可以改進(jìn)一下上述代碼

inline fun <reified T> Any.asAny(clazz: Class<T>): T? {
    return if (this is T) {
        this
    } else {
        null
    }
}

fun main() {
    val res = 1.asAny<String>(String::class.java)?.substring(1)
    println(res)
}

這時(shí)候輸出就正常了

public static final void main() {
      Integer $this$asAny$iv = 1;  //待轉(zhuǎn)換的值
      Class clazz$iv = String.class;
      int $i$f$asAny = false;
      String var10000 = (String)($this$asAny$iv instanceof String ? $this$asAny$iv : null);  //通過Java的instanceof對(duì)`$this$asAny$iv`常量驗(yàn)證是否是類型`String`
      if ((String)($this$asAny$iv instanceof String ? $this$asAny$iv : null) != null) { //使用內(nèi)聯(lián)進(jìn)行代碼替換,并通過instanceof進(jìn)行常量驗(yàn)證
         String var4 = var10000;
         byte var6 = 1;
         $i$f$asAny = false;
         if (var4 == null) {
            throw new TypeCastException("null cannot be cast to non-null type java.lang.String");
         }

         var10000 = var4.substring(var6);
         Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).substring(startIndex)");
      } else {
         var10000 = null;
      }

      String res = var10000;
      boolean var5 = false;
      System.out.println(res);
   }

如上生成的Java源碼可以看到,首先通過reified關(guān)鍵字會(huì)通過Java關(guān)鍵字instanceof驗(yàn)證常量$this$asAny$iv是否是類型String,然后再通過內(nèi)聯(lián)進(jìn)行代碼替換,另外也可以看出,通過inline內(nèi)聯(lián)函數(shù)修飾之后,制定的類型是不被擦除的,因?yàn)閕nline函數(shù)在編譯期會(huì)將字節(jié)碼copy到調(diào)用的方法里,所以編譯器在執(zhí)行此代碼時(shí)是知道具體的類型的,然后把泛型替換為具體類型,從而達(dá)到不擦除類型的目的。

[參考自泛型類相關(guān)文章]

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 說來慚愧,雖然平時(shí)經(jīng)常會(huì)使用到一些泛型類,但是卻一直沒有深入地去了解過泛型機(jī)制。今天開始學(xué)習(xí)記錄泛型機(jī)制相關(guān)的知識(shí)...
    怡紅快綠閱讀 637評(píng)論 0 1
  • 本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內(nèi)容請(qǐng)到我的倉庫里查看 https://g...
    程序員黃小斜閱讀 684評(píng)論 0 0
  • 概述 泛型的本質(zhì)是參數(shù)化類型,通常用于輸入?yún)?shù)、存儲(chǔ)類型不確定的場景。相比于直接使用 Object 的好處是:編譯...
    彳亍口巴閱讀 184評(píng)論 0 3
  • ArrayList就是個(gè)泛型類,我們通過設(shè)定不同的類型,可以往集合里面存儲(chǔ)不同類型的數(shù)據(jù)類型(而且只能存儲(chǔ)設(shè)定的數(shù)...
    dinel閱讀 527評(píng)論 0 2
  • [TOC] 深入理解 Java 泛型 概述 泛型的本質(zhì)是參數(shù)化類型,通常用于輸入?yún)?shù)、存儲(chǔ)類型不確定的場景。相比于...
    albon閱讀 5,803評(píng)論 0 7

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