泛型原理、概念、使用
泛型簡介/原理
1、從JDK1.4開始是沒有泛型的概念的,那怎麼做呢?如下列程式碼
List list = new ArrayList();
list.add("string");
list.add(11);
String name = (String)list.get(0);
Integer number = (Integer)list.get(1);
△這樣子做有什麼缺點呢
存的都是object,需要強轉,轉換時可能發生異常
2、JDK1.5以後的寫法
List<String> list = new ArrayList();
list. add("string");
list.add("string2");
String cnBlogs = list.get(0);
String myWebSite = list.get(1);
總結: 泛型就是將型別引數化,其在編譯時才確定具體的引數。在上面這個例子中,這個具體的型別就是 String。
可以看到我們在建立 List 集合的時候指定了 String 型別,這就意味著我們只能往 List 集合中存放 String 型別的資料。
而當我們指定泛型之後,我們去取出資料後就不再需要進行強制型別轉換了,這樣就減少了發生強制型別轉換的風險。
這就是Sun公司為了JAVA語言更嚴格安全。
3、泛型的原理:
能看出這段程式碼的結果是什麼嗎?
ArrayList<String> a = new ArrayList<String>();
ArrayList<Integer> b = new ArrayList<Integer>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2);
△答案:是true,泛型只存在於編譯階段,而不存在於執行階段。c1和c2獲取到的都是java.util.ArrayList
△擴充套件:上面我們只是說了泛型在集合中的使用方式,但其實泛型的應用範圍不僅僅只是集合,還包括類、方法、Map 介面等等
泛型的應用場景
泛型類
1、泛型類一般使用字母 T 作為泛型的標誌
public class GenericClass<T> {
private T object;
public T getObject() {
return object;
}
public void setObject(T object) {
this.object = object;
}
}
△擴充套件: MAP類中也有標誌 K-V
public class GenericMap<K, V> {
private K key;
private V value;
public void put(K key, V value) {
this.key = key;
this.value = value;
}
}
public static void main(String[] args) {
GenericMap<Integer, String> team = new GenericMap<>();
team.put(1, "YaoMin");
team.put(2, "Me");
GenericMap<String, Integer> score = new GenericMap<>();
score.put("YaoMin", 88);
score.put("Me", 80);
}
△擴充套件: 那麼像每次定義列舉型別,都是數值什麼的那麼我們怎麼定義中文描述
不會動的載入中...
泛型方法
1、泛型方法一般也用字母 T 作為泛型的標誌
public class GenericMethod {
public static <T> T getObject(Class<T> clz) throws InstantiationException, IllegalAccessException{
T t = clz.newInstance();
return t;
}
}
public static void main(String[] args) throws Exception{
GenericMethod genericMethod = getObject(GenericMethod.class);
System.out.println("Class:" + genericMethod.getClass().getName());
}
泛型集合 拉拉最頂上↑
泛型萬用字元
1、除了泛型類、泛型方法之外,泛型還有更加複雜的應用,如:
List<? extends Number> list = new ArrayList();
List<? super Number> list = new ArrayList();
△上面的 extends 和 super 關鍵字其實就是泛型的高階應用:泛型萬用字元。
△但在講泛型萬用字元之前,我們必須對編譯時型別和執行時型別有一個基本的瞭解,才能更好地理解萬用字元的使用。
public class Tree {}
public class Fruits extends Tree{}
public class Banana extends Fruits{}
public class Plate<T> {
T t;
public T get(){
return t;
}
public void set(T t){
this.t=t;
}
}
2、來看看泛型的劣優
(1)Error 泛型不支援向上轉型
/* Plate<Fruits> banana=new Plate<Banana>();*/
(2) ? extends 用法
Plate<? extends Fruits> bananaExtens=new Plate<Banana>();
/*//Error不允許寫入 不管是父類還是子類
bananaExtens.set(new Fruits());*/
/**就這一步,未執行時根本不知道你例項的那個子類,那 JVM 就乾脆什麼都不給放,避免出錯。**/
//只允許讀取
bananaExtens.get();
(3) ? super 用法 宣告子類,其例項物件可以是父類或是父類以上的級別,例Object也是可以的
Plate<? super Banana> bananaFruSuper=new Plate<Fruits>();
Plate<? super Banana> bananaTreSuper=new Plate<Tree>();
//可以寫入,但是隻能寫入宣告物件中同級或者以下的類 子類,子子類
//就這步,為執行時JVM既然不能知道例項物件是哪個,但是JVM能確定Banana的子類能轉換成Banana,但是父類卻不能轉換()
bananaTreSuper.set(new Banana());
/*//都是高於宣告物件級別
bananaTreSuper.set(new Tree());
bananaTreSuper.set(new Fruits());*/
/*//Error也是高於物件級別
bananaFruSuper.set(new Fruits());*/
/* //Error 能取但是,只能用Object 來接收
Banana b= bananaFruSuper.get();
Fruits f= bananaFruSuper.get();
*/
Object o= bananaFruSuper.get();
PESC原則
在泛型程式設計時,使用部分限定的形參時,<? super T>和<? extends T>的使用場景容易混淆,PECS原則可以幫助我們很好記住它們:
生產者(Producer)使用extends,消費者(Consumer)使用super。
留下一段程式碼加深印象(來自JDK 8 Collections.copy()原始碼)
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
參考於:
https://www.cnblogs.com/chanshuyi/p/deep_insight_java_generic.html