說明:
今天,繼續學姐java編程思想-泛型這一章的時候,對於通配符的理解不夠明白,因而又網上查了些資料,發現一篇寫的很好的文章,在此記錄學習一下.php
原做者傳送門java
首先本文假定讀者對Java的泛型有基礎的瞭解,若須要請參考其餘資料配合閱讀。設計模式
「當java有了泛態和多態重寫兩個特性。兩件快樂的事情重合在一塊兒。而這兩份快樂,又能帶來更多的自由。獲得的,本該是像夢境通常的自由設計模式……可是,爲何呢,爲何會變成這樣呢……」 -白·雨軒數組
泛型做爲Java1.5添加到Java語言中的一個重要特性,起着提升類型安全、增長程序可讀性等等做用。可是泛型在Java中的實現方式和某些行爲卻飽受詬病,最多見的說辭莫過於Java的泛型是僞泛型,會被擦除了。但其實之因此會拿這個說法來給泛型差評,其實很大程度上只是由於這是最容易理解的問題罷了,因此經常被一些只知其一;不知其二的人拿來講事。Java泛型不盡人意的地方遠不止這一個問題,但其餘的不盡人意的地方大多都由於其自己的複雜性被大多數人忽略了。由於大多數人寧願只使用簡單的泛型特性也不肯去研究如何作到最大化的類型安全。甚至只會使用Java的API中的泛型類,根本不清楚、沒嘗試如何去正確實現一個本身的泛型類。安全
因此今天咱們來聊聊其中一個不盡人意的地方:泛型函數重寫。markdown
重寫做爲多態的一個重要特性。由於Java中全部對象方法在被JIT優化前都會被當成虛函數,因此重寫起着相當重要的用途。使用多態的動態單分派和虛函數表來實現虛函數的特性。重寫自己在Java中的實現是十分優雅的,要是沒記錯的話應該是在Java1.5的時候還增長了返回值類型協變的特性,使得在使用特化的泛型類子類的時候不須要強轉返回值了。ide
可是,看成爲多態的招牌:「重寫」,趕上做爲類型安全的招牌:「泛型」的時候有些事情就不那麼快樂了。原本兩件快樂的事情重合在一塊兒,會獲得更多的快樂……可是,爲何呢,爲何會變成這樣呢……(白學家打死www)。wordpress
其實問題歸根結底仍是由於泛型和多態繞在一塊兒後造成的複雜度。咱們先來考慮一下,什麼樣的重寫纔是正確且不會出現類型問題的:函數
1.函數標識符應該徹底。
1.函數參數應該是徹底相同的,不能容許任何的協變或逆變。
2.函數返回值應該兼容。這意味着返回值能夠容許協變。
當符合以上3條時,咱們就能夠說重寫是正確的。那麼,若是咱們說泛型和重載 在!一!起! 在!一!起!(某MV中毒www)的時候會有啥問題,那麼也只能出如今返回值上。好,那咱們就把重點放在返回值上!
首先咱們先要明白,什麼叫協變。在沒有泛型以前,協變指的是從爲子類隱式轉換父類,這很好理解。在有了泛型後,就有點不一樣了。首先泛型自己是不支持協變的,除非你使用通配符(從數組吸收的教訓)。當你使用通配符後,泛型就是能夠協變的了,這時候就由要轉換的類型是否和通配符兼容來決定。
那麼也就是說,若是咱們嘗試重寫一個返回值包含有TypeVariable的函數,那麼要麼返回值如出一轍、要麼協變到擁有一樣父類泛型參數的子類、要麼把其中的泛型參數協變爲其通配符兼容的泛型參數、要麼類型自己和泛型參數一塊兒協變。
如今咱們來看看這段代碼:
interface I1 { } interface I2 extends I1 { } interface I3 extends I2 { } class A { <T extends I1> List<T> foo() { return null; } } class B extends A { @Override <T extends I2> List<T> foo() { return null; } }
這段代碼的意圖在於想讓B的foo函數返回一個List
List<? extends I1> foo() { return null; } } class B extends A { @Override List<? extends I2> foo() { return null; } }
如今呢?這段代碼是能夠經過編譯的。那問題到底出在哪裏?咱們再嘗試將代碼改成
class A { <T extends I1> List<? extends T> foo() { return null; } } class B extends A { @Override <T extends I2> List<? extends T> foo() { return null; } }
這種狀態下通配符的範圍顯而易見至少應該和第2組代碼同樣。可是這段代碼仍然不能經過編譯。這時候大多數人基本上都棄治了,要麼認爲遇到了BUG,要麼各類病急亂投醫。那麼問題到底在哪?其實問題的根本在於若是想重載泛型函數的話那麼要麼不寫任何TypeVariables,要麼函數的TypeVariables必須如出一轍。What!?Excuse me???不是說重載是否正確只取決於那3條麼?怎麼還要要求TypeVariables如出一轍?
首先要聲明的一點是所謂的TypeVariables必須如出一轍是指TypeVariables的Bounds必須相同,因此說標識符是能夠不一樣的,好比 T a()和 A a()的重載是合法的。其實之因此要設計成要求TypeVariables如出一轍是有緣由的。咱們考慮下面的代碼:
// Abstract Stage interface Shitagi { } interface Pants extends Shitagi { } interface BlueWhitePants extends Pants { } interface PinkPants extends Pants { } interface BearPants extends Pants { } // Real Stage interface RealShitagi extends Shitagi { } interface RealPants extends Pants, RealShitagi { } interface RealBlueWhitePants extends BlueWhitePants, RealPants { } interface RealPinkPants extends PinkPants, Pants { } interface RealBearPants extends BearPants, RealPants { } class A<E extends Shitagi> { <T extends Pants> A<T> foo() { return null; } } class B<E extends RealShitagi> extends A<E> { @Override <T extends RealPants> B<T> foo() { return null; } }
在看下面這一段前,先S!T!O!P。想一想到底這玩意到底有沒有問題。而後若是有問題,問題在哪?
=======================EMPTY AREA=======================
首先這段代碼仍然不能被編譯(必定是由於太污,因此編譯器拒絕與您說話並丟出了辣雞BUG www),可是這段代碼更能體現TypeVariables不相同所致使的問題。首先當咱們調用B.foo的時候返回的類型的通配符範圍是B
class A<E extends Shitagi> { <T extends Pants> A<T> foo() { return null; } } class B<E extends RealShitagi> extends A<E> { @Override <T extends Pants> B<T> foo() { return null; } }
這樣編譯器就能直接檢測出B.foo的返回值類型B<>(這裏的2個括號並非打多了,是表示返回值的類型並非一個Wildcard,而是由調用函數時提供的泛型參數T來決定的)和B自己的泛型參數B不兼容。就能避免出現類型安全問題。好比若是這裏的T是PinkPants的話,那麼返回值類型就必須爲B,和B不兼容。
好那麼問題來了,咱們怎麼修復咱們的代碼,使其能夠工做?嗯,這個問題纔是關鍵。首先咱們來看看剛剛那個例子,剛剛那例子的問題在於B.foo的返回值,要使B.foo的既和A.foo兼容,又和B的泛型參數兼容,就只能這麼作:
class A<E extends Shitagi> { <T extends Pants> A<? extends T> foo() { return null; } } class B<E extends RealShitagi> extends A<E> { @Override <T extends Pants> B<? extends T> foo() { return null; } }
這樣改動後B.foo的返回值類型就是B
class A<E extends Shitagi> { <T extends Pants> A<? extends T> foo() { return null; } } class B<E extends RealShitagi> extends A<E> { @Override <T extends Pants> B<? extends RealPants> foo() { return null; } }
這段代碼行不行?固然不行,首先第一個問題是返回值類型和A.foo不兼容,其次問題是T被吃掉了。到這裏大部分人都一臉懵逼了,沒辦法動T,怎麼玩?一些會玩黑魔法的碼農也許會建立一個class C extends B。可是這是異端,設計上毫無美感,而且之後擴展程序時也可能會遇到問題。既然咱們沒辦法動TypeVariables,那麼能不能把T的上界設置爲某個類的TypeVariable,而後對其動手?好想法,咱們來實際嘗試一下:
class A<E extends Shitagi, BASE extends Pants> { <T extends BASE> A<? extends T, BASE> foo() { return null; } } class B<E extends RealShitagi, BASE extends RealPants> extends A<E, BASE> { @Override <T extends BASE> B<? extends T, BASE> foo() { return null; } }
咱們給類加了一個名爲BASE的泛型參數,用於傳遞T的返回值類型。這段代碼可否經過編譯?固然能夠,這段代碼沒有任何問題。惟一缺點就是在建立B的對象時須要手工指定一個多餘的泛型參數,固然咱們也能夠直接寫成:
class B<E extends RealShitagi> extends A<E, RealPants> { @Override <T extends RealPants> B<? extends T> foo() { return null; } }
若是咱們不須要B還有子類,這是目前看來的最佳解決方案。等會,這樣B.foo的TypeVariable是否是和A.foo的不同了?其實仍是同樣的,畢竟在B裏面來看A,BASE這個泛型參數就是RealPants,畢竟在繼承的時候就已經被特化爲RealPants了。因此沒有任何問題,只是寫法有些許區別。好,那麼如何解決返回值類型的泛型參數是Wildcard的問題?那就更簡單了,直接刪掉Wildcard解決問題。代碼能經過編譯,完事OK?目前看來確實是只要把PAD塞進去就完事OK了(wwwww)。畢竟這種問題仍是能夠經過一(塞)些(入)tr(P)ic(A)k(D)來解決的。
那麼如今問題來了,有沒有什麼狀況是用這種最終手段解決不了的。答案是有。假設咱們如今Shitagi系列的自己接口自己就自帶泛型,咱們來看下面的代碼:
// Base Ningen interface Ningen { } interface Onnanoko extends Ningen { } interface Otoko extends Ningen { } // Real Ningen interface KanameMadoka extends Onnanoko { } interface EnkanNoKotowari extends KanameMadoka { // 圓環之理指引着你www } interface ShiinaMashiro extends Onnanoko { } interface ShiinaMakuro extends ShiinaMashiro { // 椎名真黑:實行Plane Cwww } interface OgisoSetsuna extends Onnanoko { } interface Sprite extends OgisoSetsuna { // 老闆來一箱82年的雪碧! } interface NoumiKudryavka extends Onnanoko { } interface NoumiKudryavkaAnatolyevnaStrugatskaya extends NoumiKudryavka { // Wafu,你可識得庫特全名www } interface ItouMakoto extends Otoko { } interface KinoshitaHideyoshi extends Ningen { // 秀吉的性別就是秀吉www } // Abstract Stage interface Shitagi<N extends Ningen> { } interface Pants<N extends Ningen> extends Shitagi<N> { } interface BlueWhitePants<N extends Ningen> extends Pants<N> { } interface PinkPants<N extends Ningen> extends Pants<N> { } interface BearPants<N extends Ningen> extends Pants<N> { } // Real Stage interface RealShitagi<N extends Ningen> extends Shitagi<N> { } interface RealPants<N extends Ningen> extends Pants<N>, RealShitagi<N> { } interface RealBlueWhitePants<N extends Ningen> extends BlueWhitePants<N>, RealPants<N> { } interface RealPinkPants<N extends Ningen> extends PinkPants<N>, Pants<N> { } interface RealBearPants<N extends Ningen> extends BearPants<N>, RealPants<N> { } class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen> { <T extends Pants<NEW_N>, NEW_N extends Ningen> A<T, NEW_N> gift(NEW_N ningen) { return null; } } class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen> extends A<E, ORIGIN_N> { @Override <T extends RealPants<NEW_N>, NEW_N extends Ningen> B<T, NEW_N> gift(NEW_N ningen) { return null; } }
如今咱們把B.foo改爲了B.gift用於gift胖次www。首先因爲TypeVariables不一樣,因此是這段代碼沒法經過編譯。那麼能不能像剛剛那樣解決?若是你嘗試使用上面的方法解決問題,你會發現行不通了,由於RealPants如今帶了一個泛型參數NEW_N。這就尷尬了,而NEW_N這個泛型參數是在調用函數的時候才能使用的,在類裏面是找不到的。
這裏有一種很不美觀且晦澀難懂的解決辦法,將NEW_N放到類裏面:
static class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends Pants<NEW_N>, NEW_N extends Ningen> { <T extends BASE> A<T, NEW_N, RealPants<?>, ?> gift(NEW_N ningen) { return null; } @SuppressWarnings("unchecked") static <E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen, NEW_N extends Ningen> A<E, ORIGIN_N, Pants<NEW_N>, NEW_N> prepare(A<E, ORIGIN_N, ?, ?> a) { return (A<E, ORIGIN_N, Pants<NEW_N>, NEW_N>) a; } } static class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends RealPants<NEW_N>, NEW_N extends Ningen> extends A<E, ORIGIN_N, BASE, NEW_N> { @Override <T extends BASE> B<T, NEW_N, RealPants<?>, ?> gift(NEW_N ningen) { return null; } @SuppressWarnings("unchecked") static <E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen, NEW_N extends Ningen> B<E, ORIGIN_N, RealPants<NEW_N>, NEW_N> prepare(B<E, ORIGIN_N, ?, ?> a) { return (B<E, ORIGIN_N, RealPants<NEW_N>, NEW_N>) a; } }
這樣代碼就能經過編譯了,使用時咱們用一種很彆扭的方式:
B<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, RealPants<?>, ?> b = <Other Code>; ShiinaMashiro mashiro = <Other Code>; B<RealBlueWhitePants<ShiinaMashiro>, ShiinaMashiro, RealPants<?>, ?> new_b = B.<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, ShiinaMashiro>prepare(b).<RealBlueWhitePants<ShiinaMashiro>>gift(mashiro);
先強轉,再調用(嗯,很是Mashiro,Mashiro得我都想給設計Java泛型的人執行Plan C寄刀片了www)。如下是上面代碼使用實例函數的實現:
class A<E extends Shitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends Pants<NEW_N>, NEW_N extends Ningen> { <T extends BASE> A<T, NEW_N, RealPants<?>, ?> gift(NEW_N ningen) { return null; } @SuppressWarnings("unchecked") final <P_NEW_N extends Ningen> A<E, ORIGIN_N, Pants<P_NEW_N>, P_NEW_N> prepareA() { return (A<E, ORIGIN_N, Pants<P_NEW_N>, P_NEW_N>) this; } } class B<E extends RealShitagi<ORIGIN_N>, ORIGIN_N extends Ningen, BASE extends RealPants<NEW_N>, NEW_N extends Ningen> extends A<E, ORIGIN_N, BASE, NEW_N> { @Override <T extends BASE> B<T, NEW_N, RealPants<?>, ?> gift(NEW_N ningen) { return null; } @SuppressWarnings("unchecked") final <P_NEW_N extends Ningen> B<E, ORIGIN_N, RealPants<P_NEW_N>, P_NEW_N> prepareB() { return (B<E, ORIGIN_N, RealPants<P_NEW_N>, P_NEW_N>) this; } }
調用改成:
B<RealBlueWhitePants<NoumiKudryavka>, NoumiKudryavka, RealPants<?>, ?> b = <Other Code>; ShiinaMashiro mashiro = <Other Code>; B<RealBlueWhitePants<ShiinaMashiro>, ShiinaMashiro, RealPants<?>, ?> new_b = b.<ShiinaMashiro>prepareB().<RealBlueWhitePants<ShiinaMashiro>>gift(mashiro);
首先本文假定讀者對Java的泛型有基礎的瞭解,若須要請參考其餘資料配合閱讀。
泛型的泛參(type argument)可使用實際類型或者通配符(wildcard)。其中通配符能夠經過邊界(bound)來限制其接受的實際參數的類型。根據其種類,能夠分爲無界(unbounded)、上界(upper bound)和下界(lower bound)。其泛型邊界決定了輸入(input)和輸出(output)分別能接受什麼類型。
輸入爲其函數的參數、屬性可以賦值的值的類型,輸出爲函數的返回值、獲取到的屬性的值的類型。
如下將詳細描述。
1、實際類型
泛型的泛參可使用實際類型。也就是相似於List,直接指定泛型的類型。這時候泛型的表現最容易理解,輸入和輸出都爲實際類型。須要注意的一點是,泛型不支持協變(Covariant),協變需使用通配符。爲何泛型不支持協變呢。咱們先從支持協變的數組開始考慮。考慮如下代碼:
Object[] array = new String[1]; array[0] = 12.450F;
這段代碼是能夠經過編譯的,然而會讓靜態類型的Java語言在沒有任何強制類型轉換的狀況下出現類型異常。咱們嘗試往一個String類型的數組索引爲0的位置賦值一個Float類型的值,這固然是行不通和徹底錯誤的。Java數組可以協變是一個設計上的根本錯誤,它能致使你的代碼在你徹底不知情的狀況下崩潰和異常,但如今改已經爲時已晚。幸虧咱們有和常用集合API,不然最多見的狀況可能以下:
public Number evil; public void setAll(Number[] array) { for (int i = 0;i < array.length;i++) { array[i] = evil; } } public void looksGood() { atSomewhereWeDontKnown(); //We summoned evil to our kawaii and poor code Float[] ourKawaiiArray = getOurKawaiiArray(); //Oops } public void atSomewhereWeDontKnown() { evil = 12450; } public Float[] getOurKawaiiArray() { Float[] weWantFloatFilled = new Float[0xFF]; setAll(weWantFloatFilled); //Buts... we got (1)E(2)V(4)I(5)L(0)... return weWantFloatFilled; }
咱們可不想讓(1)E(2)V(4)I(5)L(0)充滿咱們的代碼。因此,泛型吸收了這個教訓,自己就是爲了提升類型安全性而設計的泛型不能犯這樣的低級錯誤。因此你不能寫如下代碼:
List<Object> array = new ArrayList<String>; array.set(0, 12.450F);
這段代碼在第一行就沒法經過編譯,由於你嘗試協變一個泛型。其解決辦法和其餘的說明將在後續討論。
2、通配符
1.無界通配符
無界通配符爲」?」,能夠接受任何的實際類型做爲泛參。其能接受的輸入和輸出類型十分有限。
①可用輸入類型
嚴格意義上不能接受任何的類型做爲輸入,考慮如下代碼:
List<?> list = new ArrayList<String>(); list.add("123");
你可能以爲這段代碼看起來沒有問題。一般會這樣考慮,咱們能夠簡單的把無界通配符」?」當作Object,往一個Object類型的列表加一個String有什麼問題?何況其實際就是String類型。其實並不能經過編譯,這並非編譯器出現了錯誤。這裏有個邏輯漏洞,咱們仔細考慮無界通配符的意義。無界通配符表明其接受任何的實際類型,但這並不意味着任何的實際類型均可以做爲其輸入和輸出。其語義上有微妙的但巨大的區別。其含義是不肯定究竟是哪一個實際類型。多是String,多是UUID,多是任何可能的類型。若是這是個UUID列表,那麼往裏面加String等就會出事。若是是String列表,往裏面加UUID等也會出事。或者咱們無論其是什麼類型的列表,往裏面加Object,然而Object裏有你的實際類型的屬性和方法麼。即便實際是Object列表,咱們也沒法肯定。那麼,無界通配符就不能接受任何輸入了麼,看起來是這樣。其實有個例外,null做爲一個十分特殊的值,表示不引用任何對象。咱們能夠說String類型的值能夠爲null、UUID類型的值能夠爲null,甚至Object類型的值能夠爲null。不管是什麼類型,均可以接受null做爲其值,表示不引用任何對象。因此無界通配符的輸入惟一可接受的是可爲全部類型的null。
②可用輸出類型
無界通配符的輸出類型始終爲Object,由於其意義爲接受任何的實際類型做爲泛參,而任何的實際類型均可以被協變爲Object類型,因此其輸出類型天然就爲Object了。沒有什麼須要注意的地方。
2.上界通配符
上界通配符爲」extends」,能夠接受其指定類型或其子類做爲泛參。其還有一種特殊的形式,能夠指定其不只要是指定類型的子類,並且還要實現某些接口。這種用法很是少用,我在不少開源項目中基本沒看到這種用法。因爲這和本章內容無關,不影響輸入和輸出的類型,因此暫不描述。
①可用輸入類型
嚴格意義上一樣不能接受任何的類型做爲輸入,出於嚴謹目的,咱們再從頭分析一遍,此次以Minecraft的源代碼爲例,考慮如下代碼:
List<? extends EntityLiving> list = new ArrayList<EntityPlayer>(); list.add(player);
你可能以爲這段代碼又沒問題了,EntityPlayer確實繼承了EntityLiving。往一個EntityLiving的列表里加EntityPlayer有什麼問題?放肆!12450!好不鬧/w\。這裏的問題在於若是其實是EntityPig的列表呢。這麼想你就應該懂了,和無界通配符差很少,其只是限定了列表必須是EntityLiving的子類而已,咱們並不知道實際是什麼。因此在這裏咱們只能添加EntityLiving類型的對象。是否是以爲有什麼不對?對了,我就是超威藍貓!好不鬧/w\,咱們能在EntityLiving上調用EntityPlayer的getGameProfile麼,明顯不能,何況咱們到底能不能實例化EntityLiving也是個問題。這裏真的很容易混淆概念,必定要牢記,只能使用null做爲上界通配符的輸入值。
②可用輸出類型
好了,此次終於能玩了,上界通配符的輸出類型爲其指定的類型,實際上若是通配符位於泛型類的聲明中例如:
public class Foo<T extends EntityLiving> { public T entity; }
這個類中entity字段的實際類型不是全部類型的父類Object了,而是EntityLiving,這能夠用查看字節碼的方式證明。固然其類型是Object也不會有太大的差異,能夠想到的問題是當咱們以某種方式往其內部傳入了Object類型或其餘不是EntityLiving類型或其子類的對象時,可能會出現類型轉換異常或者更嚴重的留下隨時代碼會崩潰的隱患。而直接使用EntityLiving類型做爲其實際類型就會在嘗試這麼作的同時拋出類型轉換異常,從而避免這種問題。
3.下界通配符
下界通配符爲」super」,能夠接受其指定類型或其父類做爲泛參。可能不少人都沒有用過下界通配符,由於其真的不多用。其主要用處之一是在使用Java或第三方的API的泛型類時,對泛參類型不一樣,但泛參具備繼承關係,且主要關注其輸入的泛型對象進行概括。以Minecraft的源碼爲例,考慮如下代碼:
private EntityMob ourKawaiiMob; private EntityMob otherKawaiiMob; public int compareMobEntity(Comparator<? super EntityMob> comparator) { return comparator.compare(ourKawaiiMob, otherKawaiiMob); }
此方法能夠接受一個比較器,用於比較兩EntityMob。這裏的含義是,咱們但願接受一個EntityMob或其父類的比較器。例如Comparator只會把EntityMob當作一個Entity進行比較,這樣咱們就能夠對EntityMob的某一部分進行比較。咱們不能將一個徹底不是EntityMob的父類的比較器,例如Comparator做爲參數傳入。也不能將一個EntityMob的子類的比較器,例如Comparator做爲參數傳入。由於實際咱們比較的是EntityMob或其子類的對象,即便咱們傳入的是其子類的比較器,咱們也不能保證不會發生用Comparator比較一個EntityEnderman的狀況。又或者即便咱們利用Java的類型擦除這麼作了,java的動態類型檢查會強制拋出ClassCastException。因此在這種狀況下應該使用下界通配符。
①可用輸入類型
下界通配符的輸入類型爲其指定的類型或子類。由於其意義爲接受其指定類型或其父類做爲泛參。那麼不管咱們提供的對象是什麼類型,只要是其指定的類型或子類的對象,那麼毫無例外必定是其指定的類型的對象。咱們不能提供其指定的類型的父類做爲對象,考慮如下代碼:
private EntityLiving our; private EntityLiving other; Comparator<? super EntityMob> comparator = new EntityMobComparator(); comparator.compare(our, other);
這段代碼不能經過編譯,咱們嘗試用一個EntityMob的比較器來比較EntityLiving。不仔細考慮可能覺得這並無什麼問題,EntityMob的比較器徹底有能力來比較EntityLiving啊?可是實際狀況是若是這段代碼成功編譯,並且沒有動態類型檢查的話EntityMob的比較器就可能會嘗試其獲取EntityLiving並無的,屬於EntityMob的屬性,而後就會獲取到非法的數據,或致使Java運行時崩潰,這固然是不行的。好在咱們即便這麼作了,Java也會強制拋出ClassCastException。
②可用輸出類型
下界通配符的輸出類型始終爲Object,由於其意義爲接受其指定類型或其父類做爲泛參,咱們並不知道具體是哪個父類。而任何的實際類型均可以被協變爲Object類型,因此其輸出類型天然就爲Object了。
3、回顧泛型邊界和輸入輸出類型的區別
泛型邊界並不直接表明着能接受的輸入輸出的類型,其含義爲能接受什麼樣的實際類型。而輸入輸出類型能是什麼則是根據泛型邊界的含義得出的,其中的限制是因爲咱們只能經過泛型邊界對實際類型進行猜想而產生的,但願你們能仔細理解其中的含義。
首先本文假定讀者對Java的泛型有基礎的瞭解,若須要請參考其餘資料配合閱讀。
泛型系統是做爲Java 5的一套加強類型安全及減小顯式類型轉換的系統出現的。泛型也叫參數化類型,顧名思義,經過給類型賦予必定的泛型參數,來達到提升代碼複用度和減小複雜性的目的。
在Java中,泛型是做爲語法糖出現的。在虛擬機層面,並不存在泛型這種類型,也不會對泛型進行膨脹,生成出相似於List、List之類的類型。在虛擬機看來,List這個泛型類型只是普通的類型List而已,這種行爲叫泛型擦除(Type Erasure)。
那麼在Java中泛型是如何如何實現其目的的呢?Java的泛型充分利用了多態性。將無界(unbounded)的通配符(wildcard)理解爲Object類型,由於Object類型是全部除標量(Scalar)之外,包括普通的數組和標量數組的類型的父類。將全部有上界(upper bound)的通配符理解爲其上界類型例如將被理解爲CharSequence類型。並在相應的地方自動生成checkcast字節碼進行類型檢查和轉換,這樣就既能夠實現泛型,又不須要在字節碼層面的進行改動來支持泛型。這樣的泛型叫作僞泛型。
下面是幾個幫助理解的例子
public class Foo<T extends CharSequence> { private T value; public void set(T value) { this.value = value; } public T get() { return this.value; } public static void main(String[] args) { Foo<String> foo = new Foo<String>(); foo.set("foo"); String value = foo.get(); } }
編譯後:
public class Foo { private CharSequence value; public void set(CharSequence value) { this.value = value; } public CharSequence get() { return this.value; } public static void main(String[] args) { Foo foo = new Foo(); foo.set("foo"); String value = (String) foo.get(); } }