隨筆-67  評論-522  文章-0  trackbacks-0
              大象根據自己對泛型和反射的使用,來談談對它們的理解,順便整理一下知識,記錄下來,以便以后查找。
              至少在我看來,JDK5.0絕對是一個很具有里程碑意義的版本,在這個版本中,提供了非常多的很有價值的新特性,泛型就是其中之一,并且對反射機制進行了增強,而且5.0版本還把以前集合框架進行了重構全部添加了泛型支持。
              5.0發布到現在差不多快有10年時間了,關于這方面的知識介紹網上可以查到很多,書上也都有講到。大象現在再寫這些東西,一是將自己的經歷體會總結出來作一個積累,另外一點是希望能夠給剛接觸這方面的童鞋一點幫助。
              泛型最大的好處就是類型檢查,尤其是對集合非常有用,另外在底層代碼設計中很有用處,它實現了重用的功能。泛型有兩種定義方式,一個是泛型類,另一個是泛型方法。
              那到底什么是泛型呢?簡單點講(可能不嚴謹),就是用到了類型參數這樣的類型變量,不管是類、接口還是方法,都可以說是用到了泛型。請看例子:
              泛型類
              public class Person<T> {
                  private T t;
                  public T getT() {
                      return t;
                  }
                  public void setT(T t) {
                      this.t = t;
                  }
              }
              T就是類型變量,是一個參數化類型,用尖括號(<>)括起來,放在類名的后面。泛型類的類型變量可以定義多個。類型變量一般都使用一個大寫字母表示,比如本例的Person<T>JDK中的List<E>Map<K,V>等等。
              用具體的類替換類型變量就可以實例化泛型類:Person<Man> person = new Person<Man>();
              像這樣實例化是錯誤的:Person<T> person = new Person<T>(); //ERROR
              泛型方法

          public <T> T get(String key, Object params) {
                 return (T) getSqlSession().selectOne(key, params);
              }

              這是我在SSM3示例的MyBatisDao這個類里面定義的一個方法,此方法就是一個泛型方法。<T>就是類型變量,而get前面的T是返回類型。其實這個方法是存在類型安全問題的,如果我在RoleService里面調用這個方法,將返回類型T寫成User,編譯器是不會有任何警告信息的。
              但如果我改寫一下,將MyBatisDao加上泛型,public class MyBatisDao<T> extends SqlSessionDaoSupport
              這時User的返回類型就出現編譯錯誤了:
              
              編譯器根據MyBatisDao<Role>這個Role類型變量就會推斷出它里面定義的get方法應該返回Role類型。不過這樣改過之后MyBatisDao就變成泛型類了,而get方法也不再是泛型方法。那么泛型方法能不能有安全檢查呢?有,但是需要一些編程技巧,關鍵還是跟你寫的泛型方法有關系,后面提到的類型參數的限定可以對泛型加以約束,解決一些安全檢查的問題。
              類型參數的限定
              對于像<T>這樣的類型變量所代表的范圍有時太大了點,有時不方便使用。比如現在需要實現了java.io.Serializable接口的泛型類,那么這應該如何做呢?JDK那幫專家們為我們設計了一種叫做“有限制的通配符類型”來解決這個問題。一般我們稱為上限 下限,他們一般寫成下面這樣:
              上限:<extends Serializable>或者<? extends T>
              下限:<? super T>
              問號(?)叫做無限制的通配符,它可以表示任何類型。有時候使用類型變量不是那么的方便,通配符類型就很好的解決了這個問題。
              <T extends Serializable>的含義是,T為實現了Serializable接口的類,T為綁定類型(Serializable)的子類型,T和綁定類型可以是接口也可以是類。如果想再加個實現了Comparable接口的限定,只需要這樣寫:<T extends Serializable & Comparable>這樣寫有點不嚴謹,因為Comparable接口是一個泛型接口可以接收泛型參數,現在我們不討論這么復雜的情況。
              <? super T>可以這樣理解,任何T類型變量的超類型,還包括T本身,因為T可以看成是它本身的一個超類型。
              那為什么說extends是上限,而extends是下限呢?通過前面兩個解釋就應該可以看出來,extends Serializable或者extends T說明類型變量必須是Serializable的子類和T變量的子類型,這是不是相當于限制了類型變量的上限了?同理就可以理解下限的含義了。
              說了這么多關于上限和下限的東西,那他們到底有什么用?和怎么用呢?簡單來講,extends限定的類型參數可以從泛型對象讀取,super限定的類型參數可以向泛型對象寫入。這樣說可能有些童鞋要暈了,這到底說的神馬東西呢?
              讓我們換個方式來講,關于泛型的上限與下限已經總結出來一個公式:PECS
              PECS表示producer-extendsconsumer-super
              上面這個就是說如果參數化類型表示一個生產者,就用<? extends T>,如果它表示一個消費者,就用<? super T>。再結合上面的說明一起來理解,是不是清楚了呢?如果還不是很理解,大象再附上一小段代碼來體會下其中的區別。
              public void add(List<? extends T> list){
                  for(T t : list){
                     add(t);
                 }
              }
              public void add(T t){};
              public void add(T t, List<? super T> list){
                  list.add(t);
              }
              泛型的擦除
              泛型主要是在編譯期有效,即編譯的時候檢查類型安全,現在寫代碼一般都會用EclipseIntelliJ,這些集成開發工具都能做到即時編譯,哪里有錯馬上會出現紅色的錯誤標識。因此如果出現類型轉換錯誤,會很明顯的看到結果。但是,在程序的運行階段,JVM是不認識泛型是神馬東西的,所有有泛型的類,接口,方法都會被擦除掉泛型,變成原生類型(raw type),即Person<T>變為PersonList<? extends T> list變成List list等等。
              下面就是之前的Person類用javap反編譯后的結果,所有的類型變量T都被擦除掉了,因為T是一個無限定類型所以用Object替換。而且add方法中的<? extends T><? super T>也被去掉了。
              public class com.bolo.Person extends java.lang.Object{
                 private java.lang.Object t;
                 public com.bolo.Person();
                 public java.lang.Object getT();
                 public void setT(java.lang.Object);
                 public void add(java.util.List);
                 public void add(java.lang.Object);
                 public void add(java.lang.Object, java.util.List);
              }
              因此針對泛型擦除這一特點,我們需要注意這樣幾點:
                  1JVM里面沒有泛型,只有普通的類和方法。
                  2、所有的類型參數都用限定類型或者無限定類型用Object來替代。
                  3、請謹慎處理方法重載,錯誤的使用重載不會實現想要的多態。
                  4、為保證類型安全,必要時請使用強制類型轉換。
              泛型的限制
              基本類型不能作為類型參數。Person<int>就是錯誤的,只能用Person<Integer>
              類型檢查只能用原始類型。if(t instanceof Person) 如果寫成if(t instanceof Person<String>)馬上會出現編譯錯誤
              不能實例化類型變量。這樣寫是錯誤的:T t = new T()
              不能實例化參數化類型的數組。Person<Integer>[] p = new Person<Integer>[5] //ERROR
              不能定義靜態實例變量和靜態方法。如果你想這樣寫:private static T a 那對不起,編譯器馬上會給你一個錯誤提示。
              其實關于泛型的限制完全可以不用講,現在編譯器都很強大,只要你這樣做了,馬上會給你顯示一個錯誤。
              最后說下泛型對于集合的用處來說是最大的,集合是一個容器,有了泛型就更方便重用。而我們使用最頻繁的集合就是List列表,還有一個容器就是數組,大象在這里強烈建議大家多用List,盡量或最好不要用數組。其一是List有類型安全性檢查,其二是數組的功能List都提供了并且更豐富,其三Listgc進行了優化。如果使用數組,特別是操作對象數組,如果經驗不足,沒有釋放數組里面的對象引用,則很容易造成內存泄漏的問題。
              以上這些都是大象使用泛型的一點經驗總結,有什么不對的,或不完善的地方,還請各位指出來,謝謝!
              本文為菠蘿大象原創,如要轉載請注明出處。http://www.aygfsteel.com/bolo
          posted on 2014-04-29 17:09 菠蘿大象 閱讀(7033) 評論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 宜宾市| 禹城市| 泉州市| 弥勒县| 泸水县| 林甸县| 水城县| 新宾| 三门县| 文登市| 内江市| 江孜县| 中西区| 吕梁市| 樟树市| 阳信县| 建平县| 张家口市| 霍山县| 平定县| 临汾市| 灵寿县| 洛川县| 耒阳市| 会同县| 肥乡县| 大方县| 成安县| 贺州市| 阿勒泰市| 桂阳县| 潞西市| 固安县| 长垣县| 容城县| 沁水县| 东宁县| 穆棱市| 黑河市| 汤原县| 屯留县|