更多 Java 高級(jí)知識(shí)方面的文章旗唁,請(qǐng)參見文集《Java 高級(jí)知識(shí)》
類型系統(tǒng)
Liskov 里氏替換原則,子類可以替換父類。
- 需要
Object
的引用時(shí)源葫,可以傳入String
對(duì)象 - 需要
String
的引用時(shí),傳入Object
對(duì)象需要強(qiáng)制類型轉(zhuǎn)換砖瞧,運(yùn)行時(shí)可能拋出ClassCastException
類型擦除 Type Erasure
Java 的泛型是在 編譯器 層次實(shí)現(xiàn)的息堂。
在編譯生成的字節(jié)碼中不包含泛型中的類型參數(shù),類型參數(shù)會(huì)在編譯時(shí)去掉块促。
例如:List<String>
和 List<Integer>
在編譯后都變成 List
荣堰。
類型擦除的基本過(guò)程:將代碼中的類型參數(shù)替換為具體的類,同時(shí)去掉 <>
的內(nèi)容竭翠。
泛型
- 類型參數(shù)只能是類振坚,例如
List<Integer>
,不能是簡(jiǎn)單類型逃片,例如List<int>
- 類型參數(shù)可以有多個(gè)屡拨,例如
HashMap<String, Integer>
泛型的優(yōu)勢(shì)
- 編譯時(shí)更強(qiáng)大的類型檢測(cè)只酥。Java 編譯器對(duì)泛型應(yīng)用了強(qiáng)大的類型檢測(cè),如果代碼違反了類型安全就會(huì)報(bào)錯(cuò)呀狼。修復(fù)編譯時(shí)錯(cuò)誤比修復(fù)運(yùn)行時(shí)錯(cuò)誤更加容易裂允,因?yàn)檫\(yùn)行時(shí)錯(cuò)誤很難查找到。
例如如下代碼: 方法傳入一個(gè) String
對(duì)象哥艇,傳出一個(gè) String
對(duì)象绝编,并強(qiáng)制轉(zhuǎn)換為 Integer
對(duì)象。
這段代碼編譯可以通過(guò)貌踏,因?yàn)槎际?Object
的子類十饥,但是運(yùn)行時(shí)會(huì)產(chǎn)生 ClassCastException
。
public static Object setAndReturn(Object obj) {
return obj;
}
public static void main(String[] args) {
Integer i = (Integer) setAndReturn(new String("abc"));
}
而如果通過(guò)泛型來(lái)實(shí)現(xiàn)祖乳,則會(huì)在編譯時(shí)進(jìn)行類型的檢測(cè)逗堵。例如如下代碼:會(huì)產(chǎn)生編譯錯(cuò)誤。
public static <T> T setAndReturn(T t) {
return t;
}
public static void main(String[] args) {
Integer i = (Integer) setAndReturn(new String("abc"));
}
-
提供自動(dòng)和隱式的類型轉(zhuǎn)換
例如如下代碼:在函數(shù)返回時(shí)眷昆,不需要顯示的類型轉(zhuǎn)換<Integer>setAndReturn(new Integer("123"));
public static <T> T setAndReturn(T t) {
return t;
}
public static void main(String[] args) {
// 不需要使用 = <Integer>setAndReturn(new Integer("123"));
Integer i = setAndReturn(new Integer("123"));
}
- 實(shí)現(xiàn)泛型算法蜒秤,類似于 C++ 中的模板
泛型的奇怪特性
- 泛型類并沒(méi)有獨(dú)有的
Class
對(duì)象。即不存在List<String>.class
亚斋,只存在List.class
作媚。 - 泛型類中的靜態(tài)變量被所有實(shí)例共享。
- 泛型類中的類型參數(shù)不能用在異常處理的 catch 中帅刊。
因?yàn)楫惓L幚硎?JVM 運(yùn)行時(shí)刻來(lái)進(jìn)行的纸泡,此時(shí)類型參數(shù)已被擦除。
<T> VS <?>
不同點(diǎn):
-
<T>
用于 泛型的定義赖瞒,例如class MyGeneric<T> {...}
-
<?>
用于 泛型的聲明女揭,即泛型的使用,例如MyGeneric<?> g = new MyGeneric<>();
相同點(diǎn):都可以指定上界和下界冒黑,例如:
class MyGeneric<T extends Collection> {...}
class MyGeneric<T super List> {...}
MyGeneric<? extends Collection> g = new MyGeneric<>();
MyGeneric<? super List> g = new MyGeneric<>();
泛型類
示例:
public class Generic_Test {
public static void main(String[] args) {
MyGeneric<String> g1 = new MyGeneric<String>(new String("123"));
g1.print();
MyGeneric<Integer> g2 = new MyGeneric<Integer>(new Integer("123"));
g2.print();
}
}
class MyGeneric<T> {
private T t;
public MyGeneric(T t) {
this.t = t;
}
public void print() {
System.out.println(t.getClass().getName());
}
}
Java7 的泛型類型推斷改進(jìn)
在上面的代碼中田绑,MyGeneric<String> g1 = new MyGeneric<String>(new String("123"));
÷盏可以看出掩驱,需要在聲明和賦值的時(shí)候,兩側(cè)都加上泛型類型 <String>
冬竟。
在Java 7中欧穴,這種方式得以改進(jìn),可以使用如下語(yǔ)句進(jìn)行聲明和賦值:
MyGeneric<String> g1 = new MyGeneric<>(new String("123"));
在這條語(yǔ)句中泵殴,編譯器會(huì)根據(jù)變量聲明時(shí)的泛型類型自動(dòng)推斷出實(shí)例化 MyGeneric
時(shí)的泛型類型涮帘。
泛型方法
示例:
public static <T> void print(T t) {
System.out.println(t.getClass().getName());
}
public static void main(String[] args) {
print(new String("123"));
print(new Integer("123"));
}
使用泛型類
- 指定具體類型,如
List<String>
- 指定未知類型笑诅,如
List<?>
调缨。
List<?>
不等于List<Object>
疮鲫,例如:
List<Object> list1 = new ArrayList();
list1.add(1); // 編譯通過(guò)
List<?> list2 = new ArrayList();
list2.add(1); // 編譯錯(cuò)誤
泛型的繼承關(guān)系及擴(kuò)展
-
String
是Object
的子類,但是List<String>
不是List<Object>
的子類弦叶。
例如:如下代碼會(huì)導(dǎo)致編譯錯(cuò)誤俊犯。
如果將List<Object>
換成List<?>
,則可以編譯通過(guò)伤哺。
public static void f(List<Object> list) {
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
f(list); // 編譯錯(cuò)誤.
}
- 相同參數(shù)類型的泛型類的繼承關(guān)系取決于泛型類自身的繼承結(jié)構(gòu)燕侠。
例如List<String>
是Collection<String>
的子類 - 當(dāng)類型聲明中使用通配符
?
時(shí),其子類型可以在兩個(gè)維度上擴(kuò)展立莉。
例如Collection<? extends Number>
在維度1上擴(kuò)展:List<? extends Number>
在維度2上擴(kuò)展:Collection<Integer>
兩個(gè)維度上同時(shí)擴(kuò)展:List<Integer>