28-泛型--概述+擦除&補償+在集合中的應用+泛型類+泛型方法+泛型介面
一、泛型概述
1、泛型:對要操作的資料型別進行指定。是JDK1.5出現的安全機制。泛型是給編譯器使用的技術,用在編譯時期,提高了編譯的安全性(確保型別安全)
2、向集合中新增元素,public boolean add(E e); 任何型別都可以接收(新增的元素被提升為Object型別)。通常,會將元素取出,對元素進行特有的操作。為了避免後期出現安全隱患,在定義容器時,就需要明確容器中儲存元素的型別。而迭代器是從集合獲取到的,集合明確了其中的元素型別,迭代器取出的元素型別就是明確的,就沒有必要再做強轉動作了(編譯時會檢測型別)
注:迭代器的泛型和獲取迭代器物件集合的泛型一致
//定義容器時,明確容器中儲存元素的型別 ArrayList<String> list = new ArrayList<String>(); //新增元素時,型別匹配的才可以存入,否則編譯報錯 list.add("abc"); // list.add(true); //型別不匹配,編譯報錯 //迭代器取出的元素型別明確 for (Iterator<String> it = list.iterator(); it.hasNext();){ //不用再做強轉動作 String value = it.next(); System.out.println(value); }
3、泛型的好處
(1)將執行時期的問題ClassCastException轉到了編譯時期
(2)避免了強制轉換的麻煩
4、何時使用<> ?
當操作的引用資料型別不確定的時候,就使用<>,將要操作的引用資料型別傳入即可。<>是一個用於接收具體引用資料型別的引數範圍。在程式中,只要用到了帶有<>的類或者介面,就要明確傳入的具體引用資料型別
注:<>中接收的都是引用資料型別(類/介面),要麼是類名,要麼是介面名。只要出現<>,就要向<>中傳入型別。泛型中不能傳基本資料型別
二、泛型的擦除&補償
1、泛型的擦除:執行時,會將泛型去掉。即 生成的 .class 檔案是不帶泛型的
注:用反射機制可以驗證集合中的泛型,是為了防止錯誤輸入的,只在編譯階段有效
2、執行時為何要擦除泛型?
為了相容執行時的類載入器。執行時,JVM會去啟動類載入器。但以前的類載入器沒有使用泛型,如果在類檔案中加入泛型,以前的類載入器需要升級。希望以前的類載入器還可以使用,且編譯時已經對型別進行了檢查,型別已經統一,是安全的,執行時就可以把泛型去掉了。所以,只在編譯器中加入了泛型機制
注:類載入器:專門用於讀取類檔案、解析類檔案,並將類檔案載入進記憶體的程式
3、泛型的補償:在執行時,通過獲取元素的型別進行轉換動作,使用者無需再進行強制轉換
4、編譯器只做檢查,之後<>中的型別被擦除,add()中傳入的型別還是Object。取出時,為了避免強轉時出現問題,在類載入器的基礎上編寫了一個補償程式。先獲取指定元素的型別(通過getClass()方法獲取物件所屬的類檔案,就是<>中被擦除的型別),再對其進行型別轉換
三、泛型在集合中的應用
1、API中,只要類或介面後有<>的,使用時都要在後面用<>明確元素型別
2、在定義容器時,就要明確容器中要儲存的元素型別
/**
* 實現Comparable<Person>介面,使用泛型,該Comparable介面中只能傳入Person型別的物件
*/
public class Person implements Comparable<Person> {
private int age;
private String name;
//......此處省略建構函式和get()、set()方法
/**
* 覆蓋Comparable<T>介面中的compareTo(T o)方法,引數型別是Person.因為 implements Comparable<Person> 限制了型別的使用
*
* @param p
* @return
*/
@Override
public int compareTo(Person p) {
//不用再做強轉動作,因為只有匹配的型別才能傳進來
int temp = this.age - p.age;
return temp == 0 ? this.name.compareTo(p.name) : temp;
}
/**
* 覆蓋Object類中的equals(Object obj)方法,引數是Object型別(固定型別),不能修改。因為父類中沒有定義過泛型
* @param obj
* @return
*/
@Override
public boolean equals(Object obj){
//......
}
}
3、泛型在集合框架中應用的最多
4、泛型中,當引用資料型別不確定時,用泛型表示。泛型中不能傳基本資料型別
5、一般情況下,寫泛型要具備的特點是:左右兩邊的泛型要一致
四、泛型類
1、JDK1.5後,使用泛型來接收類中要操作的引用資料型別 -- 泛型類(自定義泛型類)
2、何時使用?
當類中操作的引用資料型別不確定時,就使用泛型來表示
3、沒泛型用Object,但泛型這種替代方式比Object安全得多,只是書寫比Object麻煩一點,要寫<>
/**
* 存入時,用Object型別接收(向上轉型),什麼型別都可以存入 -- 提高擴充套件性
* 取出時,先強轉再使用,注意報錯:ClassCastException。所以,取出時需要做健壯性判斷
*/
public class Tool {
private Object object;
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
/**
* 在JDK1.5後,使用泛型來接收類中要操作的引用資料型別 -- 泛型類
* 何時使用?
* 當類中操作的引用資料型別不確定時,就使用泛型來表示
*/
public class Tool<QQ> {
private QQ q;
public QQ getObject() {
return q;
}
public void setObject(QQ object) {
this.q = object;
}
}
注:泛型類取出時不用做強轉動作,進一步驗證了:(1)泛型將執行時期的問題轉移到了編譯時期(2)避免了強轉的麻煩
五、泛型方法
1、在不明確型別的情況下,Object其實是一種具體型別。只不過它是所有類的父類,導致它可以有多型方式來接收
2、如果不明確型別,使用者往裡傳什麼型別,就操作什麼型別。這時可以定義泛型,叫做把泛型定義在方法上
eg:public <W> void show(W xxx){ ... }
public class Tool<QQ> {
private QQ q;
public QQ getObject() {
return q;
}
public void setObject(QQ object) {
this.q = object;
}
/**
* 工具類中指定什麼型別(Tool<QQ>),方法中就操作什麼型別(QQ) -- show()方法的引數的泛型跟著物件走
* 方法的引數用的也是泛型,使用的是類上定義的泛型
*/
public void show(QQ object) {
System.out.println("show: " + object);
}
/**
* 將泛型定義在方法上:自動找型別匹配,什麼都能傳入 -- 泛型是在方法上自定義的
* <W>是定義引數,W是使用引數
*/
public <W> void show1(W obj) {
System.out.println("泛型:show: " + obj); //等同與obj.toString()方法
}
}
3、當方法靜態時,不能訪問類上定義的泛型。如果靜態方法需要使用泛型,只能將泛型定義在方法上
注:自定義的泛型一定要寫在返回值型別前,修飾符後
public class Tool<QQ> {
private QQ q;
public QQ getObject() {
return q;
}
public void setObject(QQ object) {
this.q = object;
}
/**
* 靜態是不需要物件的,而泛型得需要物件來明確
* 當方法靜態時,不能訪問類上定義的泛型。如果靜態方法需要使用泛型,只能將泛型定義在方法上
*/
// public static void method(QQ xxx) {
// System.out.println("method: " + xxx);
// }
public static <W> void method(W xxx) {
System.out.println("method: " + xxx);
}
}
4、一旦使用了泛型,變數的型別不確定,就不能使用具體物件的方法。Object中的方法可以使用,因為無論傳入什麼型別,都是Object的子類物件,都具備著Object中的方法(Object中的方法是所有物件的通用方法)
public void print(QQ str) {
// System.out.println("print: " + str.length()); //報錯。使用了泛型,變數str的型別不確定,不能使用具體物件的方法
}
六、泛型介面
1、泛型介面:將泛型定義在介面上(將泛型定義在類上就叫泛型類)
2、定義泛型介面時,一般不會明確具體型別(無意義)
/**
* 泛型介面:定義時,一般不會明確具體型別
*/
interface Inter<T> {
public void show(T t);
}
3、泛型介面一般有兩種實現方式。使用哪一種,要看何時能明確型別
(1)實現介面時明確型別
/**
* 實現介面時,明確型別:Inter<T> --> Inter<String>
*/
class InterImpl implements Inter<String> {
@Override
public void show(String str){
System.out.println(str);
}
}
public class Test {
public static void main(String[] args) {
InterImpl in = new InterImpl();
in.show("abc");
}
}
(2)實現介面時型別尚不明確,繼續使用泛型。直到使用子類物件時,才明確型別
/**
* 實現介面時,型別尚不明確,繼續使用泛型:Inter<T> --> Inter<Q>
*/
class InterImpl<Q> implements Inter<Q> {
@Override
public void show(Q q){
System.out.println(q);
}
}
public class Test {
public static void main(String[] args) {
//使用子類物件時,才明確型別
InterImpl<Integer> in1 = new InterImpl<Integer>();
in1.show(1);
InterImpl<String> in2 = new InterImpl<String>();
in2.show("abc");
}
}