01、泛型是什麼?
A:泛型其實就是在定義類、接口、方法的時候不侷限地指定某一種特定類型,而讓類、接口、方法的調用者來決定具體使用哪一種類型的參數。
B:比如一個水杯生產的時候不用指定它將來幹什麼?而是由將來的使用者決定放入什麼。
C:其實就是一句話:我是一個泛型隊列,狗可以站進來,貓也可以站進來,但最好不要既站貓,又站狗!
別讓貓狗站在隊列裏
注:在Java中,經常用T、E、K、V等形式的參數來表示泛型參數。
T:代表一般的任何類。
E:代表 Element 的意思,或者 Exception 異常的意思。
K:代表 Key 的意思。
V:代表 Value 的意思,通常與 K 一起配合使用。
02、不使用泛型,放入貓狗
package fanxing;
import java.util.HashMap;
import java.util.Map;
public class MaoGou {
class Dog {
}
class Cat {
}
public static void main(String[] args) {
MaoGou maoGou = new MaoGou();
Map map = new HashMap();
map.put("dog", maoGou.new Dog());
map.put("cat", maoGou.new Cat());
Cat cat = (Cat) map.get("dog");
Dog dog = (Dog) map.get("cat");
System.out.println(dog + "dog");
System.out.println(cat + "cat");
}
}
Exception in thread "main" java.lang.ClassCastException:
fanxing.MaoGou$Dog cannot be cast to fanxing.MaoGou$Cat
編譯階段沒有問題,運行時報錯,就是放入的是貓,不能取出的是狗
2.1那如何解決上述問題呢?用泛型!!!
直接在map定義的時候指定要放入的對象
package fanxing;
import java.util.HashMap;
import java.util.Map;
public class MaoGou {
class Dog {
}
class Cat {
}
public static void main(String[] args) {
MaoGou maoGou = new MaoGou();
Map map = new HashMap();
// map.put("dog", maoGou.new Dog());
// map.put("cat", maoGou.new Cat());
// Cat cat = (Cat) map.get("dog");
// Dog dog = (Dog) map.get("cat");
// System.out.println(dog + "dog");
// System.out.println(cat + "cat");
Map<String, Cat> map2 = new HashMap<>();
map2.put("cat2", maoGou.new Cat());
Cat cat2 = map2.get("cat2");
System.out.println("cat2 " + cat2);
}
}
03、泛型的存在階段是編譯階段還是運行階段?
有人說,Java的泛型做的只是表面功夫——泛型信息存在於編譯階段(狗隊在編譯時不允許站貓),運行階段就消失了(運行時的隊列裏沒有貓的信息,連狗的信息也沒有)——這種現象被稱爲“類型擦除”。
package fanxing;
import java.util.HashMap;
import java.util.Map;
public class MaoGou {
class Dog {
}
class Cat {
}
public static void main(String[] args) {
MaoGou maoGou = new MaoGou();
Map map = new HashMap();
Map<String, Cat> catMap = new HashMap<>();
Map<String, Dog> dogMap = new HashMap<>();
System.out.println(catMap.getClass());
System.out.println(dogMap.getClass());
}
}
輸出結果:
class java.util.HashMap
class java.util.HashMap
分析:
也就是說,Java代碼在運行的時候並不知道catMap的鍵位上放的是Cat,dogMap的鍵位上放的是Dog。
即滿足了上述說的《類型擦除》
3.1 那麼,試着想一些可怕的事情:
既然運行時泛型的信息被擦除了,而反射機制是在運行時確定類型信息的,那麼利用反射機制,是不是就能夠在鍵位爲Cat的Map上放一隻Dog呢?
package fanxing;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class MaoGou {
class Dog { }
class Cat { }
public static void main(String[] args) {
MaoGou maoGou = new MaoGou();
Map map = new HashMap();
Map<String, Cat> catMap = new HashMap<>();
try {
Method method =catMap.getClass().getDeclaredMethod("put",Object.class,Object.class);
method.invoke(catMap,"dog",maoGou.new Dog());
System.out.println(catMap);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
輸出結果:{dog=fanxing.MaoGou$Dog@3f99bd52}
看到沒?我們竟然在鍵位爲Cat的Map上放了一隻Dog!注:Java的設計者在JDK 1.5時才引入了泛型,但爲了照顧以前設計上的缺陷,同時兼容非泛型的代碼,
不得不做出了一個折中的策略:編譯時對泛型要求嚴格,運行時卻把泛型擦除了——要兼容以前的版本,
還要升級擴展新的功能,真的很不容易!
04、自定義泛型
package fanxing;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class PetHouse<T> {
private List<T> list;
public PetHouse() {
}
public void add(T item) {
list.add(item);
}
public T get() {
return list.get(0);
}
public static void addTest(List<?> list) {
Object o = new Object();
// list.add(o); // 編譯報錯
// list.add(1); // 編譯報錯
// list.add("ABC"); // 編譯報錯
list.add(null);
}
}
05、泛型通配符
? 通配符類型 無邊界的通配符(Unbounded Wildcards), 就是<?>, 比如List<?>
無邊界的通配符的主要作用就是讓泛型能夠接受未知類型的數據.
<? extends T> 表示類型的上界,表示參數化類型的可能是T 或是 T的子類
<? super T> 表示類型下界(Java Core中叫超類型限定),
表示參數化類型是此類型的超類型(父類型),直至Object
注意: 你可以爲一個泛型指定上邊界或下邊界, 但是不能同時指定上下邊界.
上界限定符接受 extends 後面類的本身與其子類, 下界限定符接受 super 後面類的本身與其父類。無限定通配符接受任何類。
5.1 ?不能使用add()方法,NULL除外
public static void addTest(List<?> list) {
Object o = new Object();
// list.add(o); // 編譯報錯
// list.add(1); // 編譯報錯
// list.add("ABC"); // 編譯報錯
list.add(null);
}
所以“?”聲明的集合,不能往此集合中添加元素,所以它只能作爲生產者(亦即它只能被迭代),如下:
public static void main(String[] args) {
List<?> names = new ArrayList<String>() {
{
for (int i = 0; i < 10; i++) {
add("A" + i);
}
}
};
System.out.println(names.toString());
// 只能以Object迭代元素
for (Object name : names) {
System.out.println(name);
}
}
5.2 “? extends T”也不能添加元素 只能存指定類型或者其子類
public static void main(String[] args) {
List<? extends String> names = new ArrayList<String>() {
{
for (int i = 0; i < 10; i++) {
add("A" + i);
}
}
};
System.out.println(names.toString());
// 相比?更能準確定位元素類型
for (String name : names) {
System.out.println(name);
}
}
因爲Integer Double Long都是Number的子類
List<? extends Number> numbers =new ArrayList<Number>(){
{
add(1);
add(1.2);
add((long)1);
//add("2"); 報錯
}
};