摘要:泛型方法可以定義在普通類和泛型類中,泛型方法可以被修飾符修飾。泛型類型變量會在編譯后被擦除,用第一個限定類型替換沒有限定類型的用替換。比如通配符類型代表任何泛型類型的類型變量是和的子類。這個類型參數有一個子類型限定,其自身又是一個泛型類型。
我的博客 轉載請注明原創出處。序
之所以會想來寫泛型相關的內容,是因為看到這樣的一段代碼:
當時我的內心是這樣的:
所以就趕緊去復習了下,記錄下來。基礎不扎實,源碼看不懂啊。
泛型介紹Java 泛型(generics)是 JDK 5 中引入的一個新特性,泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數,在Java集合框架里使用的非常廣泛。
定義的重點是提供了編譯時類型安全檢測機制。比如有這樣的一個泛型類:
public class Generics{ private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
然后寫這樣一個類:
public class Generics { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
它們同樣都能存儲所有值,但是泛型類有編譯時類型安全檢測機制:
泛型類一個類定義了一個或多個類型變量,那么就是泛型類。語法是在類名后用尖括號括起來,類型變量寫在里面用逗號分開。然后就可以在方法的返回類型、參數和域、局部變量中使用類型變量了,但是不能在有static修飾符修飾的方法或域中使用。例子:
類定義參考上文例子 使用形式 Generics泛型方法generics = new Generics (); 后面尖括號內的內容在Jdk7以后可以省略 Generics generics = new Generics<>();
一個方法定義了一個或多個類型變量,那么就是泛型方法。語法是在方法修飾符后面、返回類型前面用尖括號括起來,類型變量寫在里面用逗號分開。泛型方法可以定義在普通類和泛型類中,泛型方法可以被static修飾符修飾。
例子:
private void out(U u) { System.out.println(u); } 調用形式, Test.類型變量的限定out("test"); 大部分情況下 都可以省略,編譯器可以推斷出來類型 Test.out("test");
有時候我們會有希望限定類型變量的情況,比如限定指定的類型變量需要實現List接口,這樣我們就可以在代碼對類型變量調用List接口里的方法,而不用擔心會沒有這個方法。
public class Generics{ private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } public void add(Object u) { value.add(u); } public static void main(String[] args) { Generics generics = new Generics<>(); generics.setValue(new ArrayList<>()); generics.add("ss"); System.out.println(generics.getValue()); } }
限定的語法是在類型變量的后面加extends關鍵字,然后加限定的類型,多個限定的類型要用&分隔。類型變量和限定的類型可以是類也可以是接口,因為Java中類只能繼承一個類,所以限定的類型是類的話一定要在限定列表的第一個。
類型擦除類型擦除是為了兼容而搞出來的,大意就是在虛擬機里是沒有泛型類型,泛型只存在于編譯期間。泛型類型變量會在編譯后被擦除,用第一個限定類型替換(沒有限定類型的用Object替換)。上文中的Generics
public class Generics { private Object value; public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } }
之所以我們能設置和返回正確的類型是因為編譯器自動插入了類型轉換的指令。
public static void main(String[] args) { Genericsgenerics = new Generics<>(); generics.setValue("ss"); System.out.println(generics.getValue()); } javac Generics.java javap -c Generics 編譯后的代碼 public static void main(java.lang.String[]); Code: 0: new #3 // class generics/Generics 3: dup 4: invokespecial #4 // Method " ":()V 7: astore_1 8: aload_1 9: ldc #5 // String ss 11: invokevirtual #6 // Method setValue:(Ljava/lang/Object;)V 14: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 17: aload_1 18: invokevirtual #8 // Method getValue:()Ljava/lang/Object; 21: checkcast #9 // class java/lang/String 24: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 27: return
我們可以看到在21行插入了一條類型轉換的指令。
類型擦除還帶來了另一個問題,如果我們有一個類繼承了泛型類并重寫了父類的方法:
public class SubGenerics extends Generics{ @Override public void setValue(String value) { System.out.println(value); } public static void main(String[] args) { Generics generics = new SubGenerics(); generics.setValue("ss"); } }
因為類型擦除所以SubGenerics實際上有兩個setValue方法,SubGenerics自己的setValue(String value)方法和從Generics繼承來的setValue(Object value)方法。例子中的generics引用的是SubGenerics對象,所以我們希望調用的是SubGenerics.setValue。為了保證正確的多態性,編譯器在SubGenerics類中生成了一個橋方法:
public void setValue(Object value) { setValue((String) value); }
我們可以編譯驗證下:
Compiled from "SubGenerics.java" public class generics.SubGenerics extends generics.Generics{ public generics.SubGenerics(); Code: 0: aload_0 1: invokespecial #1 // Method generics/Generics." ":()V 4: return public void setValue(java.lang.String); Code: 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: aload_1 4: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 7: return public static void main(java.lang.String[]); Code: 0: new #4 // class generics/SubGenerics 3: dup 4: invokespecial #5 // Method " ":()V 7: astore_1 8: aload_1 9: ldc #6 // String ss 11: invokevirtual #7 // Method generics/Generics.setValue:(Ljava/lang/Object;)V 14: return public void setValue(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast #8 // class java/lang/String 5: invokevirtual #9 // Method setValue:(Ljava/lang/String;)V 8: return }
引用《Java核心技術 卷一》約束與局限性總之,需要記住有關 Java 泛型轉換的事實:
1.虛擬機中沒有泛型,只有普通的類和方法。
2.所有的類型參數都用它們的限定類型替換。
3.橋方法被合成來保持多態。
4.為保持類型安全性,必要時插人強制類型轉換。
類型變量不能是基本變量,比如int,double等。應該使用它們的包裝類Integer,Double。
虛擬機中沒有泛型,所以不能使用運行時的類型查詢,比如 if (generics instanceof Generics
因為類型擦除的原因,不能創建泛型類數組,比如Generics
不能實例化類型變量,比如new T(...) new T[...] 或 T.class
通配符類型通配符類型和上文中的類型變量的限定有些類似,區別是通配符類型是運用在聲明的時候而類型變量的限定是在定義的時候。比如通配符類型Generics extends List>代表任何泛型Generics類型的類型變量是List和List的子類。
Generics extends List> generics = new Generics
不過這樣聲明之后Generics的方法也發生了變化,變成了
這樣就導致了不能調用setValue方法
而getValue方法是正常的
超類型限定通配符限定還可以限定超類,比如通配符類型Generics super ArrayList>代表任何泛型Generics類型的類型變量是ArrayList和ArrayList的超類。
Generics super ArrayList> generics = new Generics();
同樣的,Generics的方法也發生了變化,變成了
調用getValue方法只能賦值給Object變量
調用setValue方法只能傳入ArrayList和ArrayList的子類,超類List,Object等都不行
反射和泛型雖然因為類型擦除,在虛擬機里是沒有泛型的。不過被擦除的類還是保留了一些關于泛型的信息,可以使用反射相關的Api來獲取。
類似地,看一下泛型方法
public static
這是擦除后
public static Comparable min(Coniparable[] a)
可以使用反射 API 來確定:
這個泛型方法有一個叫做T的類型參數。
這個類型參數有一個子類型限定, 其自身又是一個泛型類型。
這個限定類型有一個通配符參數。
這個通配符參數有一個超類型限定。
這個泛型方法有一個泛型數組參數。
后記周一就建好的草稿,到了星期天才寫好,還是刪掉了一些小節情況下,怕是拖延癥晚期了......不過也是因為泛型的內容夠多,雖然日常業務里很少自己去寫泛型相關的代碼,但是在閱讀類庫源碼時要是不懂泛型就寸步難行了,特別是集合相關的。這次的大部分內容都是《Java核心技術 卷一》里的,這可是本關于Java基礎的好書。不過還是老規矩,光讀可不行,還是要用自己的語言記錄下來。眾所周知,人類的本質是復讀機,把好書里的內容重復一遍,就等于我也有責任了!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/71738.html
摘要:哪吒社區技能樹打卡打卡貼函數式接口簡介領域優質創作者哪吒公眾號作者架構師奮斗者掃描主頁左側二維碼,加入群聊,一起學習一起進步歡迎點贊收藏留言前情提要無意間聽到領導們的談話,現在公司的現狀是碼農太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區Java技能樹打卡?【打卡貼 day2...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
摘要:基礎問題的的性能及原理之區別詳解備忘筆記深入理解流水線抽象關鍵字修飾符知識點總結必看篇中的關鍵字解析回調機制解讀抽象類與三大特征時間和時間戳的相互轉換為什么要使用內部類對象鎖和類鎖的區別,,優缺點及比較提高篇八詳解內部類單例模式和 Java基礎問題 String的+的性能及原理 java之yield(),sleep(),wait()區別詳解-備忘筆記 深入理解Java Stream流水...
閱讀 2296·2021-11-24 10:18
閱讀 2730·2021-11-19 09:59
閱讀 1718·2019-08-30 15:53
閱讀 1196·2019-08-30 15:53
閱讀 1078·2019-08-30 14:19
閱讀 2489·2019-08-30 13:14
閱讀 3024·2019-08-30 13:00
閱讀 1957·2019-08-30 11:11