使用泛型的目的
當(dāng)我們第一次接觸泛型時(shí),第一個(gè)問(wèn)題肯定會(huì)是,為什么要使用泛型?最直接的答案是為了避免轉(zhuǎn)型,使得編譯器能夠在編譯期就發(fā)現(xiàn)轉(zhuǎn)型錯(cuò)誤而不用等到運(yùn)行時(shí)。
比如說(shuō),當(dāng)我們聲明了一個(gè)泛型為Integer的列表,那么該列表的元素就只能是Integer,當(dāng)我們往里面放非Integer的元素時(shí),編譯器就能夠發(fā)現(xiàn),
List<Integer> intList = new ArrayList<>();
intList.add("abc"); //編譯期錯(cuò)誤,不能存放String對(duì)象到intList中
同理,當(dāng)我們從列表中獲取元素時(shí),我們也不再需要使用強(qiáng)制轉(zhuǎn)型將列表中的元素轉(zhuǎn)型成Integer
Integer i = intList.get(0);
Integer i = (Integer)intList.get(0);//不再需要進(jìn)行強(qiáng)制轉(zhuǎn)型
然而,泛型卻有一些容易令人困惑的地方,第一個(gè)就是關(guān)于泛型的帶有通配符的上下界。
帶有通配符的上下界
Java泛型現(xiàn)在支持兩種帶有通配符的上下界的表達(dá)方式,
- ? extends T - 這里的?表示類型T的任意子類型,包括類型T本身。
- ? super T - 這里的?表示類型T的任意父類型,包括類型T本身。
這兩者的含義都很容易理解和區(qū)分,難點(diǎn)在于我們什么時(shí)候該用 ? extends T, 什么時(shí)候改用? super T.
比如說(shuō),由于? extends T表示類型T和它的任意子類型,那么我們可以說(shuō)List<? extends Number> 實(shí)例可以添加任意為Number子類的元素嗎?
List<? extends Number> intList = new ArrayList<>();
intList.add(1); //complier error
intList.add(3.14); //compiler error
從上述的代碼來(lái)看,它并不像我們所理解的那樣工作。那么,我們?cè)谑裁磿r(shí)候需要使用? extends T和? super T呢? 這里有一條簡(jiǎn)單的規(guī)則 (Get and Put Rule):
當(dāng)你需要從一個(gè)數(shù)據(jù)結(jié)構(gòu)中獲取數(shù)據(jù)時(shí)(get), 那么就使用? extends T. 如果你需要存儲(chǔ)數(shù)據(jù)(put)到一個(gè)數(shù)據(jù)結(jié)構(gòu)時(shí),那么就使用? super T. 如果你又想存儲(chǔ)數(shù)據(jù),又想獲取數(shù)據(jù),那么就不要使用通配符? . 即直接使用具體泛型T。
所以,根據(jù)Get&Put規(guī)則,在上面的例子中,我們是需要往數(shù)據(jù)結(jié)構(gòu)里面存儲(chǔ)數(shù)據(jù)的,所以需要使用? super T. 修改上面的例子,你可以發(fā)現(xiàn)程序可以工作了。
List<? super Number> intList = new ArrayList<>();
intList.add(1); //it works
intList.add(3.14); //it works
第二個(gè)比較讓人困惑的是關(guān)于泛型的類型系統(tǒng)。
泛型的類型系統(tǒng)
泛型的類型系統(tǒng)中最令人困惑的地方就是,Integer類是Number類的子類,而List<Integer>卻不是List<Number>的子類。也就是說(shuō),你不能將List<Integer>實(shí)例對(duì)象直接賦值給List<Number>實(shí)例對(duì)象。 List<Integer>不是List<Number>子類的原因我們只能通過(guò)反證法推理出來(lái):
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<Number> nums = ints; // compile-time error
nums.add(3.14);
assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!
從上面的例子中,假設(shè)List<Integer>是List<Number>的子類,那么List<Integer>實(shí)例對(duì)象ints就可以賦值給List<Number>實(shí)例對(duì)象nums。接著我們可以往nums實(shí)例添加非Integer對(duì)象,如float對(duì)象3.14。但是這個(gè)是不允許的,因?yàn)閚ums實(shí)例實(shí)際上是List<Integer>實(shí)例對(duì)象,它不能接受任何非Integer對(duì)象。由此可證,List<Integer>并不是List<Number>的子類。但是,List<Integer>是Collection<Integer>的子類。
所以對(duì)于泛型的類型系統(tǒng)來(lái)講,它應(yīng)當(dāng)遵循以下一些規(guī)則,摘抄自Java深度歷險(xiǎn)-Java泛型
引入泛型之后的類型系統(tǒng)增加了兩個(gè)維度:一個(gè)是類型參數(shù)自身的繼承體系結(jié)構(gòu),另外一個(gè)是泛型類或接口自身的繼承體系結(jié)構(gòu)。第一個(gè)指的是對(duì)于List<String>和List<Object>這樣的情況,類型參數(shù)String是繼承自O(shè)bject的。而第二種指的是List接口繼承自Collection接口。對(duì)于這個(gè)類型系統(tǒng),有如下的一些規(guī)則:
- 相同類型參數(shù)的泛型類的關(guān)系取決于泛型類自身的繼承體系結(jié)構(gòu)。即List<String>是Collection<String> 的子類型,List<String>可以替換Collection<String>。這種情況也適用于帶有上下界的類型聲明。
- 當(dāng)泛型類的類型聲明中使用了通配符的時(shí)候,其子類型可以在兩個(gè)維度上分別展開(kāi)。如對(duì)Collection<? extends Number>來(lái)說(shuō),其子類型可以在Collection這個(gè)維度上展開(kāi),即List<? extends Number>和Set<? extends Number>等;也可以在Number這個(gè)層次上展開(kāi),即Collection<Double>和 Collection<Integer>等。如此循環(huán)下去,ArrayList<Long>和 HashSet<Double>等也都算是Collection<? extends Number>的子類型。
- 如果泛型類中包含多個(gè)類型參數(shù),則對(duì)于每個(gè)類型參數(shù)分別應(yīng)用上面的規(guī)則。
最后我們來(lái)看看在Java泛型參數(shù)是如何進(jìn)行推斷的。
泛型參數(shù)的自動(dòng)推斷
有兩種方式可以指定泛型參數(shù)的類型,一種是顯示的指定,比如對(duì)于下面的方法,
public class GenericUtil {
public static <T> List<T> asList(T... array) {
List<T> tlist = new ArrayList<>();
for (T t : array) {
tlist.add(t);
}
return tlist;
}
}
我們可以顯示的指定泛型參數(shù)的類型,
System.out.println(GenericUtil.<String>asList("a","b","c"));
除了顯示的指定之外,我們還可以隱式的指定, 實(shí)際上就是讓編譯器自己去推斷。
System.out.println(GenericUtil.asList("a","b","c"));
編譯器可以通過(guò)asList方法接收的參數(shù)類型來(lái)推斷出T實(shí)際上就是String。 如果從方法接收的參數(shù)類型推斷不出來(lái)的話,那么編譯器還會(huì)從方法賦值的目標(biāo)參數(shù)來(lái)推斷,比如說(shuō)下面的newObject方法,
public class GenericUtil {
public static <T> T newObject(String className) throws Exception {
Class clz = Class.forName(className);
return (T) clz.newInstance();
}
}
從方法接收參數(shù)上編譯器并不能推斷出T是什么類型。這個(gè)時(shí)候可以從方法賦值的目標(biāo)參數(shù)進(jìn)行推斷,
Person p = GenericUtil.newObject("com.kevin.hibernate01.Person");
從方法賦值的目標(biāo)參數(shù)p可以得知,newObject方法中定義的泛型參數(shù)T類型應(yīng)該為Person類型。