,這其中E表示List集合中元素的類型。在Java中,相對(duì)于每個(gè)泛型類都有一個(gè)原生類與之對(duì)應(yīng),即不帶任何實(shí)際類型參數(shù)的泛型名稱,如List的原生類型List。他們之間最為明顯的區(qū)別在于List包含的元素必須是E(泛型)類型,如List,那么他的元素一定是String,否則將產(chǎn)生" />

亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

Effective Java (泛型)

系統(tǒng) 2029 0

二十三、請(qǐng)不要在新代碼中使用原生態(tài)類型:

?? ?? 先簡(jiǎn)單介紹一下泛型的概念和聲明形式。聲明中具有一個(gè)或者多個(gè)類型參數(shù)的類或者接口,就是泛型類或接口,如List<E>,這其中E表示List集合中元素的類型。在Java中,相對(duì)于每個(gè)泛型類都有一個(gè)原生類與之對(duì)應(yīng),即不帶任何實(shí)際類型參數(shù)的泛型名稱,如List<E>的原生類型List。他們之間最為明顯的區(qū)別在于List<E>包含的元素必須是E(泛型)類型,如List<String>,那么他的元素一定是String,否則將產(chǎn)生編譯錯(cuò)誤。和泛型不同的是,原生類型List可以包含任何類型的元素,因此在向集合插入元素時(shí),即使插入了不同類型的元素也不會(huì)引起編譯期錯(cuò)誤。那么在運(yùn)行,當(dāng)List的使用從List中取出元素時(shí),將不得不針對(duì)類型作出判斷,以保證在進(jìn)行元素類型轉(zhuǎn)換時(shí)不會(huì)拋出ClassCastException異常。由此可以看出,泛型集合List<E>不僅可以在編譯期發(fā)現(xiàn)該類錯(cuò)誤,而且在取出元素時(shí)不需要再進(jìn)行類型判斷,從而提高了程序的運(yùn)行時(shí)效率。

1 ???? // 原生類型的使用方式
2 ???? class TestRawType {
3 ???????? private final List stamps = new List();
4 ???????? public static void main(String[] args) {
5 ???????????? stamps.add( new Coin(...));
6 ???????? }
7 ???? }
8 ???? class MyRunnable implements Runnable {
9 ???????? @Override
10 ???????? void run() {
11 ???????????? for (Iterator i = stamps.iterator(); i.hasNext(); ) {
12 ???????????????? Stamp s = (Stamp)i.next();? // 這里將拋出類型轉(zhuǎn)換異常
13 ??????????????? // TODO: do something.
14 ???????????? }
15 ???????? }
16 ???? }
?

????? 以上僅為簡(jiǎn)化后的示例代碼,當(dāng)run()方法中拋出異常時(shí),可以很快發(fā)現(xiàn)是在main()中添加了非Stamp類型的元素。如果給stamps對(duì)象添加元素的操作是在多個(gè)函數(shù)或線程中完成的,那么迅速定位到底是哪個(gè)或哪幾個(gè)函數(shù)添加了非Stamp類型的元素,將會(huì)需要更多的時(shí)間去調(diào)試。

1 ???? // 泛型類型的使用方式
2 ???? class TestGenericType {
3 ???????? private final List<Stamp> stamps = new List<Stamp>();
4 ???????? public static void main(String[] args) {
5 ???????????? stamps.add( new Coin(...)); // 該行將直接導(dǎo)致編譯錯(cuò)誤。
6 ???????? }
7 ???? }
8 ???? class MyRunnable implements Runnable {
9 ???????? @Override
10 ???????? void run() {
11 ???????????? for (Stamp s : stamps) { // 這里不再需要類型轉(zhuǎn)換了。
12 ??????????????? // TODO: do something
13 ???????????? }
14 ???????? }
15 ???? }
?

? ? ? 通過(guò)以上兩個(gè)例子可以看出泛型類型相對(duì)于原生類型還是有著非常明顯的優(yōu)勢(shì)的。一般而言,原生類型的使用都是為了保持一定的兼容性,畢竟泛型是在Java 1.5中才推出的。如原有的代碼中(Java 1.5之前)包含一個(gè)函數(shù),其參數(shù)為原生類型,如void func(List l); 在之后的升級(jí)代碼中,如果給該函數(shù)傳入泛型類型的List<E>對(duì)象將是合法的,不會(huì)產(chǎn)生編譯錯(cuò)誤。同時(shí)Java的泛型對(duì)象在運(yùn)行時(shí)也會(huì)被擦除類型,即List<E>擦除類型后將會(huì)變成List,Java之所以這樣實(shí)現(xiàn)也就是為了保持向后的兼容性。
? ? ? 現(xiàn)在我們比較一下List和List<Object>這兩個(gè)類型之間的主要區(qū)別,盡管這兩個(gè)集合可以包含任何類型的對(duì)象元素,但是前者是類型不安全的,而后者則明確告訴使用者可以存放任意類型的對(duì)象元素。另一個(gè)區(qū)別是,如果void func(List l)改為void func(List<Object> l),List<String>類型的對(duì)象將不能傳遞給func函數(shù),因?yàn)镴ava將這兩個(gè)泛型類型視為完全不同的兩個(gè)類型。
? ? ? 在新代碼中不要使用原生類型,這條規(guī)則有兩個(gè)例外,兩者都源于“泛型信息可以在運(yùn)行時(shí)被擦除”這一事實(shí)。在Class對(duì)象中必須要使用原生類型。JLS不允許使用Class的參數(shù)化類型。換句話說(shuō),List.class, String[].class和int.class都是合法的,但是List<String>.class和List<?>.class則是不合法。這條規(guī)則的第二個(gè)例外與instanceof操作符相關(guān)。由于泛型信息可以在運(yùn)行時(shí)被擦除,因此在泛型類型上使用instanceof操作符是非法的。如:

        
          1
        
        
          private
        
        
          void
        
         test(Set o) {


        
          2
        
        
          if
        
         (o 
        
          instanceof
        
         Set) {


        
          3
        
                     Set<?> m = (Set<?>)o;


        
          4
        
                 }


        
          5
        
             }
      

二十四、消除非受檢警告:

? ? ? 在進(jìn)行泛型編程時(shí),經(jīng)常會(huì)遇到編譯器報(bào)出的非受檢警告(unchecked cast warnings),如:Set<Lark> exaltation = new HashSet(); 對(duì)于這樣的警告要盡可能在編譯期予以消除。對(duì)于一些比較難以消除的非受檢警告,可以通過(guò)@SuppressWarnings("unchecked")注解來(lái)禁止該警告,前提是你已經(jīng)對(duì)該條語(yǔ)句進(jìn)行了認(rèn)真地分析,確認(rèn)運(yùn)行期的類型轉(zhuǎn)換不會(huì)拋出ClassCastException異常。同時(shí)要在盡可能小的范圍了應(yīng)用該注解(SuppressWarnings),如果可以應(yīng)用于變量,就不要應(yīng)用于函數(shù)。盡可能不要將該注解應(yīng)用于Class,這樣極其容易掩蓋一些可能引發(fā)異常的轉(zhuǎn)換。見如下代碼:

1 ???? public <T> T[] toArray(T[] a) {
2 ???????? if (a.length < size)
3 ???????????? return (T[])Arrays.copyOf(elements,size,a.getClass());
4 ???????? System.arraycopy(elements,0,a,0,size);
5 ???????? if (a.length > size)
6 ???????????? a[size] = null ;
7 ???????? return a;
8 ???? }
?

?? ?? 編譯該代碼片段時(shí),編譯器會(huì)針對(duì)(T[])Arrays.copyOf(elements,size,a.getClass())語(yǔ)句產(chǎn)生一條非受檢警告,現(xiàn)在我們需要做的就是添加一個(gè)新的變量,并在定義該變量時(shí)加入@SuppressWarnings注解,見如下修訂代碼:

1 ???? public <T> T[] toArray(T[] a) {
2 ???????? if (a.length < size) {
3 ???????????? // TODO: 加入更多的注釋,以便后面的維護(hù)者可以非常清楚該轉(zhuǎn)換是安全的。
4 ???????????? @SuppressWarnings("unchecked") T[] result =
5 ???????????????? (T[])Arrays.copyOf(elements,size,a.getClass());
6 ???????????? return result;
7 ???????? }
8 ???????? System.arraycopy(elements,0,a,0,size);
9 ???????? if (a.length > size)
10 ???????????? a[size] = null ;
11 ???????? return a;
12 ???? }
?

?? ?? 這個(gè)方法可以正確的編譯,禁止非受檢警告的范圍也減少到了最小。
?? ?? 為什么要消除非受檢警告,還有一個(gè)比較重要的原因。在開始的時(shí)候,如果工程中存在大量的未消除非受檢警告,開發(fā)者認(rèn)真分析了每一處警告并確認(rèn)不會(huì)產(chǎn)生任何運(yùn)行時(shí)錯(cuò)誤,然而所差的是在分析之后沒有消除這些警告。那么在之后的開發(fā)中,一旦有新的警告發(fā)生,極有可能淹沒在原有的警告中,而沒有被開發(fā)者及時(shí)發(fā)現(xiàn),最終成為問(wèn)題的隱患。如果恰恰相反,在分析之后消除了所有的警告,那么當(dāng)有新警告出現(xiàn)時(shí)將會(huì)立即引起開發(fā)者的注意。
?? ?
二十五、列表優(yōu)先于數(shù)組:

?? ?? 數(shù)組和泛型相比,有兩個(gè)重要的不同點(diǎn)。首先就是數(shù)組是協(xié)變的,如:Object[] objArray = new Long[10]是合法的,因?yàn)長(zhǎng)ong是Object的子類,與之相反,泛型是不可協(xié)變的,如List<Object> objList = new List<Long>()是非法的,將無(wú)法通過(guò)編譯。因此泛型可以保證更為嚴(yán)格的類型安全性,一旦出現(xiàn)插入元素和容器聲明時(shí)不匹配的現(xiàn)象是,將會(huì)在編譯期報(bào)錯(cuò)。二者的另一個(gè)區(qū)別是數(shù)組是具體化的,因此數(shù)組會(huì)在運(yùn)行時(shí)才知道并檢查它們的元素類型約束。如將一個(gè)String對(duì)象存儲(chǔ)在Long的數(shù)組中時(shí),就會(huì)得到一個(gè)ArrayStoreException異常。相比之下,泛型則是通過(guò)擦除來(lái)實(shí)現(xiàn)的。因此泛型只是在編譯時(shí)強(qiáng)化類型信息,并在運(yùn)行時(shí)丟棄它們的元素類型信息。擦除就是使泛型可以與沒有使用泛型的代碼隨意進(jìn)行交互。由此可以得出混合使用泛型和數(shù)組是比較危險(xiǎn)的,因?yàn)镴ava的編譯器禁止了這樣的使用方法,一旦使用,將會(huì)報(bào)編譯錯(cuò)誤。見如下用例:

1 ???? public void test() {
2 ???????? // 這里我們先假設(shè)該語(yǔ)句可以通過(guò)編譯
3 ???????? List<String>[] stringLists = new List<String>[1];
4 ???????? // 該語(yǔ)句是正常的,intList中將僅包含值為42的一個(gè)整型元素
5 ???????? List<Integer> intList = Arrays.asList(42);
6 ???????? // 該語(yǔ)句也是合法的,因?yàn)閿?shù)組支持協(xié)變
7 ???????? Object[] objects = stringLists;
8 ???????? // 由于泛型對(duì)象在運(yùn)行時(shí)是擦除對(duì)象類型信息的,擦除后intList將變?yōu)長(zhǎng)ist類型
9 ??????? // 而objects是Object類型的數(shù)組,List本身也是Object的子類,因此下面的語(yǔ)句合法。
10 ???????? objects[0] = intList;
11 ???????? // 下面的語(yǔ)句將會(huì)拋出ClassCastException異常。很顯然stringLists[0]是List<Integer>對(duì)象。
12 ???????? String s = stringLists[0].get(0);
13 ???? }
?

? ? ? 從以上示例得出,當(dāng)你得到泛型數(shù)組創(chuàng)建錯(cuò)誤時(shí),最好的解決辦法通常是優(yōu)先使用集合類型List<E>,而不是數(shù)組類型E[]。這樣可能會(huì)損失一些性能或簡(jiǎn)潔性,但是換回的卻是更高的類型安全性和互用性。見如下示例代碼:

1 ???? static Object reduce(List l, Function f, Object initVal) {
2 ???????? Object[] snapshot = l.toArray();
3 ???????? Object result = initVal;
4 ???????? for (Object o : snapshot) {
5 ???????????? return = f.apply(result,o);
6 ???????? }
7 ???????? return result;
8 ???? }
9 ???? interface Function {
10 ???????? Object apply(Object arg1,Object arg2);
11 ???? }
?

? ? ? 事實(shí)上,從以上函數(shù)和接口的定義可以看出,如果他們被定義成泛型函數(shù)和泛型接口,將會(huì)得到更好的類型安全,同時(shí)也沒有對(duì)他們的功能造成任何影響,見如下修改為泛型的示例代碼:

1 ???? static <E> E reduce(List<E> l,Function<E> f,E initVal) {
2 ???????? E[] snapshot = l.toArray();
3 ???????? E result = initVal;
4 ???????? for (E e : snapshot) {
5 ???????????? result = f.apply(result,e);
6 ???????? }
7 ???????? return result;
8 ???? }
9 ???? interface Function<E> {
10 ???????? E apply(E arg1,E arg2);
11 ???? }
?

?? ?? 這樣的寫法回提示一個(gè)編譯錯(cuò)誤,即E[] snapshot = l.toArray();是無(wú)法直接轉(zhuǎn)換并賦值的。修改方式也很簡(jiǎn)單,直接強(qiáng)轉(zhuǎn)就可以了,如E[] snapshot = (E[])l.toArray();在強(qiáng)轉(zhuǎn)之后,仍然會(huì)收到編譯器給出的一條警告信息,即無(wú)法在運(yùn)行時(shí)檢查轉(zhuǎn)換的安全性。盡管結(jié)果證明這樣的修改之后是可以正常運(yùn)行的,但是這樣的寫法確實(shí)也是不安全的,更好的辦法是通過(guò)List<E>替換E[],見如下修改后的代碼:

1 ???? static <E> E reduce(List<E> l,Function<E> f,E initVal) {
2 ???????? E[] snapshot = new ArrayList<E>(l);
3 ???????? E result = initVal;
4 ???????? for (E e : snapshot) {
5 ???????????? result = f.apply(result,e);
6 ???????? }
7 ???????? return result;
8 ???? }
?

二十六、優(yōu)先考慮泛型:

?? ?? 如下代碼定義了一個(gè)非泛型集合類:

1 ???? public class Stack {
2 private Object[] elements;
3 ???????? private int size = 0;
4 ???????? private static final int DEFAULT_INITIAL_CAPACITY = 16;
5 ???????? public Stack() {
6 ???????????? elements = new Object[DEFAULT_INITIAL_CAPACITY];
7 ???????? }
8 ???????? public void push(Object e) {
9 ???????????? ensureCapacity();
10 ???????????? elements[size++] = e;
11 ???????? }
12 ???????? public Object pop() {
13 ???????????? if (size == 0)
14 ???????????????? throw new EmptyStackException();
15 ???????????? Object result = elements[--size];
16 ???????????? elements[size] = null ;
17 ???????????? return result;
18 ???????? }
19 ???????? public boolean isEmpty() {
20 ???????????? return size == 0;
21 ???????? }
22 ???????? private void ensureCapacity() {
23 ???????????? if (elements.length == size)
24 ???????????????? elements = Arrays.copyOf(elements,2 * size + 1);
25 ???????? }
26 ???? }
?

????? 在看與之相對(duì)于的泛型集合實(shí)現(xiàn)方式:

1 ???? public class Stack<E> {
2 ???????? private E[] elements;
3 ???????? private int size = 0;
4 ???????? private static final int DEFAULT_INITIAL_CAPACITY = 16;
5 ???????? public Stack() {
6 ???????????? elements = new E[DEFAULT_INITIAL_CAPACITY];
7 ???????? }
8 ???????? public void push(E e) {
9 ???????????? ensureCapacity();
10 ???????????? elements[size++] = e;
11 ???????? }
12 ???????? public E pop() {
13 ???????????? if (size == 0)
14 ???????????????? throw new EmptyStackException();
15 ???????????? E result = elements[--size];
16 ???????????? elements[size] = null ;
17 ???????????? return result;
18 ???????? }
19 ???????? public boolean isEmpty() {
20 ???????????? return size == 0;
21 ???????? }
22 ???????? private void ensureCapacity() {
23 ???????????? if (elements.length == size)
24 ???????????????? elements = Arrays.copyOf(elements,2 * size + 1);
25 ???????? }
26 ???? }
?

????? 上面的泛型集合類Stack<E>在編譯時(shí)會(huì)引發(fā)一個(gè)編譯錯(cuò)誤,即elements = new E[DEFAULT_INITIAL_CAPACITY]語(yǔ)句不能直接實(shí)例化泛型該類型的對(duì)象。修改方式如下:elements = (E[])new Object[DEFAULT_INITIAL_CAPACITY],只要我們保證所有push到該數(shù)組中的對(duì)象均為該類型的對(duì)象即可,剩下需要做的就是添加注解以消除該警告:

        
          1
        
             @SuppressWarning("unchecked")


        
          2
        
        
          public
        
         Stack() {


        
          3
        
                 elements = (E[])
        
          new
        
         Object[DEFAULT_INITIAL_CAPACITY];


        
          4
        
             }
      

????? 總而言之,使用泛型比使用需要在客戶端代碼中進(jìn)行轉(zhuǎn)換的類型來(lái)的更加安全,也更加容易。在設(shè)計(jì)新類型的時(shí)候,要確保它們不需要這種轉(zhuǎn)換就可以使用。這通常意味著要把類做成是泛型的。
?? ?
二十七、優(yōu)先考慮泛型方法:

?? ?? 和優(yōu)先選用泛型類一樣,我們也應(yīng)該優(yōu)先選用泛型方法。特別是靜態(tài)工具方法尤其適合于范興華。如Collections.sort()和Collections.binarySearch()等靜態(tài)方法。見如下非泛型方法:

        
          1
        
        
          public
        
        
          static
        
         Set union(Set s1, Set s2) {


        
          2
        
                 Set result = 
        
          new
        
         HashSet(s1);


        
          3
        
                 result.addAll(s2);


        
          4
        
        
          return
        
         result;


        
          5
        
             }
      

????? 這個(gè)方法在編譯時(shí)會(huì)有警告報(bào)出。為了修正這些警告,最好的方法就是使該方法變?yōu)轭愋桶踩?,要將方法聲明修改為聲明一個(gè)類型參數(shù),表示這三個(gè)集合的元素類型,并在方法中使用類型參數(shù),見如下修改后的泛型方法代碼:

        
          1
        
        
          public
        
        
          static
        
         <E> Set<E> union(Set<E> s1,Set<E> s2) {


        
          2
        
                 Set<E> result = 
        
          new
        
         HashSet<E>(s1);


        
          3
        
                 result.addAll(s2);


        
          4
        
        
          return
        
         result;


        
          5
        
             }
      

????? 和調(diào)用泛型對(duì)象構(gòu)造函數(shù)來(lái)創(chuàng)建泛型對(duì)象不同的是,在調(diào)用泛型函數(shù)時(shí)無(wú)須指定函數(shù)的參數(shù)類型,而是通過(guò)Java編譯器的類型推演來(lái)填充該類型信息,見如下泛型對(duì)象的構(gòu)造:
?? ?? Map<String,List<String>> anagrams = new HashMap<String,List<String>>();
?? ?? 很明顯,以上代碼在等號(hào)的兩邊都顯示的給出了類型參數(shù),并且必須是一致的。為了消除這種重復(fù),可以編寫一個(gè)泛型靜態(tài)工廠方法,與想要使用的每個(gè)構(gòu)造器相對(duì)應(yīng),如:

        
          1
        
        
          public
        
        
          static
        
         <K,V> HashMap<K,V> newHashMap() {

        
          2
        
        
          return
        
        
          new
        
         HashMap<K,V>();

        
          3
        
             }
      

?? ?? 我們的調(diào)用方式也可以改為:Map<String,List<String>> anagrams = newHashMap();
?? ?? 除了在以上的情形下使用泛型函數(shù)之外,我們還可以在泛型單例工廠的模式中應(yīng)用泛型函數(shù),這些函數(shù)通常為無(wú)狀態(tài)的,且不直接操作泛型對(duì)象的方法,見如下示例:

1 ???? public interface UnaryFunction<T> {
2 ???????? T apply(T arg);
3 ???? }
4 ???? private static UnaryFunction<Object> IDENTITY_FUNCTION
5 ???????? = new UnaryFunction<Object>() {
6 ???????????? public Object apply(Object arg) {
7 ???????????????? return arg;
8 ???????????? }
9 ???????? };
10 ???? @SuppressWarning("unchecked")
11 ???? public static <T> UnaryFunction<T> identityFunction() {
12 ???????? return (UnaryFunction<T>)IDENTITY_FUNCTION;
13 ???? }
?

????? 調(diào)用方式如下:

1 ???? public static void main(String[] args) {
2 ???????? String[] strings = {"jute","hemp","nylon"};
3 ???????? UnaryFunction<String> sameString = identityFunction();
4 ???????? for (String s : strings)
5 ???????????? System.out.println(sameString.apply(s));
6 ????????
7 ???????? Number[] numbers = {1,2.0,3L};
8 ???????? UnaryFunction<Number> sameNumber = identityFunction();
9 ???????? for (Number n : numbers)
10 ???????????? System.out.println(sameNumber.apply(n));
11 ???? }
?

????? 對(duì)于該靜態(tài)函數(shù),如果我們?yōu)轭愋蛥?shù)添加更多的限制條件,如參數(shù)類型必須是Comparable<T>的實(shí)現(xiàn)類,這樣我們的函數(shù)對(duì)象便可以基于該接口做更多的操作,而不僅僅是像上例中只是簡(jiǎn)單的返回參數(shù)對(duì)象,見如下代碼:

1 ???? public static <T extends Comparable<T>> T max(List<T> l) {
2 ???????? Iterator<T> i = l.iterator();
3 ???????? T result = i.next();
4 ???????? while (i.hasNext()) {
5 ???????????? T t = i.next();
6 ???????????? if (t.compareTo(result) > 0)
7 ???????????????? result = T;
8 ???????? }
9 ???????? return result;
10 ???? }
?

????? 總而言之,泛型方法就想泛型對(duì)象一樣,提供了更為安全的使用方式。
?? ?
二十八、利用有限制通配符來(lái)提升API的靈活性:

?? ?? 前面的條目已經(jīng)解釋為什么泛型不支持協(xié)變,而在我們的實(shí)際應(yīng)用中可能確實(shí)需要一種針對(duì)類型參數(shù)的特化,幸運(yùn)的是,Java提供了一種特殊的參數(shù)化類型,稱為有限制的通配符類型(bounded wildcard type),來(lái)處理類似的情況。見如下代碼:

        
          1
        
        
          public
        
        
          class
        
         Stack<E> {


        
          2
        
        
          public
        
         Stack();


        
          3
        
        
          public
        
        
          void
        
         push(E e);


        
          4
        
        
          public
        
         E pop();


        
          5
        
        
          public
        
        
          boolean
        
         isEmpty();


        
          6
        
             }
      

?? ?? 現(xiàn)在我們需要增加一個(gè)方法:

        
          1
        
        
          public
        
        
          void
        
         pushAll(Iterable<E> src) {


        
          2
        
        
          for
        
         (E e : src)


        
          3
        
                     push(e);


        
          4
        
             }
      

?? ?? 如果我們的E類型為Number,而我們卻喜歡將Integer對(duì)象也插入到該容器中,現(xiàn)在的寫法將會(huì)導(dǎo)致編譯錯(cuò)誤,因?yàn)榧词笽nteger是Number的子類,由于類型參數(shù)是不可變的,因此這樣的寫法也是錯(cuò)誤的。需要進(jìn)行如下的修改:

        
          1
        
        
          public
        
        
          void
        
         pushAll(Iterable<? 
        
          extends
        
         E> src) {


        
          2
        
        
          for
        
         (E e : src)


        
          3
        
                     push(e);


        
          4
        
             }
      

?? ?? 修改之后該方法便可以順利通過(guò)編譯了。因?yàn)閰?shù)中Iterable的類型參數(shù)被限制為E(Number)的子類型即可。
?? ?? 既然有了pushAll方法,我們可能也需要新增一個(gè)popAll的方法與之對(duì)應(yīng),見如下代碼:

        
          1
        
        
          public
        
        
          void
        
         popAll(Collection<E> dst) {

        
          
2
        
        
          while
        
         (!isEmpty())


        
          3
        
                     dst.add(pop());


        
          4
        
             }
      

? ? ? popAll方法將當(dāng)前容器中的元素全部彈出,并以此添加到參數(shù)集合中。如果Collections中的類型參數(shù)和Stack完全一致,這樣的寫法不會(huì)有任何問(wèn)題,然而在實(shí)際的應(yīng)用中,我們通常會(huì)將Collection中的元素視為更通用的對(duì)象類型,如Object,見如下應(yīng)用代碼:
? ? ? Stack<Number> numberStack = new Stack<Number>();
? ? ? Collection<Object> objs = createNewObjectCollection();
? ? ? numberStack.popAll(objs);
? ? ? 這樣的應(yīng)用方法將會(huì)導(dǎo)致編譯錯(cuò)誤,因?yàn)镺bject和Stack中Number參數(shù)類型是不匹配的,而我們對(duì)目標(biāo)容器中對(duì)象是否為Number并不關(guān)心,Object就已經(jīng)滿足我們的需求了。為了到達(dá)這種更高的抽象,我們需要對(duì)popAll做如下的修改:

        
          1
        
        
          public
        
        
          void
        
         popAll(Collection<? 
        
          super
        
         E> dst) {


        
          2
        
        
          while
        
         (!isEmpty())

        
          
3
        
                     dst.add(pop());


        
          4
        
             }
      

?? ?? 修改之后,之前的使用方式就可以順利通過(guò)編譯了。因?yàn)閰?shù)集合的類型參數(shù)已經(jīng)被修改為E(Number)的超類即可。
? ? ? 這里給出了一個(gè)助記方式,便于我們記住需要使用哪種通配符類型:
? ? ? ? PECS(producer-extends, consumer-super)
? ? ? 解釋一下,如果參數(shù)化類型表示一個(gè)T生產(chǎn)者,就使用<? extends T>,如果它表示一個(gè)T消費(fèi)者,就使用<? super T>。在我們上面的例子中,pushAll的src參數(shù)產(chǎn)生E實(shí)例供Stack使用,因此src相應(yīng)的類型為Iterable<? extends E>;popAll的dst參數(shù)通過(guò)Stack消費(fèi)E實(shí)例,因此dst相應(yīng)的類型為Collection<? super E>。PECS這個(gè)助記符突出了使用通配符類型的基本原則。
? ? ? 在上一個(gè)條目中給出了下面的泛型示例函數(shù):
? ? ? ? public static <E> Set<E> union(Set<E> s1, Set<E> s2);
? ? ? 這里的s1和s2都是生產(chǎn)者,根據(jù)PECS原則,它們的聲明可以改為:
?? ?? ? public static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2);
? ? ? 由于泛型函數(shù)在調(diào)用時(shí),其參數(shù)類型是可以通過(guò)函數(shù)參數(shù)的類型推演出來(lái)的,如果上面的函數(shù)被如下方式調(diào)用時(shí),將會(huì)導(dǎo)致Java的編譯器無(wú)法推演出泛型參數(shù)的實(shí)際類型,因此引發(fā)了編譯錯(cuò)誤。
?? ?? Set<Integer> integers = new Set<Integer>();
?? ?? Set<Double> doubles = new Set<Double>();
? ? ? Set<Number> numbers = union(integers,doubles);
? ? ? 如果想順利通過(guò)編譯并得到正確的執(zhí)行結(jié)果,我們只能通過(guò)顯示的方式指定該函數(shù)類型參數(shù)的實(shí)際類型,從而避免了編譯器的類型參數(shù)自動(dòng)推演,見修改后的代碼:
? ? ? Set<Number> numbers = Union.<Number>union(integers,doubles);
?? ?
? ? ? 現(xiàn)在我們?cè)賮?lái)看一下前面也給出過(guò)的max方法,其初始聲明為:
? ? ? public static <T extends Comparable<T>> T max<List<T> srcList);
? ? ? 下面是修改過(guò)的使用通配符類的聲明:
?? ?? public static <T extends Comparable<? super T>> T max(List<? extends T> srcList);
? ? ? 下面將逐一給出新聲明的解釋:
? ? ? 1.?? ?函數(shù)參數(shù)srcList產(chǎn)生了T實(shí)例,因此將類型從List<T>改為L(zhǎng)ist<? extends T>;
?? ?? 2.?? ?最初T被指定為擴(kuò)展Comparable<T>,然而Comparable又是T的消費(fèi)者,用于比較兩個(gè)T之間的順序關(guān)系。因此參數(shù)化類型Comparable<T>被替換為Comparable<? super T>。
? ? ? ? 注:Comparator和Comparable一樣,他們始終都是消費(fèi)者,因此Comparable<? super T>優(yōu)先于Comparable<T>。

二十九、優(yōu)先考慮類型安全的異構(gòu)容器:

?? ?? 泛型通常用于集合,如Set和Map等。這樣的用法也就限制了每個(gè)容器只能有固定數(shù)目的類型參數(shù),一般來(lái)說(shuō),這也確實(shí)是我們想要的。然而有的時(shí)候我們需要更多的靈活性,如數(shù)據(jù)庫(kù)可以用任意多的Column,如果能以類型安全的方式訪問(wèn)所有Columns就好了,幸運(yùn)的是有一種方法可以很容易的做到這一點(diǎn),就是將key進(jìn)行參數(shù)化,而不是將容器參數(shù)化,見以下代碼:

        
          1
        
        
          public
        
        
          class
        
         Favorites {


        
          2

        
        
          public
        
         <T> 
        
          void
        
         putFavorite(Class<T> type,T instance);

        
          
3
        
        
          public
        
         <T> T getFavorite(Class<T> type);

        
          
4
        
             }
      

? ? ? 下面是該類的使用示例:

1 ???? public static void main(String[] args) {
2 Favorites f = new Favorites();
3 ???????? f.putFavorite(String. class ,"Java");
4 f.putFavorite(Integer. class ,0xcafebabe);
5 ???????? f.putFavorite(Class. class ,Favorites. class );
6 ???????? String favoriteString = f.getFavorite(String. class );
7 ???????? int favoriteInteger = f.getFavorite(Integer. class );
8 ???????? Class<?> favoriteClass = f.getFavorite(Class. class );
9 ???????? System.out.printf("%s %x %s\n",favoriteString
10 ,favoriteInteger,favoriteClass.getName());
11 ???? }
12 ???? // Java cafebabe Favorites
?

? ? ? 這里Favorites實(shí)例是類型安全的:當(dāng)你請(qǐng)求String的時(shí)候,它是不會(huì)給你Integer的。同時(shí)它也是異構(gòu)的容器,不像普通的Map,他的所有鍵都是不同類型的。下面就是Favorites的具體實(shí)現(xiàn):

1 ???? public class Favorites {
2 ???????? private Map<Class<?>,Object> favorites =
3 ???????????? new HashMap<Class<?>,Object>();
4 ???????? public <T> void putFavorite(Class<T> type,T instance) {
5 ???????????? if (type == null )
6 ???????????????? throw new NullPointerException("Type is null");
7 ???????????? favorites.put(type,type.cast(instance));
8 ???????? }
9 ???????? public <T> T getFavorite(Class<T> type) {
10 ???????????? return type.cast(favorites.get(type));
11 ???????? }
12 ???? }
?

?? ?? 可以看出每個(gè)Favorites實(shí)例都得到一個(gè)Map<Class<?>,Object>容器的支持。由于該容器的值類型為Object,為了進(jìn)一步確實(shí)類型的安全性,我們?cè)趐ut的時(shí)候通過(guò)Class.cast()方法將Object參數(shù)嘗試轉(zhuǎn)換為Class所表示的類型,如果類型不匹配,將會(huì)拋出ClassCastException異常。以此同時(shí),在從Map中取出值對(duì)象的時(shí)候,由于該對(duì)象當(dāng)前的類型是Object,因此我們需要再次利用Class.cast()函數(shù)將其轉(zhuǎn)換為我們的目標(biāo)類型。
? ? ? 對(duì)于Favorites類的put/get方法,有一個(gè)非常明顯的限制,即我們無(wú)法將“不可具體化”類型存入到該異構(gòu)容器中,如List<String>、List<Integer>等泛型類型。這樣的限制主要源于Java中泛型類型在運(yùn)行時(shí)的類型擦出機(jī)制,即List<String>.class和List<Integer>.class是等同的對(duì)象,均為L(zhǎng)ist.class。如果Java編譯器通過(guò)了這樣的調(diào)用代碼,那么List<String>.class和List<Integer>.class將會(huì)返回相同的對(duì)象引用,從而破壞Favorites的內(nèi)部結(jié)構(gòu)。

Effective Java (泛型)


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦?。。?/p>

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 中文一级国产特级毛片视频 | 一级aa毛片| 涩涩亚洲| 久久伊伊香蕉综合精品 | 久久婷婷综合在线视频观看6 | 日本黄 色 成 年 人免费观看 | 亚洲国产精品久久久久网站 | 成人欧美一区二区三区视频xxx | 欧美日韩一二三区 | 中文字幕在线不卡精品视频99 | 99精品视频在线观看免费 | 亚洲va欧美va国产va天堂 | 久久综合九色综合97欧美 | 久久久久久久久久久96av | 波多野结衣av1区2区3区 | 黄色www网站 | 久青草国产在线 | 中文无码久久精品 | 日本强日本不卡一 | 亚洲视频在线免费观看 | 奇米888在线看奇米999 | 亚洲色五月| 日韩欧美综合视频 | 日本一级毛片中文字幕 | 日本老熟妇激情毛片 | 99视频全部看免费观 | 亚洲精品a| 欧美一区在线观看视频 | 在线欧洲成人免费视频 | 亚洲我不卡 | 波多野结衣久久精品 | 日韩中文在线 | 看全色黄大色黄大片色责看的 | 亚洲欧洲一区二区 | 亚洲va精品中文字幕 | 99精品视频在线 | 精品国产一区二区三区在线观看 | 欧美末成年videos在线观看 | 亚洲综合专区 | 91九色视频无限观看免费 | 精品国产不卡一区二区三区 |