泛型產(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。
類型擦除引起的問題和解決辦法
因?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á)到不擦除類型的目的。