Java學習之泛型

一、泛型概述

--->JDK1.5新特性

1、泛型的出現:

1、泛型是在JDK1.5以后出現的新特性。泛型是用于解決安全問題的,是一個安全機制。

2、JDK1.5的集合類希望在定義集合時,明確表明你要向集合中裝入那種類型的數據,無法加入指定類型以外的數據。

3、泛型是提供給javac編譯器使用的可以限定集合中的輸入類型說明的集合時,會去掉“類型”信息,使程序運行效率不受影響,對參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣。

4、由于編譯生成的字節碼會去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,如用反射得到集合,再調用add方法即可。

2、好處:

1、使用泛型集合,可將一個集合中的元素限定為一個特定類型,集合中只能存儲同一個類型的對象;這樣就將運行時期出現的問題ClassCastException轉移到了編譯時期,方便與程序員解決問題,讓運行時期問題減少,提高安全性。

2、當從集合中獲取一個對象時,編譯器也可知道這個對象的類型,不需要對對象進行強制轉化,避免了強制轉換的麻煩,這樣更方便。

3、泛型格式:

通過<>來定義要操作的引用數據類型
如:

TreeSet<String>   //來定義要存入集合中的元素指定為String類型

4、泛型定義中的術語:

如:ArrayList<E>類和ArrayList<Integer>

1、ArrayList<E>整個稱為泛型類型

2、ArrayList<E>中的E稱為類型變量或類型參數

3、整個ArrayList<Integer>稱為參數化類型

4、ArrayList<Integer>中的Integer稱為類型參數的實例或實際類型參數

5、ArrayList<Integer>中的<>稱為typeof

6、ArrayList稱為原始類型

參數化:parametered,已經將參數變為實際類型的狀態。

5、在使用java提供的對象時,何時寫泛型?

通常在集合框架中很常見,只要見到<>就要定義泛型,其實<>就是用來接收類型的,當使用集合時,將集合中要存儲的數據類型作為參數傳遞到<>中即可。

6、關于參數化類型的幾點說明:

1、參數化類型與原始類型的兼容性

第一、參數化類型可引用一個原始類型的對象,編譯只是報警告,能不能通過編譯,是編譯器說了算。
如:

Collection<String> coll = new Date();

第二、原始類型可引用一個參數化類型的對象,編譯報告警告
如:

Collection coll = new Vector<String>();

原來的方法接受一個集合參數,新類型也要能傳進去。

2、參數的類型不考慮類型參數的繼承關系:

Vector<String> v = new Vector<Objec>();//錯誤的

不寫Object沒錯,寫了就是明知故犯

Vector<Objec> v = new Vector<String>();//錯誤的

3、在創建數組實例時,數組的元素不能使用參數化的類型
如:

Vector<Integer> v[] = newVector<Integer>[10];//錯誤的

示例:

import java.lang.reflect.Constructor;  
import java.util.*;  
  
public class Generic {  
    public static void main(String[] args) throws Exception {  
        ArrayList<String> al = new ArrayList<String>();  
        al.add("25");  
        al.add("b");  
        System.out.println(al.get(1));  
          
        ArrayList<Integer> at = new ArrayList<Integer>();  
        at.add(23);  
        at.add(3);  
        System.out.println(at.get(1));  
        //編譯器生成的字節碼會去掉泛型的類型信息  
        System.out.println((al.getClass() == at.getClass()) +   
                            "-->" + at.getClass().getName());  
          
        //at.add("ab")-->報錯,存儲的應為Integer類型   
        //反射方式,由于編譯器生成的字節碼會去掉泛型的類型信息,  
        //所以用反射可跳過編譯器,存入任何類型  
        at.getClass().getMethod("add",Object.class).invoke(at,"abcd");  
        at.getClass().getMethod("add",Object.class).invoke(at,5);  
        System.out.println("反射方式:" + at.get(3));  
        System.out.println("反射方式:" + at.get(4));  
          
        //反射方式獲得new String(new StringBuffer("abc"));  
        Constructor<String> cons = String.class.getConstructor(StringBuffer.class);  
        String st = cons.newInstance(new StringBuffer("abc"));  
        System.out.println(st);

二、泛型的通配符

1、泛型中的通配符?

當傳入的類型不確定時,可以使用通配符?

1、使用?通配符可引用其他各種類型化的類型,通配符的變量主要用作引用,也可調用與參數化無關的方法,但不能調用與參數化有關的方法。

2、可對通配符變量賦任意值:
Collection<?> coll ---> coll = newHashSet<Date>();
如:

public static void printObj(Collection<?> coll){  
    //coll.add(1);是錯誤的,如果傳入的是String類型,就不符合了  
    for(Object obj : coll){  
        System.out.println(obj);  
    }  
} 

示例:

import java.util.*;   
class GenerticDemo    
{    
    public static void main(String[] args)     
    {    
        ArrayList<String> p = new ArrayList<String>();    
        p.add("per20");    
        p.add("per11");    
        p.add("per52");    
        print(p);    
        ArrayList<Integer> s = new ArrayList<Integer>();    
        s.add(new Integer(4));    
        s.add(new Integer(7));    
        s.add(new Integer(1));    
        print(s);    
    }    
    
    public static void print(ArrayList<?> al) {    
        Iterator<?> it = al.listIterator();    
        while (it.hasNext()) {  
            System.out.println(it.next());    
        }    
    }    
}

2、通配符的擴展-->泛型的限定:

對于一個范圍內的一類事物,可以通過泛型限定的方式定義,
有兩種方式:

1、? extends E:可接收E類型或E類型的子類型;稱之為上限。
如:

Vector<? extends Number> x = newvector<Integer>();

2、? super E:可接收E類型或E類型的父類型;稱之為下限。
如:

Vector<? super Integer>x = newvector<Number>();

示例如下:

/*  
泛型的限定:  
  
*/    
import java.util.*;    
class GenerticXian2    
{    
    public static void main(String[] args)     
    {    
            
        TreeSet<Student> s = new TreeSet<Student>(new Comp());    
        s.add(new Student("stu0"));    
        s.add(new Student("stu3"));    
        s.add(new Student("stu1"));    
        print(s);    
        System.out.println("Hello World!");    
        TreeSet<Worker> w = new TreeSet<Worker>(new Comp());    
        w.add(new Worker("Worker0"));    
        w.add(new Worker("Worker3"));    
        w.add(new Worker("Worker1"));    
        print(w);    
    }    
    
    public static void print(TreeSet<? extends Person> ts) {    
        Iterator<? extends Person> it = ts.iterator();    
        while (it.hasNext()){    
            Person p = it.next();    
            System.out.println(p.getName());    
        }    
    }    
}    
    
class Person implements Comparable<Person>  {    
    private String name;    
    Person(String name) {    
        this.name = name;    
    }    
    public String getName() {    
        return name;    
    }    
    public int compareTo(Person p){    
        return this.getName().compareTo(p.getName());    
    }    
}    
class Comp implements Comparator<Person> {    
    public int compare(Person p1,Person p2){    
        return p1.getName().compareTo(p2.getName());    
    }    
}    
class Student extends Person {    
    Student(String name){    
        super(name);    
    }    
}    
    
class Worker extends Person {    
    Worker(String name){    
        super(name);    
    }    
}

三、泛型方法

1、java中泛型方法的定義:

private static <T> T add(T a, T b){  
        ......  
        return null;  
}  
  
        ········
        add(3,5);//自動裝箱和拆箱  
        Number x1 = add(3.5,5);//取兩個數的交集類型Number  
        Object x2 = add(3,"abc");//去最大交集為Object  
        ········

1、何時定義泛型方法:為了讓不同方法可以操作不同的類型,而且類型不確定,那么就可以定義泛型方法

2、特殊之處:靜態方法不可以訪問類上定義的泛型,如果靜態方法操作的引用數據類型不確定,可以將泛型定義在方法上。

2、泛型方法的特點:

1、位置:用于放置泛型的類型參數的<>應出現在方法的其他所有修飾符之后和在方法的返回類型之前,也就是緊鄰返回值之前,按照慣例,類型參數通常用單個大寫字母表示。

2、只有引用類型才能作為泛型方法的實際參數

3、除了在應用泛型時可以使用extends限定符,在定義泛型時也可以使用extends限定符。

4、普通方法、構造函數和靜態方法中都可以使用泛型。

5、可以用類型變量表示異常,稱之為參數化的異常,可用于方法的throws列表中,但是不能用于catch子句中。

6、在泛型中可同時有多個類型參數,在定義它們的<>中用逗號分開。

public static <K,V> V getValue(K key){  
    Map<K, V> map = new HashMap<K, V>();  
    return map.get(key);  
}  
private static <T extends Exception> void sayHello() throws T{  
    try{}  
    catch(Exception e){  
        throw (T)e;  
    }  
} 

3、這個T和?有什么區別呢?

1、T限定了類型,傳入什么類型即為什么類型,可以定義變量,接收賦值的內容。

2、?為通配符,也可以接收任意類型但是不可以定義變量。
但是這樣定義,雖然提高了擴展性,可還是有一個局限性,就是不能使用其他類對象的特有方法。

3、總結:
通配符方案要比泛型方法更有效,當一個類型變量用來表達兩個參數之間或參數和返回值之間的關系時,即同一個類型變量在方法簽名的兩處被使用,或者類型變量在方法體代碼中也被使用,而不是僅在簽名的時候使用,才需要使用泛型方法。

四、泛型類

1、概述:

1、若類實例對象中多出要使用到同一泛型參數,即這些地方引用類型要保持同一個實際類型時,這時候就要采用泛型類型的方式進行定義,也就是類級別的泛型。

2、何時定義泛型類:當類中要操作的引用數據類型不確定時,在早期定義Object來完成擴展,而現在定義泛型。

3、泛型類定義的泛型,在整個類中都有效,如果被方法調用,那么泛型類的對象要明確需要操作的具體類型后,所有要操作的類就已經固定了。

4、類級別的泛型是根據引用該類名時指定的類型信息來參數化類型變量的。

2、語法格式:

1、定義

public class GenerDao1<T>{  
    private T field;  
    public void save(T obj){}  
    public T getByteId(int Id){}  
} 

2、舉例:
擴展:Dao:Data Access Object,數據訪問對象。
對其操作:crud即增上刪改查
c:creat,創建、增加;
r:read,讀取、查詢;
u:update,更新、修改
d:delete,刪除。

對javaEE的理解:13種技術。簡單說就是對數據庫的增刪改查。
寫Dao類有五個基本方法:增刪改查,其中查包含查單個和對同類型集合的查詢,如同性別或同地區的集合獲取。

package cn.itcast.text2;  
import java.util.*;  
public class GenerticDao<T> {  
    public static <E> void staMethod(E e){}  
    public void add(T obj){}  
    public boolean delete(T obj){  
        return true;  
    }  
    public boolean delete(int id){  
        return true;  
    }  
    public T update(T obj){  
        return null;  
    }  
    public T findByUserName(String name){  
        return null;  
    }  
    public Set<T> findByPlace(String place){  
        Set<T> set = new TreeSet<T>();  
        //....  
        return set;  
    }  
}

3、注意:

1、在對泛型進行參數化時,類型參數的實例必須是引用類型,不能是基本類型。

2、當一個變量被聲明為參數時,只能被實例變量和方法調用(還有內嵌類型),而不能被靜態變量和靜態方法調用,因為靜態成員是被所有參數化的類共享的,所以靜態成員不應該有類級別的類型參數。

總結:

對泛型的定義:
第一、定義泛型:當又不確定的類型需要傳入到集合中,需要定義泛型
第二、定義泛型類:如果類型確定后,所操作的方法都是屬于此類型,則定義泛型類
第三、定義泛型方法:如果定義的方法確定了,里面所操作的類型不確定,則定義泛型方法

示例:

//測試    
class GenerticTest  {    
    public static void main(String[] args) {    
        //創建泛型類對象    
        GenClass<Worker> g = new GenClass<Worker> ();    
        g.setTT(new Worker());    
        Worker w =  g.getTT();    
        g.showC(w);    
        System.out.println("----------------------");    
            //泛型方法測試    
            GenMethod<String> g1 = new GenMethod<String>();    
            GenMethod.showS("SSS");    
            g1.show("sesf");    
            g1.print("heheh");    
            g1.printY(new Integer(5));    
            System.out.println("------------------------");    
            //泛型接口測試    
            GenInter g2 = new GenInter();    
            g2.show("haha");    
            System.out.println("Hello World!");    
            GenImpl<Integer> g3 = new GenImpl<Integer>();    
            g3.show(new Integer(95));    
        }    
    }    
    //泛型類    
    class GenClass<TT>  {    
        //定義私有屬性    
        private TT t;    
        //定義公共設置方法,設置屬性    
        public void setTT(TT t) {    
            this.t = t;    
        }    
        //定義公共訪問方法,訪問屬性    
        public TT getTT() {    
            return t;    
        }    
        //定義方法    
        public void showC(TT t) {    
            System.out.println("GenClass show:" + t);    
        }    
    }    
    //創建Worker類,作為類型傳入泛型類中    
    class Worker {}    
    //泛型方法    
    class GenMethod<T> {    
        //靜態的泛型方法    
        public static <S> void showS(S s) {  
            System.out.println("static show:" + s);    
        }    
        //非靜態泛型方法    
        public void show(T t) {    
            System.out.println("未指定T show:" + t);    
        }    
        public void print(T t) {    
            System.out.println("指定T print:" + t);    
        }    
        //指定接受其他類型的泛型方法    
        public <Y> void printY(Y y) {    
            System.out.println("和類指定的不同,為Y print:" + y);    
        }    
     }    
    //泛型接口    
        
    interface Inter<T> {    
        void show(T t);    
    }    
    //一般類實現泛型接口    
    class GenInter implements Inter<String> {    
        public void show(String s) {    
            System.out.println("接口 show:" + s);    
        }    
    }    
    //泛型類實現泛型接口    
    class GenImpl<T> implements Inter<T> {    
        public void show(T t) {    
            System.out.println("類接收類型不確定的實現接口 show:" + t);    
    }

五、參數的類型推斷

1、概述:

1、定義:編譯器判斷泛型方法的實際參數的過程,稱之為類型推斷。

2、類型推斷是相對于直覺推斷的,其實現方法是一種非常復雜的過程。

2、類型推斷的具體規則:

根據調用泛型方法時,實際傳遞的參數類型或返回值的類型來推斷。

1、當某個類型變量只在整個參數列表中的所有參數和返回值中的一處被應用了,那么根據調用方法時,該處的實際應用類型來確定,這很容易憑著感覺推斷出來,即直接根據調用方法時,傳遞的參數類型或返回值來決定泛型參數的類型,如:

swap(newString[3],1,2)

--->

static <E> void swap(E[] a, inti, int j);

2、當某個類型變量在某個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時,這多處的實際應用類型都對應同一種類型來表示,這很容易憑感覺推斷出來:

add(3,5)

--->

static<T> T add(T a,T b);

3、若對應了不同類型,且沒有使用返回值,這是取多個參數中的最大交集類型,如下面的對應類型Number,編譯沒問題,但是運行會出錯:

fill(new Integer[3],3.5f)

--->

static<T> void fill(T[] a,T v);

4、若對應了不同類型,且使用了返回值,這時候優先考慮返回值類型,如下面語句實際對應的類型就是Integer了,編譯將報錯,將變量x類型改為float,對此eclipse報錯提示,接著再將變量x類型改為Number,則就沒了錯誤:

int x = add(3,3.5f)

--->

static<T> T add(T a,T b);

5、參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型為Object,編譯沒問題,而第二種情況則會根據參數化的Vector類實例將類型變量直接確定為String類型,編譯將出現問題:

copy(newInteger[5],new String[5]);

--->

static<T> T copy(T[] a,T[] b);

六、擴展

--> 反射獲得泛型的實際類型參數
舉例說明:

package cn.itcast.text2;  
  
import java.lang.reflect.*;  
import java.sql.Date;  
import java.util.*;  
import cn.itcast.text1.ReflectPoint;  
  
public class GenerticTest {  
    public static void main(String[] args) throws Exception {  
        // TODO Auto-generated method stub  
        Object obj = "abc";  
        String str = autoContor(obj);  
          
        GenerticDao<ReflectPoint> gd = new GenerticDao<ReflectPoint>();  
        gd.add(new ReflectPoint(3,5));  
        //通過獲得方法本身的方法  
        Method applyMethod = GenerticTest.class.getMethod("applyVector", Vector.class);  
        //通過方法的獲取泛型參數的方法得到原始參數類型的集合  
        Type[] types = applyMethod.getGenericParameterTypes();  
        //將參數類型轉換為參數化類型  
        ParameterizedType pType = (ParameterizedType)types[0];  
        //得到原始類型  
        System.out.println(pType.getRawType());  
        //得到實際參數類型  
        System.out.println(pType.getActualTypeArguments()[0]);  
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • object 變量可指向任何類的實例,這讓你能夠創建可對任何數據類型進程處理的類。然而,這種方法存在幾個嚴重的問題...
    CarlDonitz閱讀 934評論 0 5
  • 在經過一次沒有準備的面試后,發現自己雖然寫了兩年的android代碼,基礎知識卻忘的差不多了。這是程序員的大忌,沒...
    猿來如癡閱讀 2,872評論 3 10
  • 有時候覺得自己的朋友圈太多了,然后頻繁地出現在別人的視線里很不安全,就刪了更多,結果,現在有點后悔,刪了太多,連回...
    ShAvIn閱讀 177評論 0 0
  • 大腦極度缺氧,眼前一片漆黑,來不及回想,便被帶到了同學家的車上。我和她,三年同學,一年同桌,初三分班,她是年級第三...
    瀚冰閱讀 762評論 0 0