《編程思想》11.泛型

1 . 泛型的主要目的之一就是用來指定容器要持有什麼類型的對象。

2 . 簡單泛型
(1)有許多原因促成了泛型的出現,而最引人注目的一個原因就是爲了創造容器類。

public class Holder3<T> {
    private T a ;
    public Holder3(T a){
        this.a = a ;
    }
    public void set(T a) {
        this.a = a ;
    }
    public T get(){
        return  a;
    }
    
    public static void main(String[] args) {
       Holder3<Automobile>(new Automobile());
        Automobile a = h3.get() ;
    })
}

3 .元組:
(1) 僅一次方法調用就能返回多個對象
(2) 它是將一組對象直接打包存儲於其中的一個單一對象。這個容器對象允許讀取其中元素,但是不允許向其中存放新的對象。

public class TwoTuple<A ,B> {
    //二維元組 ,public外部可以隨意訪問,final無法改變數據
    //可以隨便使用對象,但無法修改數據
    //聲明爲final的元素便不能被再賦予其他值了
    public final A first ;
    
    public final B second ;
    
    public TwoTuple(A a,B b){
        first = a;
        second = b;
    }
    
    public String toString(){
        return "("+first+"."+second+")";
    }
}

4 . 泛型方法
(1)同樣可以在類中包含參數化方法,而這個方法所在的類可以是泛型類,也可以不是泛型類。是否擁有泛型方法,與其所在的類是否是泛型沒有關係。泛型方法使得該方法能夠獨立於類而產生變化。
(2)如果使用泛型方法可以取代將整個類泛型化,那麼就應該只使用泛型方法,因爲它可以使事情變得更加清楚明白。
(3)對於一個static的方法而言,無法訪問泛型類的類型參數,所以如果static方法需要使用泛型能力,就必須使其成爲泛型方法

public class GenericMethods {
    //要定義泛型方法,只需將泛型參數列表置於返回值之前
    public <T> void f(T x) {
        System.out.print(x.getClass().getName());
    }
    
    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
    }
}

5 .槓桿利用類型參數推斷

//問題:重複自己做過的事情
Map<Person,List<? extends Pet>> petPeople = new HashMap<Person,List<? extends Pet>>();
//工具類:類型參數推斷避免了重複的泛型參數列表 ,但代碼可讀性差
public class New {
  public static <K,V> Map<K,V> map(){
	  return new HashMap<K,V>();
  }
  
  public static <T> List<T> list(){
	  return new ArrayList<T>();
  }
  
  public static <T> LinkedList<T> lList(){
	  return new LinkedList<T>();
  }
  
  public static <T> Set<T> set(){
	  return new HashSet<T>();
  }
  
  public static <T> Queue<T> queue(){
	  return new LinkedList<T>();
  }
  
  public static void main(String[] args){
	  Map<String,List<String>> sls = New.map();
	  List<String> ls = New.list();
	  LinkedList<String> lls = New.lList();
	  Set<String> ss = New.set();
	  Queue<String> qs = New.queue();
  }
}

(1)類型推斷只對賦值操作有效,其他時候不起作用。如果你將一個泛型方法調用(例如New.map())作爲參數,傳遞給另一個方法,這時編譯器並不會執行類型推斷。例如:

public class TestOne { 
    static void f(Map<Person,List<? extends Pet>> petPeople) {};
	public static void main(String[] args) { 
		//f(New.map());  //無效
	} 
}

6.可變參數與泛型方法

public class TestOne {  
	public static <T> List<T> makeList(T... args){
		List<T> result = new ArrayList<T>();
		for(T item : args){
			result.add(item);
		}
		return result;
		
	}
	public static void main(String[] args) { 
		List<String> ls = makeList("A","B","C");
		System.out.println(ls);
	} 
}

7.構建複雜模型
(1)泛型的一個重要好處是能夠簡單而安全地創建複雜的模型

8.類型擦除
(1)在泛型代碼內部,無法獲得任何有關泛型參數類型的信息。任何具體的類型信息都被擦除了,你唯一知道的就是你在使用一個對象。
(2)例如:List 這樣的類型註解將被擦除爲List。而普通的類型變量在未指定邊界的情況下將被擦除爲Object。
(3)邊界處的動作:正是因爲有了擦除,泛型可以表示沒有任何意義的

public class ArrayMaker<T> { 
    //kind被存儲爲Class<T>,擦除意味着它實際將被存儲爲Class,沒有任何參數。
    //因此,當在使用它時,例如創建數組時,Array.newInstance()實際上並未擁有kind
    //所蘊含的類型信息,因此這不會產生具體的結果,所以必須轉型 
	private Class<T> kind ;
	
	public ArrayMaker(Class<T> kind){
		this.kind = kind ;
	}
	
	@SuppressWarnings("unchecked")
	T[] create(int size){
		return (T[])Array.newInstance(kind, size);
	}
	 
	public static void main(String[] args) {  
		ArrayMaker<String> stringMaker = new ArrayMaker<String>(String.class);
		String[] stringArray = stringMaker.create(9);
		System.out.println(Arrays.toString(stringArray));
	} 
}

(4)對於在泛型中創建數組,使用Array.newInstance()是推薦的方式。

9.擦除的補償
(1)擦除丟失了在泛型代碼中執行某些操作的能力。任何在運行時需要知道確切類型信息的操作都將無法工作:

public class Erased<T> {
private final int SIZE = 100 ;

//錯誤代碼
public static void f(Object arg){
	if(arg instanceof T ){};   //Error  因爲其類型信息已經被擦除了
	T var = new T();           //Errot
	T[] array = new T[SIZE];   //Errot
	T[] array = (T)new Object[SIZE] ; //Unckecked warning
}
}

有時必須通過引入類型標籤來對擦除進行補償。這意味着你需要顯式地傳遞你的類型的Class對象,以便你可以在類型表達式中使用它。

class Building{};

class House extends Building{};

public class Erased<T> {
	Class<T> kind;
	
	public  Erased(Class<T> kind){
		this.kind = kind ;
	}
	
	public boolean f(Object arg){
		return kind.isInstance(arg);
	}
	
	public static void main(String[] args){
		Erased<Building> ctt1 = new Erased<Building>(Building.class);
		System.out.println(ctt1.f(new Building()));   //true
		System.out.println(ctt1.f(new House()));      //true
	}
}

10.不能創建泛型數組,一般的解決方案是在任何想要創建泛型數組的地方都使用ArrayList

public class ListOfGenerics<T> {
	private List<T> array = new ArrayList<T>();
	
	public void add(T item){
		array.add(item);
	}
	
	public T get(int index){
		return array.get(index);
	}
}

11.邊界
(1)Java泛型重用了extends關鍵字,能夠將這個參數限制爲某個類型子集

class Colored2<T extends Hascolor> extends HoldItem<T> {}

12.通配符

13.問題
(1).任何基本類型都不能作爲類型參數,因此不能創建ArrayList< int >之類的東西,解決之道是使用基本類型的包裝類以及Java SE5的自動包裝機制。如果創建一個ArrayList< Integer > ,並將基本類型int應用於這個容器。那麼你將發現自動包裝機制將自動地實現int到Interger的雙向轉換。

(2)一個類不能實現同一個泛型接口的兩種變體,由於擦除的原因,這兩個變體會成爲相同的接口。

interface Payable<T> {};

class Employee implements Payable<Employee> {};

class Hourly extends Employee implements Payable<Hourly> {};

Hourly不能編譯,因爲擦除會將Payable和Payable< Hourly > 簡化爲相同的類Payable.

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