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.