Java張孝祥視頻 學習筆記 泛型

/*******************************************************************************************************************************/
此博客主要是在觀看張孝祥老師的教學視頻的過程中,自己所做的學習筆記,在此以博客的形式分享出來,方便大家學習,建議大家觀看視頻,然後以此筆記作爲資料回顧複習。
參考資料
張孝祥2010年賀歲視頻:Java高新技術  視頻下載
瘋狂Java講義
java核心技術講義
/*******************************************************************************************************************************/
爲什麼使用泛型
之所以使用泛型,很大程度上是爲了讓集合記住其中元素的數據類型類型,在泛型出現之前,一旦把一個對象丟進集合中,集合就會忘記對象的類型,把所有的對象當成object類型來進行處理。當程序從集合中取出對象後,就需要進行強制類型轉換,這會讓代碼變得臃腫,同時類型轉換的時候也有可能會出現,ClassCastException異常,使用了泛型之後,編譯器在編譯代碼的時候,會進行類型檢查,如果試圖向集合中添加不滿足類型要求的對象,編譯器會提示類型錯誤,這提高了代碼的安全性;其次使用泛型,可以提高代碼的通用性
/***************************************37節視頻********************************************************/
泛型是給編譯器看的,讓編譯器擋住非法類型的輸入,編譯器編譯帶類型說明的集合,生成字節碼前會去除掉“類型”信息,使程序的運行效率不受影響,對於參數化的泛型類型,getClass()方法的返回值跟原始類型相同。由於編譯生成的字節碼會去除掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型的集合中加入另一個類型的數據。

ArrayList<String> collection2 = new ArrayList<String>();
ArrayList<Integer> collection3 = new ArrayList<Integer>();
System.out.println(collection3.getClass() == collection2.getClass());//打印結果爲true    

collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc");//通過反射將String類型的數據加入到了int集合中

泛型術語
整個稱爲ArrayList<E>泛型類型
ArrayList<E>  中E稱爲類型變量或類型參數
整個ArrayList<Integer>稱爲參數化的類型
ArrayList<Integer>中的Integer稱爲類型參數的實例或者實際類型參數
ArrayList<E> 中的,<>稱爲type of
ArrayList稱爲原始類型

參數化類型跟原始類型兼容    即:爲了與JDK1.4兼容,泛型可以一邊有約束,一邊無約束
(1)參數化的類型可以引用一個原始類型的對象
 ArrayList<String> list = new ArrayList<>();
(2)原始類型可以引用一個參數化類型的對象
Collection c = new ArrayList<String>();

注:將參數化的類型轉換成原始類型後,可能會發生類型錯誤,例如:
 ArrayList<String> strList = new ArrayList<>();
 List   list = strList;//ok
 list.add(1);// only a compile-time warning 不會提示錯誤,只會提示一個警告,但將list的數據取出進行強制類型轉換的時候會出錯
 String str = (String)list.get(0);//Error 報錯

爲了避免上述錯誤,可以用檢查視圖來檢測這類問題

 ArrayList<String> strLista = new ArrayList<>();
 List<String> strListb = Collections.checkedList( strLista,String.class);
 List   list = strListb;//ok
 list.add(1);//Error 報錯
 String str = (String)list.get(0);//



參數化類型不考慮類型繼承關係  即:泛型二邊要麼都不使用約束,要麼二邊約束一致類型,或者一邊有約束一邊沒有約束,同時二邊必須使用引用類型(不能用基本類型實例化類型參數)
ArrayList<Integer>並不是ArrayList<Object>的子類,前者的不能賦值給後者
ArrayList<Object> obj = new ArrayList<Integer>;//這是錯誤

在創建數組實例時,數組的元素不能使用參數化的類型,可以聲明這樣的數組,但是實例化數組的時候會出錯
ArrayList<String>[]  array = new ArrayList<String>[10];//這是錯誤

/***************************************38節視頻********************************************************/
泛型中的通配符
使用?可以引用其他各種參數化的類型,?通配符定義的變量主要用作引用,可以調用參數無關的方法,不能調用參數化有關的方法
public static void printCollection(Collection<Object> c){
     for(Object obj :c){
        System.out.println(obj);
    }
    c.add("abc");//正確
    c  = new ArrayList<String>();//錯誤
}

public static void printCollection(Collection<?> c){
     for(Object obj :c){
        System.out.println(obj);
    }
    c.size();//正確
    c.add("abc");//錯誤
    c  = new ArrayList<String>();//正確
}

類型通配符的擴展
ArrayList<? extends Number> 
ArrayList<? super   Integer>

/***************************************39節視頻********************************************************/
  HashMap<String,Integer> maps = new HashMap<String, Integer>(); //泛型參數化,不能是基本類型
  maps.put("zxx", 28); //此處的28被自動裝箱成Integer類型,
  maps.put("lhm", 35);
  maps.put("flx", 33);
 
  Set<Map.Entry<String,Integer>> entrySet = maps.entrySet();
  for(Map.Entry<String, Integer> entry : entrySet){
   System.out.println(entry.getKey() + ":" + entry.getValue());
  }

/*****************************************40,41節視頻**************************************************/
自定義泛型方法
private static <T> T add(T x,T y){ 
  return null;
}

add(3,4);            T  ---Integer
add(3,3.5);         T  ---Number
add(3,"abc");     T  ---Object

T只能是引用類型,不能是基本類型,上面的基本類型被自動裝箱成相應的引用類型

private static <T> void swap(T[] a,int i,int j){
  T tmp = a[i];
  a[i] = a[j];
  a[j] = tmp;
 }

swap(new String[]{"abc","xyz","itcast"},1,2); //正確
swap(new int[]{1,3,5,4,5},3,4);//錯誤

T只能是引用類型,不能是基本類型,此處無法進行裝箱操作

T 可以添加限定,限定: 上限,下限,實現的接口

類型限定符的擴展
ArrayList<T extends Number>  //即使是接口 ,也用extends而不是implements
ArrayList<T super   Integer>

一個類型變量或者是通配符可以有很多個限定,例如
T extends Comparable&Serializable  
限定類型用&分隔,而逗號用來分隔類型變量
在Java的繼承中,可以根據需要擁有多個接口超類型,但限定中至多隻有一個類。如果用一個類作爲限定,他必須是限定列表的第一個。

泛型代碼和虛擬機

虛擬機沒有泛型類型對象—所有對象都屬於普通類。
無論何時定義一個泛型類型,都自動提供了一個相應的原始類型( raw type)。原始類型的 名字就是刪去類型參數後的泛型類型名。 擦除( erased)類型變量,並替換爲限定類型(無限 定的變量用Object)。
原始類型用第一個限定的類型變量來替換,如果沒有給定限定就用Object替換。類 Pair<T>中的類型變量沒有顯式的限定,因此,原始類型用Object替換T,Pair<
T extends Number
>原始類型用
Number
替換T

翻譯泛型表達式
當程序調用泛型方法時,如果擦除返回類型,編譯器插入強制類型轉換。例如,下面這個
     Pair<
Employee
> pair = new ...
     Emplotee employee = pair.getFirst();
語句序列
擦除getFirst的返回類型後將返回Object類型。編譯器自動插入Employee的強制類型轉換。也就 是說,編譯器把這個方法調用翻譯爲兩條虛擬機指令: 
    對原始方法Pair.getFirst的調用。
    將返回的Object類型強制轉換爲Employee類型。
當存取一個泛型域時也要插入強制類型轉換。假設Pair類的first域和second域都是公有的 (也許這不是一種好的編程風格,但在Java中是合法的)。表達式: 
   Emplotee employee = pair.first;
也會在結果字節碼中插入強制類型轉換。

翻譯泛型方法
類型擦除也會出現在泛型方法中。程序員通常認爲下述的泛型方法
public static <T extends Number> T get()
是一個完整的方法族,而擦除類型之後,只剩下一個方法:
public static  Number  get()
注意,類型參數T已經被擦除了,只留下了限定類型Number。




練習
《一》
private static <T> T autoConvert(Object obj){
      return (T)obj;
 }
 Object obj = "abc";
 String x3 = autoConvert(obj);
《二》
 private static <T> void fillArray(T[] a,T obj){ 
      for(int i=0;i<a.length;i++){
           a[i] = obj;
      }
 }
《三》
public static <T> void printCollection(Collection<T> collection,T obj){ 
     System.out.println(collection.size()); 
      for(Object obj : collection){
           System.out.println(obj);
      }
      collection.add(obj);
 }

public static void printCollection(Collection<?> collection){ 
    System.out.println(collection.size()); 
    for(Object obj : collection){
       System.out.println(obj);
      }
 }

通配符方法比泛型方法更有效,此處利用泛型方法可以對集合添加元素

《四》類型推斷
public static <T> void copy1(Collection<T> dest,T[] src){
 
 }
 
 public static <T> void copy2(T[] dest,T[] src){
 
 } 
copy1(new Vector<String>(),new String[10]); //正確  此處T爲String
copy2(new Date[10],new String[10]);//正確  此處T取Date跟String的交集
copy1(new Vector<Date>(),new String[10]); //錯誤  此處T的類型推斷衝突

此處視頻跳過了

/***************************************42節視頻********************************************************/
定義泛型類型
如果類中有多個方法使用泛型,使用類級別的泛型
public class GenericDao<E>  {
     public void add(E x){
 
     }
 
     public E findById(int id){
             return null;
     }
 
     public void delete(E obj){
 
     }
 
     public void delete(int id){
 
     }
 
     public void update(E obj){
 
     }

    泛型類,可以看做普通類的工廠類
 
   當一個變量被聲明爲泛型時,只能被實例變量跟實例方法調用(也可以被內嵌類型調用),不能被靜態變量跟靜態方法調用。因爲靜態成員是被所有參數化的類所共享的,所以靜態成員不應該有聲明爲泛型的類型參數  
    
    //此處錯誤
    public static  void update2(E obj){ 
 
     }
    //此處正確
     public static <E> void update2(E obj){ 
 
     }
 
     public E findByUserName(String name){
          return null;
     }
     public Set<E> findByConditions(String where){
          return null;
     }
}

CRUD 增刪改查


/***************************************43節視頻********************************************************/

通過反射獲得泛型的實際類型參數

<一>通過反射獲得方法的實際類型參數
 Class<?> clazz = ResolveData .class; //取得 Class
 Method method1 = clazz.getDeclaredMethod("applyCollection1", Collection.class,Map.class); //取得方法
 Type[] type1 = method1.getGenericParameterTypes();    //取得泛型類型參數的集合
           
for(int i=0;i<type1.length;i++){
           ParameterizedType ptype = (ParameterizedType)type1[i];//將其轉成參數化類型,因爲在方法中泛型是參數 
           Type str = ptype.getRawType(); //取得參數的實際類型
           System.out.println("方法參數:"+str);
           Type[] typeActual = ptype.getActualTypeArguments();
           for(int j=0;j<typeActual.length;j++){
                    System.out.println("泛型參數的實際類型:"+typeActual[j]);
            }
  } 
       
 Method method2 = clazz.getDeclaredMethod("applyCollection2",String.class,Integer.class); //取得方法 
 //參數裏面如果不是參數化類型的話,那麼 getGenericParameterTypes就返回與 getParameterTypes 一樣 
 Type[] type2 = method2.getGenericParameterTypes();
 Type[] type3 = method2.getParameterTypes();      
 for(int i=0;i<type2.length;i++){
          System.out.println("getGenericParameterTypes方法獲得的參數:"+type2[i]);
 }
for(int i=0;i<type2.length;i++){
         System.out.println("getParameterTypes方法獲得的參數:"+type3[i]);
 }

 //聲明一個空的方法,並將泛型用做爲方法的參數類型
 public void applyCollection1(Collection<Number> collection,Map<String,Integer> msp){
       
 }
public void applyCollection2(String str,Integer inVal){ 
       
}

打印結果:

方法參數:interface java.util.Collection
泛型參數的實際類型:class java.lang.Number
方法參數:interface java.util.Map
泛型參數的實際類型:class java.lang.String
泛型參數的實際類型:class java.lang.Integer

getGenericParameterTypes方法獲得的參數:class java.lang.String
getGenericParameterTypes方法獲得的參數:class java.lang.Integer
getParameterTypes方法獲得的參數:class java.lang.String
getParameterTypes方法獲得的參數:class java.lang.Integer



整個java文件鏈接:http://pan.baidu.com/s/1gdmzPbd

<二>通過反射獲得成員變量的數據類型

  private Map<String , Integer> score;
  Class<GenericTest> clazz = GenericTest.class;
  Field f = clazz.getDeclaredField("score");
  // 直接使用getType()取出Field類型只對普通類型的Field有效
  Class<?> a = f.getType();
  // 下面將看到僅輸出java.util.Map
  System.out.println("score的類型是:" + a);
  // 獲得Field實例f的泛型類型
  Type gType = f.getGenericType();
  // 如果gType類型是ParameterizedType對象
  if(gType instanceof ParameterizedType)
  {
   // 強制類型轉換
   ParameterizedType pType = (ParameterizedType)gType;
   // 獲取原始類型
   Type rType = pType.getRawType();
   System.out.println("原始類型是:" + rType);
   // 取得泛型類型的泛型參數
   Type[] tArgs = pType.getActualTypeArguments();
   System.out.println("泛型類型是:");
   for (int i = 0; i < tArgs.length; i++)
   {
    System.out.println("第" + i + "個泛型類型是:" + tArgs[i]);
   }

完整例成鏈接:http://pan.baidu.com/s/1hqtj48G

getType方法只能獲得普通類型的Field的數據類型;對於增加了泛型參數的的Field,如果想要獲取其中增加的泛型參數的類型,應該使用getGenericType()方法獲得其類型


<三>通過反射獲得父類的類的泛型參數的數據類型

public class Person<T> {

}

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class Student extends Person<Student> {
    public static void main(String[] args) {
            Student st=new Student();
            Class clazz=st.getClass();
            //getSuperclass()獲得該類的父類
            System.out.println(clazz.getSuperclass());
            //getGenericSuperclass()獲得帶有泛型的父類
            //Type是 Java 編程語言中所有類型的公共高級接口。它們包括原始類型、參數化類型、數組類型、類型變量和基本類型。
            Type type=clazz.getGenericSuperclass();
            System.out.println(type);
            //ParameterizedType參數化類型,即泛型
            ParameterizedType p=(ParameterizedType)type;
            //getActualTypeArguments獲取參數化類型的數組,泛型可能有多個
            Class c=(Class) p.getActualTypeArguments()[0];
            System.out.println(c);
        }
}

打印結果:

class com.test.Person
com.test.Person<com.test.Student>
class com.test.Student


運行時類型查詢只適用於原始類型

虛擬機中的對象總有一個特定的非泛型類型。因此,所有的類型查詢只產生原始類型。 例如,
List<String> list = new ArrayList<>();
if(list  instanceof ArrayList<Number>){
}
list  instanceof ArrayList<Number>返回的是true
實際上僅僅測試list是否是任意類型的一個ArrayList。下面的測試同樣爲真
if(list  instanceof ArrayList<T>){
}
或強制類型轉換
List<Number>  listb = (ArrayList<Number>)list  //WARNING--can only test that list is a ArrayList
要記住這一風險,無論何時使用instanceof或涉及泛型類型的強制類型轉換表達式都會看到 一個編譯器警告。 
同樣的道理, getClass方法總是返回原始類型。例如
List<String> lista = new ArrayList<>();
List<Number> listb = new ArrayList<>();
if(lista.getClass()==listb.getClass())// they are equal 
其比較的結果是true,這是因爲兩次調用getClass都將返回ArrayList.class。

重看 42 不能是靜態類型
重看41類型推斷
翻譯泛型方法
Executors.newScheduledThreadPool(3);

不能拋出也不能捕獲泛型類實例

不能拋出也不能捕獲泛型類的對象。事實上,泛型類擴展Throwable都不合法。例如,下 面的定義將不會通過編譯:
public class Problem<T> extends Exception{}//Error--can not extend Throwable

不能在catch子句中使用類型變量。例如,下面的方法將不能通過編譯:
public static <T extends Throwable> void doWork(T t) 
{
        try{
        }
        catch(T e){ //Erroe--can not catch type variable
         
        }
}

但是,在異常聲明中可以使用類型變量。下面這個方法是合法的:       ??????
public static <T extends Throwable> void doWork(T t) throws T//ok
{
        try{
        }
        catch(Throwable realCause){
            t.initCause(realCause);
            throw t;
        }
}

不能實例化類型變量

不能使用像new T(...), new T[...]或T.class這樣的表達式中的類型變量。但是,可以通過反射調 用Class.newInstance方法來構造泛型對象。遺憾的是,細節有點複雜。具體參考java核心技術講義。

泛型類的靜態上下文中類型變量無效

不能在靜態域或方法中引用類型變量。例如,下列高招將無法施展:
public static Singleton<T>{
    private static T singleInstance;//Error
    pubclic static T getSingleInstance(){////Error
            if(singleInstance==null){
                    //construct new instance of T    
                    return singleInstance;
            }
    }

}
如果這個程序能夠運行,就可以聲明一個Singleton<Random>共享隨機數生成器,聲明一 個Singleton<JFileChooser>共享文件選擇器對話框。但是,這個程序無法工作。類型擦除之後, 只剩下Singleton類,它只包含一個singleInstance域。因此,禁止使用帶有類型變量的靜態域和方法

注意擦除後的衝突   ????????










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章