黑馬程序員 高新技術---> 泛型

-----------android培訓java培訓、java學習型技術博客、期待與您交流! ------------


第一節 泛型概述--->JDK1.5新特性

一、泛型的出現:

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

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

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

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

二、好處:

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

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

三、泛型格式:

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

     如:TreeSet<String>     -----> 來定義要存入集合中的元素指定爲String類型

四、泛型定義中的術語:

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

1ArrayList<E>整個稱爲泛型類型

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

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

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

5ArrayList<Integer>中的<>稱爲typeof

6ArrayList稱爲原始類型

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

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

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

 六、關於參數化類型的幾點說明:

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

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());  
        }  
    }  
}  

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

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

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

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

2super 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);  
    }  
}  

第三節  泛型方法

一、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、特殊之處:靜態方法不可以訪問類上定義的泛型,如果靜態方法操作的引用數據類型不確定,可以將泛型定義在方法上。

二、泛型方法的特點:

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;
		}
	}

三、這個T?有什麼區別呢?

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

2?爲通配符,也可以接收任意類型但是不可以定義變量。

但是這樣定義,雖然提高了擴展性,可還是有一個侷限性,就是不能使用其他類對象的特有方法。

3、總結:

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

 

第四節 泛型類

一、概述:

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

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

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

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

二、語法格式:

1、定義

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

2、舉例:

擴展:DaoData Access Object,數據訪問對象。

對其操作:crud即增上刪改查

ccreat,創建、增加;     rread,讀取、查詢;

uupdate,更新、修改    ddelete,刪除。

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;
	}
}

三、注意:

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、定義:編譯器判斷泛型方法的實際參數的過程,稱之爲類型推斷。

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]);
	}





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