柚子快報激活碼778899分享:
柚子快報激活碼778899分享:
} }
//編譯時生成的字節(jié)碼文件翻譯過來大致如下 class MyNode extends Node {
public MyNode(Integer data) { super(data); } // 編譯器生成的橋接方法 public void setData(Object data) { setData((Integer) data); }
public void setData(Integer data) { System.out.println(“MyNode.setData”); super.setData(data); } }
3、偽泛型
Java 中的泛型是一種特殊的語法糖,通過類型擦除實現(xiàn),這種泛型稱為偽泛型,我們可以反射繞過編譯器泛型檢查,添加一個不同類型的參數(shù)
//反射繞過編譯器檢查 public static void main(String[] args) {
List stringList = new ArrayList<>(); stringList.add(“erdai”); stringList.add(“666”);
//使用反射增加一個新的元素 Class extends List> aClass = stringList.getClass(); try { Method method = aClass.getMethod(“add”, Object.class); method.invoke(stringList,123); } catch (Exception e) { e.printStackTrace(); }
Iterator iterator = stringList.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } } //打印結果 erdai 666 123
4、泛型擦除進階
下面我拋出一個在工作中經(jīng)常會遇到的問題:
在進行網(wǎng)絡請求的時候,傳入一個泛型的實際類型,為啥能夠正確的獲取到該泛型類型,并利用 Gson 轉換為實際的對象?
答:是因為在運行期我們可以使用反射獲取到具體的泛型類型
What? 泛型不是在編譯的時候被擦除了嗎?為啥在運行時還能夠獲取到具體的泛型類型?樂?
答:泛型中所謂的類型擦除,其實只是擦除 Code 屬性中的泛型信息,在類常量池屬性(Signature 屬性、LocalVariableTypeTable 屬性)中其實還保留著泛型信息,而類常量池中的屬性可以被 class 文件,字段表,方法表等攜帶,這就使得我們聲明的泛型信息得以保留,這也是我們在運行時可以反射獲取泛型信息的根本依據(jù)
//這是反編譯后的 JavaGenericClass.class 文件,可以看到 T public class JavaGenericClass {
private T a;
public JavaGenericClass(T a) { this.a = a; }
public T getA() { return a; }
public void setA(T a) { this.a = a; }
//… }
注意:Java 是在 JDK 1.5 引入的泛型,為了彌補泛型擦除的不足,JVM 的 class 文件也做了相應的修改,其中最重要的就是新增了 Signature 屬性表和 LocalVariableTypeTable 屬性表
我們看下下面這段代碼:
class ParentGeneric {
}
class SubClass extends ParentGeneric{
}
class SubClass2 extends ParentGeneric {
}
public class GenericGet {
//獲取實際的泛型類型 public static Type findGenericType(Class cls) { Type genType = cls.getGenericSuperclass(); Type finalNeedType = null; if (genType instanceof ParameterizedType) { Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); finalNeedType = params[0]; } return finalNeedType; }
public static void main(String[] args) { SubClass subClass = new SubClass(); SubClass2 subClass2 = new SubClass2(); //打印 subClass 獲取的泛型 System.out.println("subClass: " + findNeedClass(subClass.getClass())); //打印subClass2獲取的泛型 System.out.println("subClass2: " + findGenericType(subClass2.getClass())); } }
//運行這段代碼 打印結果如下 subClass: class java.lang.String subClass2: T
上面代碼:
1、 SubClass 相當于對 ParentGeneric 做了賦值操作 T = String,我們通過反射獲取到了泛型類型為 String
2、SubClass2 對 ParentGeneric沒有做賦值操作 ,我們通過反射獲取到了泛型類型為 T
這里大家肯定會有很多疑問?
1、為啥 1 中沒有傳入任何泛型的信息卻能獲取到泛型類型呢?
2、為啥 2 中我創(chuàng)建對象的時候傳入的泛型是 Integer ,獲取的時候變成了 T 呢?
現(xiàn)在我們來仔細分析一波:
上面我講過,類型擦除其實只是擦除 Code 屬性中的泛型信息,在類常量池屬性中還保留著泛型信息,因此上面的 SubClass 和SubClass2 在編譯的時候其實會保留各自的泛型到字節(jié)碼文件中,一個是 String,一個是 T 。而 subClass 和 subClass2 是運行時動態(tài)創(chuàng)建的,這個時候你即使傳入了泛型類型,也會被擦除掉,因此才會出現(xiàn)上面的結果,到這里,大家是否明了了呢?
如果還有點模糊,我們再來看一個例子:
class ParentGeneric {
}
public class GenericGet { //獲取實際的泛型類型 public static Type findGenericType(Class cls) { Type genType = cls.getGenericSuperclass(); Type finalNeedType = null; if (genType instanceof ParameterizedType) { Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); finalNeedType = params[0]; } return finalNeedType; }
public static void main(String[] args) { ParentGeneric parentGeneric1 = new ParentGeneric(); ParentGeneric parentGeneric2 = new ParentGeneric(){};
//打印 parentGeneric1 獲取的泛型 System.out.println("parentGeneric1: " + findGenericType(parentGeneric1.getClass())); //打印 parentGeneric2 獲取的泛型 System.out.println("parentGeneric2: " + findGenericType(parentGeneric2.getClass()));
} } //運行這段代碼 打印結果如下 parentGeneric1: null parentGeneric2: class java.lang.String
上述代碼 parentGeneric1 和 parentGeneric2 唯一的區(qū)別就是多了 {},獲取的結果卻截然不同,我們在來仔細分析一波:
1、 ParentGeneric 聲明的泛型 T 在編譯的時候其實是保留在了字節(jié)碼文件中,parentGeneric1 是在運行時創(chuàng)建的,由于泛型擦除,我們無法通過反射獲取其中的類型,因此打印了 null
這個地方可能大家又會有個疑問了,你既然保留了泛型類型為 T,那么我獲取的時候應該為 T 才是,為啥打印的結果是 null 呢?
如果你心里有這個疑問,說明你思考的非常細致,要理解這個問題,我們首先要對 Java 類型(Type)系統(tǒng)有一定的了解,這其實和我上面寫的那個獲取泛型類型的方法有關:
//獲取實際的泛型類型 public static Type findGenericType(Class cls) { //獲取當前帶有泛型的父類 Type genType = cls.getGenericSuperclass(); Type finalNeedType = null; //如果當前 genType 是參數(shù)化類型則進入到條件體 if (genType instanceof ParameterizedType) { //獲取參數(shù)類型 <> 里面的那些值,例如 Map
上述代碼我們需要先獲取這個類的泛型父類,如果是參數(shù)化類型則進入到條件體,獲取實際的泛型類型并返回。如果不是則直接返回 finalNeedType , 那么這個時候就為 null 了
在例1中:
SubClass1 subClass1 = new SubClass1(); SubClass2 subClass2 = new SubClass2<>(); System.out.println(subClass1.getClass().getGenericSuperclass()); System.out.println(subClass2.getClass().getGenericSuperclass()); //運行程序 打印結果如下 com.dream.java_generic.share.ParentGeneric
可以看到獲取到了泛型父類,因此會走到條件體里面獲取到實際的泛型類型并返回
在例2中:
ParentGeneric parentGeneric1 = new ParentGeneric(); System.out.println(parentGeneric1.getClass().getGenericSuperclass()); //運行程序 打印結果如下 class java.lang.Object
可以看到獲取到的泛型父類是 Object,因此進不去條件體,所以就返回 null 了
2、parentGeneric2 在創(chuàng)建的時候后面加了 {},這就使得 parentGeneric2 成為了一個匿名內(nèi)部類,且父類就是 ParentGeneric,因為匿名內(nèi)部類是在編譯時創(chuàng)建的,那么在編譯的時候就會創(chuàng)建并攜帶具體的泛型信息,因此 parentGeneric2 可以獲取其中的泛型類型
通過上面兩個例子我們可以得出結論:如果在編譯的時候就保存了泛型類型到字節(jié)碼中,那么在運行時我們就可以通過反射獲取到,如果在運行時傳入實際的泛型類型,這個時候就會被擦除,反射獲取不到當前傳入的泛型實際類型
例子1中我們指定了泛型的實際類型為 String,編譯的時候就將它存儲到了字節(jié)碼文件中,因此我們獲取到了泛型類型。例子2中我們創(chuàng)建了一個匿名內(nèi)部類,同樣在編譯的時候會進行創(chuàng)建并保存了實際的泛型到字節(jié)碼中,因此我們可以獲取到。而 parentGeneric1 是在運行時創(chuàng)建的,雖然 ParentGeneric 聲明的泛型 T 在編譯時也保留在了字節(jié)碼文件中,但是它傳入的實際類型被擦除了,這種泛型也是無法通過反射獲取的,記住上面這條結論,那么對于泛型類型的獲取你就得心應手了
5、泛型獲取經(jīng)驗總結
其實通過上面兩個例子可以發(fā)現(xiàn),當我們定義一個子類繼承一個泛型父類,并給這個泛型一個類型,我們就可以獲取到這個泛型類型
//定義一個子類繼承泛型父類,并給這個泛型一個實際的類型 class SubClass extends ParentGeneric{
}
//匿名內(nèi)部類,其實我們定義的這個匿名內(nèi)部類也是一個子類,它繼承了泛型父類,并給這個泛型一個實際的類型 ParentGeneric parentGeneric2 = new ParentGeneric(){};
因此如果我們想要獲取某個泛型類型,我們可以通過子類的幫助去取出該泛型類型,一種良好的編程實踐就是把當前需要獲取的泛型類用 abstract 聲明
3、邊界
邊界就是在泛型的參數(shù)上設置限制條件,這樣可以強制泛型可以使用的類型,更重要的是可以按照自己的邊界類型來調(diào)用方法
1)、Java 中設置邊界使用 extends 關鍵字,完整語法結構:
2)、可以設置多個邊界,中間使用 & 連接,多個邊界中只能有一個邊界是類,且類必須放在最前面,類似這種語法結構
下面我們來演示一下:
abstract class ClassBound{ public abstract void test1(); }
interface InterfaceBound1{ void test2(); }
interface InterfaceBound2{ void test3(); }
class ParentClass
public ParentClass(T item) { this.item = item; }
public void test1(){ item.test1(); }
public void test2(){ item.test2(); }
public void test3(){ item.test3(); } }
class SubClass extends ClassBound implements InterfaceBound1,InterfaceBound2 {
@Override public void test1() { System.out.println(“test1”); }
@Override public void test2() { System.out.println(“test2”); }
@Override public void test3() { System.out.println(“test3”); } }
public class Bound { public static void main(String[] args) { SubClass subClass = new SubClass(); ParentClass parentClass = new ParentClass(subClass); parentClass.test1(); parentClass.test2(); parentClass.test3(); } } //打印結果 test1 test2 test3
4、通配符
1、泛型的協(xié)變,逆變和不變
思考一個問題,代碼如下:
Number number = new Integer(666); ArrayList numberList = new ArrayList();//編譯器報錯 type mismatch
上述代碼,為啥 Number 的對象可以由 Integer 實例化,而 ArrayList
要明白上面這個問題,我們首先要明白,什么是泛型的協(xié)變,逆變和不變
1)、泛型協(xié)變,假設我定義了一個 Class
2)、泛型逆變,假設我定義了一個 Class
3)、泛型不變,假設我定義了一個 Class
因此我們可以知道 ArrayList
Number number = new Integer(666); ArrayList extends Number> numberList = new ArrayList();
2、泛型的上邊界通配符
1)、泛型的上邊界通配符語法結構: extends Bound>,使得泛型支持協(xié)變,它限定的類型是當前上邊界類或者其子類,如果是接口的話就是當前上邊界接口或者實現(xiàn)類,使用上邊界通配符的變量只讀,不可以寫,可以添加 null ,但是沒意義
public class WildCard { public static void main(String[] args) { List integerList = new ArrayList(); List numberList = new ArrayList(); integerList.add(666); numberList.add(123);
getNumberData(integerList); getNumberData(numberList); }
public static void getNumberData(List extends Number> data) { System.out.println(“Number data :” + data.get(0)); } } //打印結果 Number data: 666 Number data: 123
問題:為啥使用上邊界通配符的變量只讀,而不能寫?
1、 extends Bound>,它限定的類型是當前上邊界類或者其子類,它無法確定自己具體的類型,因此編譯器無法驗證類型的安全,所以不能寫
2、假設可以寫,我們向它里面添加若干個子類,然后用一個具體的子類去接收,勢必會造成類型轉換異常
3、泛型的下邊界通配符
1)、泛型的下邊界通配符語法結構: super Bound>,使得泛型支持逆變,它限定的類型是當前下邊界類或者其父類,如果是接口的話就是當前下邊界接口或者其父接口,使用下邊界通配符的變量只寫,不建議讀
public class WildCard {
public static void main(String[] args) { List numberList = new ArrayList(); List objectList = new ArrayList(); setNumberData(numberList); setNumberData(objectList); }
public static void setNumberData(List super Number> data) { Number number = new Integer(666); data.add(number); } }
問題:為啥使用下邊界通配符的變量可以寫,而不建議讀?
1、 super Bound>,它限定的類型是當前下邊界類或者其父類,雖然它也無法確定自己具體的類型,但根據(jù)多態(tài),它能保證自己添加的元素是安全的,因此可以寫
2、獲取值的時候,會返回一個 Object 類型的值,而不能獲取實際類型參數(shù)代表的類型,因此建議不要去讀,如果你實在要去讀也行,但是要注意類型轉換異常
4、泛型的無邊界通配符
1)、無邊界通配符的語法結構:>,實際上它等價于 extends Object>,也就是說它的上邊界是 Object 或其子類,因此使用無界通配符的變量同樣只讀,不能寫,可以添加 null ,但是沒意義
public class WildCard {
public static void main(String[] args) { List stringList = new ArrayList(); List numberList = new ArrayList(); List integerList = new ArrayList(); stringList.add(“erdai”); numberList.add(666); integerList.add(123); getData(stringList); getData(numberList); getData(integerList); }
public static void getData(List> data) { System.out.println("data: " + data.get(0)); } } //打印結果 data: erdai data: 666 data: 123
5、PECS 原則
泛型代碼的設計,應遵循PECS原則(Producer extends Consumer super):
1)、如果只需要獲取元素,使用 extends T>
2)、如果只需要存儲,使用 super T>
//這是 Collections.java 中 copy 方法的源碼 public static void copy(List super T> dest, List extends T> src) { //… }
這是一個很經(jīng)典的例子,src 表示原始集合,使用了 extends T>,只能從中讀取元素,dest 表示目標集合,只能往里面寫元素,充分的體現(xiàn)了 PECS 原則
6、使用通配符總結
1)、當你只想讀取值的時候,使用 extends T>
2)、當你只想寫入值的時候,使用 super T>
3)、當你既想讀取值又想寫入值的時候,就不要使用通配符
5、泛型的限制
1)、泛型不能顯式地引用在運行時類型的操作里,如 instanceof 操作和 new 表達式,運行時類型只適用于原生類型
public class GenericLimitedClass { private void test(){ String str = “”; //編譯器不允許這種操作 if(str instanceof T){
} //編譯器不允許這種操作 T t = new T(); } }
2)、不能創(chuàng)建泛型類型的數(shù)組,只可以聲明一個泛型類型的數(shù)組引用
public class GenericLimitedClass { private void test(){ GenericLimitedClass[] genericLimitedClasses; //編譯器不允許 genericLimitedClasses = new GenericLimitedClass[10]; } }
3)、不能聲明類型為泛型的靜態(tài)字段
public class GenericLimitedClass { //編譯器不允許 private static T t; }
4)、泛型類不可以直接或間接地繼承 Throwable
//編譯器不允許 public class GenericLimitedClass extends Throwable {
}
5)、方法中不可以捕獲類型參數(shù)的實例,但是可以在 throws 語句中使用類型參數(shù)
public class GenericLimitedClass { private void test1() throws T{ try {
//編譯器不允許 }catch (T exception){
} } }
6)、一個類不可以重載在類型擦除后有同樣方法簽名的方法
public class GenericLimitedClass { //編譯器不允許 private void test2(List stringList){
}
private void test2(List integerList){
} }
6、問題
1)、類型邊界和通配符邊界有什么區(qū)別?
類型邊界可以有多個,通配符邊界只能有一個
2)、List> 和 List
不一樣
1、 List
2、List>可以有很多子類,但是 List
二、Kotlin 泛型
Kotlin 泛型和 Java 泛型基本上是一樣的,只不過在 Kotlin 上有些東西換了新的寫法
1、泛型的基本用法
1)、在 Kotlin 中我們定義和使用泛型的方式如下:
//1、定義一個泛型類,在類名后面使用 這種語法結構就是為這個類定義一個泛型 class MyClass{ fun method(params: T) {
} } //泛型調(diào)用 val myClass = MyClass() myClass.method(12)
//2、定義一個泛型方法,在方法名的前面加上 這種語法結構就是為這個方法定義一個泛型 class MyClass{ fun method(params: T){
} } //泛型調(diào)用 val myClass = MyClass() myClass.method(12) //根據(jù) Kotlin 類型推導機制,我們可以把泛型給省略 myClass.method(12)
//3、定義一個泛型接口,在接口名后面加上 這種語法結構就是為這個接口定義一個泛型 interface MyInterface{ fun interfaceMethod(params: T) }
對比 Java 中定義泛型,我們可以發(fā)現(xiàn):在定義類和接口泛型上沒有任何區(qū)別,在定義方法泛型時,Kotlin 是在方法名前面添加泛型,而 Java 是在返回值前面添加泛型
2、邊界
1)、為泛型指定邊界,我們可以使用
2)、如果有多個邊界,可以使用 where 關鍵字,中間使用 : 隔開,多個邊界中只能有一個邊界是類,且類必須放在最前面
//情況1 單個邊界 class MyClass1 {
var data: T? = null
fun method(params: T) {
} }
//情況2 多個邊界使用 where 關鍵字 open class Animal interface Food interface Food2
class MyClass2 where T : Animal, T : Food, T : Food2 {
fun method(params: T) where T : Animal, T : Food, T : Food2 {
} }
3、泛型實化
泛型實化在 Java 中是不存在的,Kotlin 中之所以能實現(xiàn)泛型實化,是因為使用的內(nèi)聯(lián)函數(shù)會對代碼進行替換,那么在內(nèi)聯(lián)函數(shù)中使用泛型,最終也會使用實際的類型進行替換
1)、使用內(nèi)聯(lián)函數(shù)配合 reified 關鍵字對泛型進行實化,語法結構如下:
inline fun getGenericType() {
}
實操一下:
inline fun getGenericType() = T::class.java
fun main() { //泛型實化 這種情況在 Java 是會被類型擦除的 val result1 = getGenericType() val result2 = getGenericType() println(result1) println(result2) } //打印結果 class java.lang.String class java.lang.Number
2)、實際應用
在我們跳轉 Activity 的時候通常會這么操作
val intent = Intent(mContext,TestActivity::class.java) mContext.startActivity(intent)
有沒有感覺寫這種 TestActivity::class.java 的語法很難受,反正我是覺得很難受,那么這個時候我們就可以使用泛型實化換一種寫法:
//定義一個頂層函數(shù) inline fun startActivity(mContext: Context){ val intent = Intent(mContext,T::class.java) mContext.startActivity(intent) }
//使用的時候 startActivity(mContext)
這種寫法是不是清爽了很多,那么在我們跳轉 Activity 的時候,可能會攜帶一些參數(shù),如下:
val intent = Intent(mContext,TestActivity::class.java) intent.putExtra(“params1”,“erdai”) intent.putExtra(“params2”,“666”) mContext.startActivity(intent)
這個時候我們可以增加一個函數(shù)類型的參數(shù),使用 Lambda 表達式去調(diào)用,如下:
inline fun startActivity(mContext: Context, block: Intent.() -> Unit){ val intent = Intent(mContext,T::class.java) intent.block() mContext.startActivity(intent) }
//使用的時候 startActivity(mContext){ putExtra(“params1”,“erdai”) putExtra(“params2”,“666”) }
4、泛型協(xié)變,逆變和不變
最后
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進入阿里一直到現(xiàn)在。
深知大多數(shù)初中級Android工程師,想要提升技能,往往是自己摸索成長,自己不成體系的自學效果低效漫長且無助。
因此我收集整理了一份《2024年Android移動開發(fā)全套學習資料》,初衷也很簡單,就是希望能夠幫助到想自學提升又不知道該從何學起的朋友,同時減輕大家的負擔。
既有適合小白學習的零基礎資料,也有適合3年以上經(jīng)驗的小伙伴深入學習提升的進階課程,基本涵蓋了95%以上Android開發(fā)知識點!不論你是剛入門Android開發(fā)的新手,還是希望在技術上不斷提升的資深開發(fā)者,這些資料都將為你打開新的學習之門
如果你覺得這些內(nèi)容對你有幫助,需要這份全套學習資料的朋友可以戳我獲?。?!
由于文件比較大,這里只是將部分目錄截圖出來,每個節(jié)點里面都包含大廠面經(jīng)、學習筆記、源碼講義、實戰(zhàn)項目、講解視頻,并且會持續(xù)更新!
putExtra(“params2”,“666”) }
4、泛型協(xié)變,逆變和不變
最后
自我介紹一下,小編13年上海交大畢業(yè),曾經(jīng)在小公司待過,也去過華為、OPPO等大廠,18年進入阿里一直到現(xiàn)在。
深知大多數(shù)初中級Android工程師,想要提升技能,往往是自己摸索成長,自己不成體系的自學效果低效漫長且無助。
因此我收集整理了一份《2024年Android移動開發(fā)全套學習資料》,初衷也很簡單,就是希望能夠幫助到想自學提升又不知道該從何學起的朋友,同時減輕大家的負擔。
[外鏈圖片轉存中…(img-dbFTaLyT-1715792113182)]
[外鏈圖片轉存中…(img-JDdik24g-1715792113184)]
[外鏈圖片轉存中…(img-I552yHSE-1715792113185)]
[外鏈圖片轉存中…(img-Z1l5Wq4S-1715792113186)]
既有適合小白學習的零基礎資料,也有適合3年以上經(jīng)驗的小伙伴深入學習提升的進階課程,基本涵蓋了95%以上Android開發(fā)知識點!不論你是剛入門Android開發(fā)的新手,還是希望在技術上不斷提升的資深開發(fā)者,這些資料都將為你打開新的學習之門
如果你覺得這些內(nèi)容對你有幫助,需要這份全套學習資料的朋友可以戳我獲?。?!
由于文件比較大,這里只是將部分目錄截圖出來,每個節(jié)點里面都包含大廠面經(jīng)、學習筆記、源碼講義、實戰(zhàn)項目、講解視頻,并且會持續(xù)更新!
柚子快報激活碼778899分享:
相關文章
本文內(nèi)容根據(jù)網(wǎng)絡資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉載請注明,如有侵權,聯(lián)系刪除。