Java 1.5版本中增加了泛型喉誊。
為什么需要引入泛型?
在引入泛型之前,讀取集合中的每一個元素都需要進行轉(zhuǎn)換,如果在集合中插入了錯誤類型的對象妒蔚,那么就會在程序運行時報類型轉(zhuǎn)換的錯誤。在引入泛型后我們可以告訴編譯器需要向集合中插入的元素類型月弛,編譯器會在插入元素時進行自動轉(zhuǎn)換肴盏,并在編譯時報告往集合中插入錯誤類型的代碼。
在Java代碼中還可以繼續(xù)使用原生態(tài)類型例如List等的原因是因為向后兼容帽衙,讓以前未使用泛型的代碼保存合法菜皂,并且能夠與使用泛型的代碼進行交互。在新代碼中不推薦使用List等原生態(tài)類型厉萝。
List 和 List<Object> 區(qū)別恍飘?
兩種集合都允許插入任何類型的對象。簡單說List逃避了編譯器的類型檢查谴垫,而List<Object>則明確告知編譯器其可以保存任何類型的對象章母。
泛型有子類型化的規(guī)則,List<String> 是原生態(tài)類型List的子類型而不是List<Object>的子類型翩剪,所有可以將List<String> 類型的對象傳遞給List類型的參數(shù)但不能傳遞給List<Object>類型的參數(shù):
package template;
import java.util.ArrayList;
import java.util.List;
/**
* @author: zhouzhaoping
* @description:
* @date: 2019-08-25
*/
public class Test1 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
test1(list);
test2(list); // 報錯
}
public static void test1(List list) {
for (Object o : list) {
System.out.println("test1:" + o.toString());
}
}
public static void test2(List<Object> list) {
for (Object o : list) {
System.out.println("test2:" + o.toString());
}
}
}
使用原生態(tài)類型List會失去類型安全性乳怎,使用List<Object>這樣的參數(shù)化類型則不會,為了更好的說明請參考下面的代碼前弯。
public static void main(String[] args) {
List<String> strings = new ArrayList<String>();
unsafeAdd(strings, new Integer(42));
String s = strings.get(0);
}
private static void unsafeAdd(List list, Object o) {
list.add(o);
}
在最新的Java8中舞肆,上述的代碼已經(jīng)無法通過編譯。
將List替換為List<Object>能看到更明確的報錯信息博杖。
在不確定集合元素類型的情況下使用無限制的通配符類型代替使用原生態(tài)類型
如果你需要篩選集合的交集椿胯,使用原生態(tài)類型你可以這樣寫:
public static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1: s1) {
if (s2.contains(o1)) {
result++;
}
}
return result;
}
由于使用了原生類型,這樣寫是很危險的剃根。
如果要定義一個參數(shù)化的類型(例如List<String>,因為不能使用List<E>定義)哩盲,但不確定或不關(guān)心實際的參數(shù)類型則可以使用一個?來代替狈醉。
public static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o1: s1) {
if (s2.contains(o1)) {
result++;
}
}
return result;
}
可以將任何元素放入原生態(tài)類型的集合中廉油,因此很容易破壞該集合的類型約束條件,例如如下代碼可通過編譯:
public static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1: s1) {
if (s2.contains(o1)) {
result++;
}
}
s1.add("1");
s2.add(2);
return result;
}
但如下代碼卻不行:
public static int numElementsInCommon(Set<?> s1, Set<?> s2) {
int result = 0;
for (Object o1: s1) {
if (s2.contains(o1)) {
result++;
}
}
s1.add("1");
s2.add(2);
return result;
}
通過以上例子不難看出苗傅?通配符主要使用在函數(shù)的形參上且在該函數(shù)內(nèi)部不會修改該集合的場景中抒线。
數(shù)組與泛型
數(shù)組與泛型相比有兩個重要的不同點,第一個是數(shù)組是協(xié)變的而泛型不是渣慕,例如如果Sub是Super的子類型嘶炭,那么Sub[]也是Super[]的子類型抱慌。相反泛型則是不可變的:對于任意兩個不同的類型Type1和Type2,List<Type1>和List<Type2>沒有關(guān)系( 其實這是數(shù)組設(shè)計的缺陷而不是泛型)眨猎。
這個不同導(dǎo)致使用泛型可以比使用數(shù)組更容易發(fā)現(xiàn)錯誤抑进,參考如下的代碼:
public static void main(String[] args) {
Object[] objectArray = new Long[1];
objectArray[0] = "I don't fit in.";
}
這段代碼可以通過編譯但在執(zhí)行時會拋出異常。
public static void main(String[] args) {
List<Object> o1 = new ArrayList<Long>();
o1.add("I don't fit in.");
}
而這段代碼則無法通過編譯睡陪,對比數(shù)組我們可以更早的發(fā)現(xiàn)錯誤寺渗。