基本概念
- 通常情況下集合中可以存放不同類型的對象,是因?yàn)閷⑺袑ο蠖伎醋鯫bject類型放入的,因此從集合中取出元素時也是Object類型,為了表達(dá)該元素真實(shí)的數(shù)據(jù)類型,則需要強(qiáng)制類型轉(zhuǎn)換,而強(qiáng)制類型轉(zhuǎn)換可能會引發(fā)類型轉(zhuǎn)換異常。
例子:
public class ListTest {
public static void main(String[] args) {
List list = new LinkedList();
list.add(0, 1);
list.add(1, true);
list.add(2, "String");
String s = (String)list.get(2);
System.out.println(s);
}
}
- 為了避免上述錯誤的發(fā)生,從Java5開始增加泛型機(jī)制,也就是在集合名稱的右側(cè)使用<數(shù)據(jù)類型>的方式來明確要求該集合中可以存放的元素類型,若放入其它類型的元素則編譯報(bào)錯。
- 泛型只在編譯時期有效,在運(yùn)行時期不區(qū)分是什么類型。
public class ListGenericityTest {
public static void main(String[] args) {
// 1.準(zhǔn)備一個支持泛型機(jī)制的List集合,明確要求集合中的元素是String類型
List<String> lt1 = new LinkedList<>();
// 2.向集合中添加元素并打印
lt1.add("one");
System.out.println("lt1 = " + lt1); // one
// lt1.add(2); Error
// 3.獲取集合中的元素并打印
String s = lt1.get(0);
System.out.println("獲取到的元素是:" + s); // one
System.out.println("============================================================");
// 4.準(zhǔn)備一個支持Integer類型的List集合
List<Integer> lt2 = new LinkedList<>();
lt2.add(1);
lt2.add(2);
// lt2.add("3"); Error
System.out.println("lt2 = " + lt2); // [1, 2]
Integer integer = lt2.get(0);
System.out.println("獲取到的元素是:" + integer); // 1
System.out.println("============================================================");
// Java7開始的新特性:菱形特性,也就是后面<>中的數(shù)據(jù)類型可以省略
List<Double> lt3 = new LinkedList<>();
// 筆試考點(diǎn)
// 視圖將lt1的數(shù)值賦值給lt3,也就是覆蓋lt3中原來的數(shù)據(jù),結(jié)果會編譯報(bào)錯
// 報(bào)錯原因是集合中支持的類型不同
// lt3 = lt1; Error
}
}
結(jié)果為:
lt1 = [one]
獲取到的元素是:one
============================================================
lt2 = [1, 2]
獲取到的元素是:1
============================================================
底層原理
- 泛型的本質(zhì)就是參數(shù)化類型,也就是讓數(shù)據(jù)類型作為參數(shù)傳遞,其中E相當(dāng)于形式參數(shù)負(fù)責(zé)占位,而使用集合時<>中的數(shù)據(jù)類型相當(dāng)于實(shí)際參數(shù),用于給形式參數(shù)E進(jìn)行初始化,從而使得集合中所有的E被實(shí)際參數(shù)替換,由于實(shí)際參數(shù)可以傳遞各種各樣廣泛的數(shù)據(jù)類型,因此得名為泛型。
- 如:
| 其中i叫做形式參數(shù),負(fù)責(zé)占位 | 其中E叫做形式參數(shù),負(fù)責(zé)占位 |
|---|---|
| int i = 10;int i = 20; | E = String; E = Integer; |
| public static void show(int i) {...} | public interface List<E> {...} |
| 其中10叫做實(shí)際參數(shù),負(fù)責(zé)給形式參數(shù)初始化 | 其中String叫做實(shí)際參數(shù) |
|---|---|
| show(10); | List<String> lt1 = ...; |
| show(20); | List<String> lt2 = ...; |
自定義泛型接口
- 泛型接口和普通接口的區(qū)別就是后面添加了類型參數(shù)列表,可以有多個類型參數(shù),如:<E, T, .. >等。
自定義泛型類
- 泛型類和普通類的區(qū)別就是類名后面添加了類型參數(shù)列表,可以有多個類型參數(shù),如:<E, T, .. >等。
- 實(shí)例化泛型類時應(yīng)該指定具體的數(shù)據(jù)類型,并且是引用數(shù)據(jù)類型而不是基本數(shù)據(jù)類型。
- 父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型類型。
- 子類必須是“富二代”,子類除了指定或保留父類的泛型,還可以增加自己的泛型。
例子:
先定義泛型類:
/**
* 自定義泛型類Person,其中T相當(dāng)于形式參數(shù)負(fù)責(zé)占位,具體數(shù)值由實(shí)參決定
* @param <T> 看做是一種名字為T的數(shù)據(jù)類型即可
*/
public class Person<T> {
private String name;
private int age;
private T gender;
public Person() {
}
public Person(String name, int age, T gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public T getGender() {
return gender;
}
public void setGender(T gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", gender=" + gender +
'}';
}
}
測試類:
public class PersonTest {
public static void main(String[] args) {
// 1.聲明Person類型的引用指向Person類型的對象
Person p1 = new Person("zhangfei", 30, "男");
// 2.打印對象的特征
System.out.println(p1);
System.out.println("============================================================");
// 3.在創(chuàng)建對象的同時指定數(shù)據(jù)類型,用于給T進(jìn)行初始化
Person<String> p2 = new Person<>();
p2.setGender("女");
System.out.println(p2);
// 4.使用Boolean了類型作為性別的類型
Person<Boolean> p3 = new Person<>();
p3.setGender(true);
System.out.println(p3);
}
}
結(jié)果為:
Person{name='zhangfei', age=30, gender=男}
============================================================
Person{name='null', age=0, gender=女}
Person{name='null', age=0, gender=true}
泛型類被繼承時的處理方式
子類繼承是有四種繼承方式的
定義泛型類的子類:
public class SubPerson extends Person{// 不保留泛型而且沒有指定類型,此時Person類中的T默認(rèn)為是Object類型 擦除
}
測試會發(fā)現(xiàn)子類調(diào)用set方法時需要傳入的參數(shù)為Object類型
public class SubPerson extends Person<String>{// 不保留類型但是指定了泛型的類型,此時Person類型中的T被指定為String類型
}
此時發(fā)現(xiàn)子類調(diào)用set方法時需要傳入的參數(shù)為String類型
public class SubPerson<T> extends Person<T> {// 保留父類的泛型,可以在構(gòu)造對象時來指定T的類型
}

public class SubPerson<T, T1> extends Person<T> {// 保留父類的泛型,同時在子類中增加新的泛型
}

自定義泛型方法
- 泛型方法就是我們輸入?yún)?shù)的時候,輸入的是泛型參數(shù),而不是具體的參數(shù)。我們在調(diào)用這個泛型 方法的時需要對泛型參數(shù)進(jìn)行實(shí)例化。
- 泛型方法的格式:
[訪問權(quán)限] <泛型> 返回值類型 方法名([泛型標(biāo)識 參數(shù)名稱]) { 方法體; } - 在靜態(tài)方法中使用泛型參數(shù)的時候,需要我們把靜態(tài)方法定義為泛型方法。
像這種不是泛型方法:
public T getGender() {
return gender;
}
像這種才是泛型方法(在Person類中新定義的方法):
// 自定義方法實(shí)現(xiàn)將參數(shù)指定數(shù)組中的所有元素打印出來
public <T1> void printArray(T1[] arr) {
for (T1 t : arr) {
System.out.println("t = " + t);
}
}
然后調(diào)用泛型方法:
// 5.調(diào)用泛型方法進(jìn)行測試
Integer[] arr = {11, 22, 33};
Person.printArray(arr);
結(jié)果為:
t = 11
t = 22
t = 33
一種特殊情況:
// 不是泛型方法,該方法不能使用static關(guān)鍵字修飾,因?yàn)樵摲椒ㄖ械腡需要在new對象時才能明確類型
public /*static*/ T getGender() {
return gender;
}
泛型在繼承上的體現(xiàn)
- 如果B是A的一個子類或子接口,而G是具有泛型聲明的類或接口,則G<B>并不是G<A>的子類型!比如:String是Object的子類,但是List<String>并不是List<Object>的子類。
通配符的使用
- 有時候我們希望傳入的類型在一個指定的范圍內(nèi),此時就可以使用泛型通配符了。
- 如:之前傳入的類型要求為Integer類型,但是后來業(yè)務(wù)需要Integer的父類Number類也可以傳入。
- 泛型中有三種通配符形式:
<?> 無限制通配符:表示我們可以傳入任意類型的參數(shù)。
<? extends E> 表示類型的上界是E,只能是E或者是E的子類。
<? super E> 表示類型的下界是E,只能是E或者是E的父類。
例子:
先定義父類
public class Animal {
}
再定義子類,繼承父類
public class Dog extends Animal{
}
測試類
public class GenericTest {
public static void main(String[] args) {
// 1.聲明兩個List類型的集合進(jìn)行測試
List<Animal> lt1 = new LinkedList<>();
List<Dog> lt2 = new LinkedList<>();
// 試圖將lt2的數(shù)值賦值給lt1,也就是發(fā)生List<Dog>類型向List<Animal>類型的轉(zhuǎn)換
//lt1 = lt2; Error: 類型之間不具備父子類關(guān)系
System.out.println("============================================================");
// 2.使用通配符作為泛型類型的公共父類
List<?> lt3 = new LinkedList<>();
// 沒報(bào)錯,說明可以發(fā)生List<Animal>類型到List<?>類型的轉(zhuǎn)換
lt3 = lt1;
// 可以發(fā)生List<Dog>類型到List<?>類型的轉(zhuǎn)換
lt3 = lt2;
// 向公共父類中添加元素和獲取元素
// lt3.add(new Animal()); Error: 不能存放Animal類型的對象
// lt3.add(new Dog()); Error: 不能存放Dog類型的對象,不支持元素的添加操作,因?yàn)?可以是任意類型
// 是可以的,支持元素的獲取操作,全部當(dāng)做Object類型來處理
Object o = lt3.get(0);
System.out.println("============================================================");
// 3.使用有限制的通配符進(jìn)行使用
List<? extends Animal> lt4 = new LinkedList<>();
// 不支持元素的添加操作,因?yàn)?可以是Animal以及任意Animal的子類類型
// 如果?為Dog類型,那么添加new Animal()對象就是錯誤的
//lt4.add(new Animal());
//lt4.add(new Dog());
//lt4.add(new Object());
// 獲取元素是可以的,因?yàn)槿〕鰜淼闹荒苁茿nimal或者Animal的子類
Animal animal = lt4.get(0);
System.out.println("============================================================");
List<? super Animal> lt5 = new LinkedList<>();
lt5.add(new Animal());
lt5.add(new Dog());
//lt5.add(new Object()); Error: 超過了Animal類型的范圍
// 只有Object才能通用的處理
Object object = lt5.get(0);
}
}