泛型:
一種程序設(shè)計(jì)語(yǔ)言的新特性,于Java而言,在JDK 1.5開(kāi)始引入鸦采。泛型就是在設(shè)計(jì)程序的時(shí)候定義一些可變部分,在具體使用的時(shí)候再給可變部分指定具體的類型咕幻。使用泛型比使用Object變量再進(jìn)行強(qiáng)制類型轉(zhuǎn)換具有更好的安全性和可讀性渔伯。在Java中泛型主要體現(xiàn)在泛型類、泛型方法和泛型接口中肄程。
Java泛型中的標(biāo)記符含義:
E - Element (在集合中使用锣吼,因?yàn)榧现写娣诺氖窃?
T - Type(Java 類)
K - Key(鍵)
V - Value(值)
N - Number(數(shù)值類型)
? - 表示不確定的java類型
S蓝厌、U玄叠、V - 2nd、3rd拓提、4th types
泛型類
什么時(shí)候使用泛型類读恃?
只要類中操作的引用數(shù)據(jù)類型不確定,就可以定義泛型類代态。通過(guò)使用泛型類寺惫,可以省去強(qiáng)制類型轉(zhuǎn)換和類型轉(zhuǎn)化異常的麻煩。
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
public class Container<k,V>{
private k key;
private V value;
public Container(K k,V v){
key=k;
value=v;
}
public K getkey(){
return key;
}
public V getValue(){
return value
}
public void setKey(){
this.key=key;
}
public void setValue(){
this.value=value;
}
}
泛型方法
泛型方法也是為了提高代碼的重用性和程序安全性蹦疑。編程原則:盡量設(shè)計(jì)泛型方法解決問(wèn)題西雀,如果設(shè)計(jì)泛型方法可以取代泛型整個(gè)類,應(yīng)該采用泛型方法歉摧。
泛型方法的格式:類型變量放在修飾符后面和返回類型前面艇肴, 如:public static <E> E getMax(T... in)
public class GenericFunc {
public static void main(String[] args) {
print("hahaha");
print(200);
}
public static <T> void print(T t){
System.out.println(t.toString());
}
}
泛型接口
將泛型原理用于接口實(shí)現(xiàn)中,就是泛型接口叁温。
泛型接口的格式:泛型接口格式類似于泛型類的格式豆挽,接口中的方法的格式類似于泛型方法的格式。
public interface MyInteface<T> {
public T read(T t);
}
public class Generic2 implements MyInterface<String>{
public static void main(String[] args) {
Generic2 g = new Generic2();
System.out.println(g.read("hahaha"));
}
@Override
public String read(String str) {
return str;
}
}
通配符
當(dāng)操作的不同容器中的類型都不確定的時(shí)候券盅,而且使用的元素都是從Object類中繼承的方法帮哈,這時(shí)泛型就用通配符“?”來(lái)表示。泛型的通配符:“?” 相當(dāng)于 “贬养? extends Object”
- <? extends T>:是指 “上界通配符(Upper Bounds Wildcards)
- <? super T>:是指 “下界通配符(Lower Bounds Wildcards)”
- 為什么要用通配符和邊界惯疙?
比如我們有Fruit類廉丽,和它的派生類Apple類政冻。
class Fruit {}
class Apple extends Fruit {}
然后有一個(gè)最簡(jiǎn)單的容器:Plate類
public class Plate<T> {
private T item;
public Plate(T item) {
super();
this.item = item;
}
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
}
現(xiàn)在我定義一個(gè)“水果盤子”的止,邏輯上水果盤子當(dāng)然可以裝蘋果座云。
Plate<Fruit> p=new Plate<Apple>(new Apple());
但實(shí)際上Java編譯器不允許這個(gè)操作发侵。會(huì)報(bào)錯(cuò)氧腰,“裝蘋果的盤子”無(wú)法轉(zhuǎn)換成“裝水果的盤子”枫浙。
Type mismatch: cannot convert from Plate<Apple> to Plate<Fruit>
實(shí)際上,編譯器腦袋里認(rèn)定的邏輯是這樣的:
- 蘋果 IS-A 水果
- 裝蘋果的盤子 NOT-IS-A 裝水果的盤子
所以就算容器里面裝的東西有繼承關(guān)系古拴,但是容器是沒(méi)有繼承關(guān)系的箩帚,所以不可以把Plate<Apple>的引用賦值給Plate<Fruit>
為了讓泛型使用起來(lái)更靈活,sun的大牛就想出了<? extends T>和<? super T>的辦法黄痪,讓水果盤子和蘋果盤子發(fā)生關(guān)系
“上界通配符(Upper Bounds Wildcards)”:
Plate<紧帕? extends Fruit>
翻譯成人話就是:一個(gè)能放水果以及一切是水果派生類的盤子。再直白點(diǎn)就是:啥水果都能放的盤子桅打。這和我們?nèi)祟惖倪壿嬀捅容^接近了是嗜。Plate<? extends Fruit>和Plate<Apple>最大的區(qū)別就是:Plate<挺尾? extends Fruit>是Plate<Fruit>以及Plate<Apple>的基類鹅搪。直接的好處就是,我們可以用“蘋果盤子”給“水果盤子”賦值了遭铺。
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
如果把Fruit和Apple的例子再擴(kuò)展一下丽柿,食物分成水果和肉類,水果有蘋果和香蕉掂僵,肉類有豬肉和牛肉航厚,蘋果還有兩種青蘋果和紅蘋果顷歌。
//Lev 1
class Food{}
//Lev 2
class Fruit extends Food{}
class Meat extends Food{}
//Lev 3
class Apple extends Fruit{}
class Banana extends Fruit{}
class Pork extends Meat{}
class Beef extends Meat{}
//Lev 4
class RedApple extends Apple{}
class GreenApple extends Apple{}
在這個(gè)體系中锰蓬,上界通配符 “Plate<? extends Fruit>” 覆蓋下圖中藍(lán)色的區(qū)域眯漩。
“下界通配符(Lower Bounds Wildcards)”:
Plate<芹扭? super Fruit>
表達(dá)的就是相反的概念:一個(gè)能放水果以及一切是水果基類的盤子。Plate<赦抖? super Fruit>是Plate<Fruit>的基類舱卡,但不是Plate<Apple>的基類。對(duì)應(yīng)剛才那個(gè)例子队萤,Plate<轮锥? super Fruit>覆蓋下圖中紅色的區(qū)域。
上下界通配符的副作用
邊界讓Java不同泛型之間的轉(zhuǎn)換更容易了要尔。但不要忘記舍杜,這樣的轉(zhuǎn)換也有一定的副作用新娜。那就是容器的部分功能可能失效。
還是以剛才的Plate為例既绩。我們可以對(duì)盤子做兩件事概龄,往盤子里set( )新東西,以及從盤子里get( )東西饲握。
class Plate<T>{
private T item;
public Plate(T t){item=t;}
public void set(T t){item=t;}
public T get(){return item;}
}
上界<? extends T>不能往里存私杜,只能往外取
<? extends Fruit>會(huì)使往盤子里放東西的set( )方法失效。但取東西get( )方法還有效救欧。比如下面例子里兩個(gè)set()方法衰粹,插入Apple和Fruit都報(bào)錯(cuò)。
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
//不能存入任何元素
p.set(new Fruit()); //Error
p.set(new Apple()); //Error
//讀取出來(lái)的東西只能存放在Fruit或它的基類里颜矿。
Fruit newFruit1=p.get();
Object newFruit2=p.get();
Apple newFruit3=p.get(); //Error
原因是編譯器只知道容器內(nèi)是Fruit或者它的派生類寄猩,但具體是什么類型不知道∑锝可能是Fruit田篇?可能是Apple?也可能是Banana箍铭,RedApple泊柬,GreenApple?編譯器在看到后面用Plate<Apple>賦值以后诈火,盤子里沒(méi)有被標(biāo)上有“蘋果”兽赁。而是標(biāo)上一個(gè)占位符:CAP#1,來(lái)表示捕獲一個(gè)Fruit或Fruit的子類冷守,具體是什么類不知道刀崖,代號(hào)CAP#1。然后無(wú)論是想往里插入Apple或者M(jìn)eat或者Fruit編譯器都不知道能不能和這個(gè)CAP#1匹配拍摇,所以就都不允許亮钦。
所以通配符<?>和類型參數(shù)<T>的區(qū)別就在于,對(duì)編譯器來(lái)說(shuō)所有的T都代表同一種類型充活。比如下面這個(gè)泛型方法里蜂莉,三個(gè)T都指代同一個(gè)類型,要么都是String混卵,要么都是Integer映穗。
public <T> List<T> fill(T... t);
但通配符<?>沒(méi)有這種約束,Plate<?>單純的就表示:盤子里放了一個(gè)東西幕随,是什么我不知道蚁滋。
所以錯(cuò)誤就在這里,Plate<? extends Fruit>里什么都放不進(jìn)去辕录。
下界<? super T>不影響往里存澄阳,但往外取只能放在Object對(duì)象里
使用下界<? super Fruit>會(huì)使從盤子里取東西的get( )方法部分失效,只能存放到Object對(duì)象里踏拜。set( )方法正常碎赢。
Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());
//存入元素正常
p.set(new Fruit());
p.set(new Apple());
//讀取出來(lái)的東西只能存放在Object類里。
Apple newFruit3=p.get(); //Error
Fruit newFruit1=p.get(); //Error
Object newFruit2=p.get();
因?yàn)橄陆缫?guī)定了元素的最小粒度的下限速梗,實(shí)際上是放松了容器元素的類型控制肮塞。既然元素是Fruit的基類,那往里存粒度比Fruit小的都可以姻锁。但往外讀取元素就費(fèi)勁了枕赵,只有所有類的基類Object對(duì)象才能裝下。但這樣的話位隶,元素的類型信息就全部丟失拷窜。
PECS原則
最后看一下什么是PECS(Producer Extends Consumer Super)原則,已經(jīng)很好理解了:
- 頻繁往外讀取內(nèi)容的涧黄,適合用上界Extends篮昧。
- 經(jīng)常往里插入的,適合用下界Super笋妥。