一直沒注意這方面的內容,想來這也算是基礎了,就寫了這個筆記。java
首先java的通配符共有三種————先別緊張,如今只是粗略的過一下,看不看其實無所謂程序員
類型 | 介紹 |
---|---|
<?> | 無限定通配符,等價於 <? extends Object> |
<? extends Number> | 上限通配符,表示參數類型只能是 Number 或是 Number 的子類。 |
<? super Number> | 下限通配符,表示參數類型只能是 Number 或是 Number 的父類。 |
而後再讓咱們定義四個類,下面會用到數組
class A { public String getName() { return "A"; } } class B extends A{ @Override public String getName() { return "B"; } } class BAge extends B{ @Override public String getName() { return "C"; } public int getAge() { return 100; } } class BSize extends B{ @Override public String getName() { return "D"; } public int getSize() { return -1; } }
class PrintAges{ public static void print(BAge[] ages){ if (ages == null) return; for (BAge bage : ages){ if (bage != null) System.out.println(bage.getAge()); } } }
仔細看看上面這個類,你以爲我寫的 PrintAges
怎樣?夠完美嗎,不會引起異常吧?我以爲也很完美了,確定不會有異常出如今個人代碼裏了。ide
BAge[] temps = new BAge[]{new BAge(), new BAge()}; PrintAges.print(temps);
輸出:函數
100 100
完美運行。測試
BAge[] temps = new BAge[]{new BAge(), new BAge()}; B[] barray = temps; // 新增長的第一行 barray[0] = new BSize(); // 新增長的第二行 PrintAges.print(temps);
你猜怎麼着?我偷偷地改變了數組中的元素!我在 BAge 類型的數組中的元素賦了一個 BSize 的對象!
並且,編譯經過了。可是確定會有異常出現,你猜是在哪一行?code
輸出:對象
Exception in thread "main" java.lang.ArrayStoreException: JavaApp.BSize at JavaApp.JavaApplicationStudyGen.main(JavaApplicationStudyGen.java:33)
原本我覺得會在 PrintAges 的 print 方法中發生異常,可是實際上新增長的第二行發生了運行時錯誤,賦值錯誤。遊戲
而在C#中,這種問題出現的可能性就更小了。C#中,新增的第一行是沒法經過編譯的。
那麼,這種問題在集合……準確地說是在泛型裏會不會出現呢?get
咱們先對 PrintAges
添加一個 print 函數的重載
class PrintAges{ public static void print(ArrayList<BAge> list) { if (list == null) return; for(BAge age : list) System.out.println(age.getAge()); } public static void print(BAge[] ages){ if (ages == null) return; for (BAge bage : ages){ if (bage != null) System.out.println(bage.getAge()); } } }
而後咱們對用再次運行以下代碼:
ArrayList<BAge> list = new ArrayList<BAge>(); list.add(new BAge()); ArrayList<B> yourList = list; // 編譯錯誤 yourList.set(0, new BSize()); // star 1 BAge age = list.get(0); // star 2 PrintAges.print(list);
此次,Java 處理的比較嚴格,在把 ArrayList<BAge>
賦值給 ArrayList<B>
類型的對象時產生了編譯錯誤。
在 C# 裏,也是同樣的,在把 ArrayList<BAge> 賦值給 ArrayList<B> 類型的對象時會產生編譯錯誤。
一開始,我不理解這樣作對 list 引用的對象 ArrayList
可是,不能賦值的緣由,把一個 BSize 類型的對象放在了一個其實是 ArrayList
通過類型擦除以後, star 2
所在行的代碼就會變成
BAge age = (BSize)list.get(0); // star 2
這樣就是徹底不正確的了。
也就是說,咱們應該禁止相似 ArrayList<B> yourList = new ArrayList<BAge>()
這樣的賦值,不然,就會出現這樣的錯誤和意外。
說實話,B[] barray = new BAge[]{new BAge(), new BAge()}
這樣的賦值操做也該被禁止的,可是 Java 就能夠。看看人家 C# 就不容許這樣作(笑)
記住這樣的錯誤。接下來,咱們就能夠討論 Java 的泛型通配符了。
因此所,通配符的出現就是爲了在錯誤避免上述錯誤的同時,給程序員提供一點便利。
而通配符是怎麼樣發生做用的呢?是經過編譯器給定的三條「遊戲規則」(也便是上面給的表格裏的規則)發生做用的。
在一開始理解的時候是須要一點邏輯能力的:
<? extends B>
確保了可讀性, <? extends B> 表示參數類型只能是 B 或是 B 的子類
能夠被編譯經過的語句:ArrayList<? extends B> list = new ArrayList<A>(); // 編譯錯誤 ArrayList<? extends B> list = new ArrayList<B>(); // ok ArrayList<? extends B> list = new ArrayList<BAge>(); // ok ArrayList<? extends B> list = new ArrayList<BSize>(); // ok
基於以上的編譯規則,咱們能夠得出如下事實:
ArrayList<B>
,要麼指向包含 B 子類對象的 ArrayList<B>
ArrayList<BSize>
或者指向 ArrayList<BAge>
ArrayList<BSize>
ArrayList<BAge>
注意,上述代碼中, list 中的 T
被替換成了 ? extends B
也就是說,讀取操做能夠被確保,你必定能從 list 中讀取到一個 B 元素 這樣, list.get
方法就能夠被正常使用了。
而 list.set(int, T)
就被替換成了 list.set(int, ? extends B)
,這個方法就被編譯器「禁止」了。也就是說,若是你寫出 list.set(0, new B())
或 list.set(0, new BSize())
是不行的。
在這裏你確定要提出疑問了,你不是說符合「遊戲規則」 <? extends B> 表示參數類型只能是 B 或是 B 的子類
就行的嗎? 我只能說,文字所能傳達的信息是有限的,這個表述也只適用於 ArrayList<? extends B> list = new ArrayList<A>();
這樣的賦值時刻。仍是得看上述推導的「事實」
<? super B>
確保了寫入性ArrayList<? super B> list = new ArrayList<Object>(); // ok ArrayList<? super B> list = new ArrayList<A>(); // ok ArrayList<? super B> list = new ArrayList<B>(); // ok ArrayList<? super B> list = new ArrayList<BAge>(); // 編譯錯誤 ArrayList<? super B> list = new ArrayList<BSize>(); // 編譯錯誤
基於以上的編譯規則,咱們能夠得出如下事實:
ArrayList
,要麼指向包含 B 超類型的 ArrayList 對象,好比: list 多是 ArrayList<Object>
或 ArrayList<A>
。ArrayList<Object>
或者是 ArrayList<B>
這樣, list.set
方法就能夠被正常使用了。假設 list
指向 ArrayList<Object>
,咱們把一個 B 類型的對象添加到 ArrayList<Object>
中也沒錯啊。
BAge
對象添加到 ArrayList<Object>
或 ArrayList<A>
中也沒錯啊。BSize
對象添加到 ArrayList<Object>
或 ArrayList<A>
中也沒錯啊。? extends B
確保了可讀性,? super B
確保了寫入性。? extends B
和 ? super B
給人的感受是逆操做。