本文大量參考Thinking in java(解析,填充)莲祸。
定義:多態(tài)算是一種泛化機(jī)制酪穿,解決了一部分可以應(yīng)用于多種類型代碼的束縛。雖然我們可以在參數(shù)定義一個(gè)基類或者是一個(gè)接口辫红,但是他們的約束還是太強(qiáng)了凭涂,有的時(shí)候我們更希望編寫更通用的代碼祝辣,使代碼能夠應(yīng)用于“某種不具體的類型”.而正是泛型的出現(xiàn)解決了這類問題,它實(shí)現(xiàn)了參數(shù)化類型的概念切油。其最初的目的是希望類或方法能夠具備最廣泛的表達(dá)能力(通過解耦類或方法與所使用的 類型之間的約束)蝙斜。在你創(chuàng)建參數(shù)化類型的一個(gè)實(shí)例時(shí),編譯器會(huì)為你負(fù)責(zé)轉(zhuǎn)型操作澎胡,并且保證類的正確性孕荠。
泛型參數(shù)不能使用基本類型.
泛型類:
public class Holder<T> {
private T a;
public T getA() {
return a;
}
public void setA(T a) {
this.a = a;
}
public static void main(String[] args) {
Holder<Object> aHolder=new Holder<>();
aHolder.setA("asd");
aHolder.setA(new Object());
}
}```
創(chuàng)建Holder對(duì)象時(shí)可以指定泛型指向的對(duì)象,指明后就只能在Holder內(nèi)部放入該類型(或其子類攻谁,多態(tài)與泛型不沖突)稚伍,所以這里放入Object以及String都是可以的,Object是所有類的爹戚宦。
###泛型接口:同上
###泛型方法:
![泛型方法](http://upload-images.jianshu.io/upload_images/3267534-beeaebe61351a122.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
方法可以獨(dú)立于類而變化个曙。無論何時(shí),只要你能夠做到受楼,你就應(yīng)該盡量使用泛型方法垦搬。在使用泛型類時(shí)我們創(chuàng)建對(duì)象需要去指定類型參數(shù)的值,而使用泛型方法的時(shí)候通常不需要明確指明艳汽,如上圖猴贰,編譯器會(huì)自動(dòng)找出相應(yīng)的值(type argument inference)。同樣也可以顯示指明類型:
`response.<String>f("123");`
當(dāng)然泛型方法與可變參數(shù)之間也是可以共存的:
public class Holder1 {
public static <T>ArrayList<T> test(T...a)
{
ArrayList<T> result=new ArrayList<>();
for(T item:a)
{
result.add(item);
}
return result;
}
public static void main(String[] args) {
System.out.println(test("123","234","345"));
}
}
output:[123,234,345]
###擦除:
盡管可以聲明Arraylist.class但是無法聲明ArrayList<Integer>.class.
Class class1=new ArrayList<String>().getClass();
Class class2=new ArrayList<Integer>().getClass();
System.out.println(class1==class2);
output:true
通過這種方式可以看的更清楚:
public class Holder1 {
class A{};
class B{};
public static void main(String[] args) {
List<A> list =new ArrayList<A>();
HashMap<A, B> hashMap=new HashMap<>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(hashMap.getClass().getTypeParameters()));
}
}
output:
[E]
[K, V]
Class.getTypeParameters()將返回一個(gè)TypeVariavle對(duì)象數(shù)組河狐,表示有泛型聲明所聲明的類型參數(shù)米绕。然而輸出的只是標(biāo)識(shí)符,所以可以得出以下結(jié)論:
在泛型代碼內(nèi)部是無法獲得任何有關(guān)泛型參數(shù)類型的信息甚牲。
盡管你知道類型參數(shù)標(biāo)識(shí)符和泛型類型邊界這類的信息--你卻無法知道用來創(chuàng)建某個(gè)特定實(shí)例的實(shí)際類型參數(shù)义郑。
這就是擦除帶來的弊端。比如說:
public class Holder1<T> {
private T obj;
public Holder1(T obj) {
// TODO Auto-generated constructor stub
this.obj=obj;
}
public void test()
{
//obj.test(); wrong
}
public T getObj()
{
return obj;
}
public static void main(String[] args) {
Holder1<Test>holder1=new Holder1<Test>(new Test());
holder1.test();
}
}
class Test{
public void test()
{
System.out.println("sb");
}
}
由于擦除丈钙,這種調(diào)用在java中是無法實(shí)現(xiàn)的非驮,而為了實(shí)現(xiàn)這種需求(obj需要調(diào)用f())就有了extends關(guān)鍵字,也就是泛型的邊界
extends:
` public class Holder1<T extends Test>`
<T extends Test>聲明T必須具有類型Test或者從Test導(dǎo)出的類型.也就是當(dāng)前定義的泛型必須是Test或其子類.
為什么通過這樣就能成功實(shí)現(xiàn)我們的需求雏赦?因?yàn)榉盒皖愋蛥?shù)在運(yùn)行時(shí)是擦除到它的第一個(gè)邊界劫笙,編譯器實(shí)際上會(huì)把類型參數(shù)替換為它的最后擦除類,所以當(dāng)前的T在擦除后實(shí)際上是Test,等于做了一個(gè)替換星岗。
那么這么做的意義在哪里填大,和我這樣去寫的差別在哪:
public class Holder1 {
private Test obj;
public Holder1(Test obj) {
// TODO Auto-generated constructor stub
this.obj=obj;
}
public Test getObj() {
return obj;
}
public void test()
{
obj.test();
}
}
根本原因是通過泛化,能讓當(dāng)前代碼跨越多個(gè)類工作俏橘,它不明確定義某個(gè)字類型允华,在使用時(shí)能返回確切的類型信息。比如:
這里返回的就是我定義的Test1
public class Holder1<T extends Test> {
private T obj;
public Holder1(T obj) {
// TODO Auto-generated constructor stub
this.obj=obj;
}
public T getObj() {
return obj;
}
public void test()
{
obj.test();
}
public static void main(String[] args) {
Holder1<Test1>holder1=new Holder1<Test1>(new Test1());
holder1.getObj();//這里返回的就是我定義的Test1對(duì)象
}
}
class Test{
public void test()
{
System.out.println("sb");
}
}
class Test1 extends Test
{
}
為什么會(huì)有擦除,而不能像C++一樣實(shí)現(xiàn)完整的泛化機(jī)制:
> 這就是為了保證向前的兼容性靴寂,java早期并沒有泛型的相關(guān)概念磷蜀,并且能夠減少JVM相關(guān)的改變,以及不破壞現(xiàn)有類庫的前提下百炬,以最小代價(jià)來實(shí)現(xiàn)相關(guān)概念褐隆。
而這也使得泛型在java當(dāng)中不是那么好用。所以在運(yùn)行時(shí)期剖踊,所有泛型都將被擦除庶弃,替換成它們的非泛型上界,例如List<T>這種將被擦除為L(zhǎng)ist,而普通的類型變量在沒定義邊界的情況下被擦除為Object.
####擦除的代價(jià):
不能用于顯式地引用運(yùn)行時(shí)類型的操作之中:轉(zhuǎn)型(cast)德澈、instanceof操作和new表達(dá)式歇攻。因?yàn)樗械膮?shù)類型信息在運(yùn)行時(shí)期都會(huì)丟失,所以需要無時(shí)無刻提醒自己:參數(shù)的類型信息只是目前看起來擁有而已圃验。最后只會(huì)留下它的上界掉伏。
###邊界處的動(dòng)作:
public class Holder1<T> {
private Class<T> clazz;
public Holder1(Class<T> class1) {
// TODO Auto-generated constructor stub
clazz=class1;
}
public T[] Test() {
return (T[]) Array.newInstance(clazz, 2);//這里強(qiáng)轉(zhuǎn)缝呕,并且有cast警告
}
public static void main(String[] args) {
System.out.println(Arrays.toString(new Holder1<String>(String.class).Test()));
}
}
output:[null, null]
這里即使clazz被存儲(chǔ)為Class<T>澳窑,但是由于擦除,實(shí)際上也只是Class,因此在運(yùn)行時(shí)Array.newInstance內(nèi)的clazz沒有實(shí)際含義供常。接上文描述的:在泛型代碼內(nèi)部是無法獲得任何有關(guān)泛型參數(shù)類型的信息摊聋。所以這里Array.newInstance實(shí)際上并未擁有clazz蘊(yùn)含的類型信息(這里的T沒有實(shí)際意義,不知道實(shí)際上是什么)栈暇。
而另一個(gè)例子;
public class Holder1<T> {
private List<T> list;
public Holder1() {
}
public List<T> maker(T t,int n)
{
List<T>result=new ArrayList<>();
for(int i=0;i<n;i++)
{
result.add(t);
}
return result;
}
public static void main(String[] args) {
Holder1<String> sHolder1=new Holder1<>();
System.out.println(sHolder1.maker("asd", 6));
}
}
這個(gè)例子中麻裁,盡管在運(yùn)行時(shí)會(huì)擦除所有T類型的相關(guān)信息,可是它仍舊可以確保在編譯器你放置到Holder1當(dāng)中的對(duì)象具有T類型源祈,使其適合List<T>,確保了在方法或類中的類型內(nèi)部一致性煎源,這也可以認(rèn)為是一種規(guī)法。不過還是那句話香缺,內(nèi)部并不知道T的實(shí)際含義.只能確保類型的統(tǒng)一.
因?yàn)椴脸诜椒w中移除了類型信息手销,所以在運(yùn)行時(shí)的問題就是邊界:對(duì)象進(jìn)入和離開方法的地點(diǎn)。
看下面兩個(gè)例子:
1.不使用泛型
public class Holder1 {
private Object object;
public Holder1() {
}
public void setObject(Object object) {
this.object = object;
}
public Object getObject() {
return object;
}
public static void main(String[] args) {
Holder1 holder1=new Holder1();
holder1.setObject("String");
String string=(String) holder1.getObject();
}
}
反編譯后:
D:>javap -c Holder1
Compiled from "Holder1.java"
public class Holder1 {
public Holder1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
public void setObject(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field object:Ljava/lang/Object;
5: return
public java.lang.Object getObject();
Code:
0: aload_0
1: getfield #2 // Field object:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3 // class Holder1
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5 // String String
11: invokevirtual #6 // Method setObject:(Ljava/lang/Obje
ct;)V
14: aload_1
15: invokevirtual #7 // Method getObject:()Ljava/lang/Obj
ect;
18: checkcast #8 // class java/lang/String
21: astore_2
22: return
}
前面的一大部分省略,set和get都是針對(duì)Object操作图张,觀察第18行得知锋拖,get之后會(huì)有一個(gè)checkcast類型檢查,翻閱
Java Virtual Machine Online Instruction Reference:得知
> checkcast:ensure type of an object or array, checks that the top item on the operand stack (a reference to an object or array) can be cast to a given type.
翻譯:確保一個(gè)Object或者Array的類型祸轮。檢查操作數(shù)棧的最上層item(object或array的引用)是否能被轉(zhuǎn)換成相應(yīng)的類型兽埃。
checkcast 實(shí)際上可以被認(rèn)為是:
if (! (obj == null || obj instanceof <class>)) {
throw new ClassCastException();
}
// if this point is reached, then object is either null, or an instance of
// <class> or one of its superclasses.
2.使用泛型:
public class Holder1<T> {
private T object;
public Holder1() {
}
public void setObject(T object) {
this.object = object;
}
public T getObject() {
return object;
}
public static void main(String[] args) {
Holder1<String> holder1=new Holder1<>();
holder1.setObject("String");
String string=holder1.getObject();
}
}
反編譯后:
public static void main(java.lang.String[]);
Code:
0: new #3 // class Holder1
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5 // String String
11: invokevirtual #6 // Method setObject:(Ljava/lang/Obje
ct;)V
14: aload_1
15: invokevirtual #7 // Method getObject:()Ljava/lang/Obj
ect;
18: checkcast #8 // class java/lang/String
21: astore_2
22: return
}
11行setObject開始傳入的就是Object,但是set()方法不需要類型檢查适袜,編譯器已經(jīng)檢查過了柄错,但是對(duì)get方法在18行checkcast還是進(jìn)行了類型檢查,只不過用了泛型以后由編譯器自動(dòng)插入,其實(shí)效果是一樣的售貌。
在泛型中所有動(dòng)作發(fā)生在邊界處---對(duì)傳進(jìn)來的值做額外的編譯期檢查冕房,并由編譯器插入傳出去的值的轉(zhuǎn)型。這都是在編譯期間完成的趁矾。
###泛型數(shù)組:
class Gener<T>
{
public void gg()
{
System.out.println("ASD");
}
}
public class Holder1 {
static Gener<Integer>[] gia;
public Holder1() {
}
public static void main(String[] args) {
// gia=(Gener<Integer>[]) new Object[10]; //編譯器不會(huì)報(bào)錯(cuò)耙册,但是運(yùn)行會(huì)報(bào)錯(cuò)ClassCastException
// gia[0].gg();
gia=(Gener<Integer>[]) new Gener[10];
gia[0]=new Gener<Integer>();
gia[0].gg();
}
}
那么既然數(shù)組無論它們持有的類型如何,都具有相同的結(jié)構(gòu)毫捣,看起來是可以創(chuàng)建一個(gè)Object數(shù)組并將其轉(zhuǎn)型為所希望的數(shù)組類型详拙。事實(shí)上這樣做會(huì)報(bào)錯(cuò),為什么呢蔓同。
通過編寫下述代碼饶辙,進(jìn)行反編譯
public class TT {
public static void main(String[] args) {
int[] aa=new int[10];
String[]bb=new String[10];
}
}
獲得
public class TT {
public TT();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 10 //push the int 10 onto the stack,將10放入堆棧中
2: newarray int
4: astore_1
5: bipush 10
7: anewarray #2 // class java/lang/String
10: astore_2
11: return
}
這里有個(gè)newarray和anewarray就是創(chuàng)建數(shù)組,同樣翻閱
Java Virtual Machine Online Instruction Reference:
>newarray<type>:allocate new array for numbers or booleans.
newarray is used to allocate single-dimension arrays of booleans, chars, floats, doubles, bytes, shorts, ints or longs.
newarray pops a positive int, n, off the stack, and constructs an array for holding n elements of the type given by <type>. Initially the elements in the array are set to zero. A reference to the new array object is left on the stack.
翻譯:newarray<type>給numbers或者booleans分配一個(gè)新的數(shù)組。它被用來分配booleans, chars, floats, doubles, bytes, shorts, ints or longs. 的一維數(shù)組斑粱。
newarray從堆棧中彈出一個(gè)正整數(shù)n,然后構(gòu)造一個(gè)type是你定義的類型的數(shù)組弃揽。初始化數(shù)組當(dāng)中所有的元素(都設(shè)置默認(rèn)為0)。在堆棧上留下對(duì)新數(shù)組對(duì)象的引用.
>anewarray<type>:allocate new array for objects.<type> is either the name of a class or interface, e.g. java/lang/String, or, to create the first dimension of a multidimensional array, <type> can be an array type descriptor, e.g. [Ljava/lang/String;
anewarray allocates a new array for holding object references. It pops an int, size, off the stack and constructs a new array capable of holding size object references of the type indicated by <type>.
<type> indicates what types of object references are to be stored in the array (see aastore). It is the name of a class or an interface, or an array type descriptor. If it is java/lang/Object, for example, then any type of object reference can be stored in the array. <type> is resolved at runtime to a Java class, interface or array. See Chapter 7 for a discussion of how classes are resolved.
A reference to the new array is pushed onto the stack. Entries in the new array are initially set to null.
翻譯:給對(duì)象分配新的數(shù)組.<type>可以是class或者interface的名稱,比如說java/lang/String则北, 或者為了創(chuàng)建多維數(shù)組的第一維矿微,<type>可以是數(shù)組類型描述符,例如 [Ljava/lang/String; anewarray 分配一個(gè)新的數(shù)組去持有對(duì)象的引用尚揣。它從對(duì)戰(zhàn)中彈出一個(gè)int類型的size(大杏渴浮),并構(gòu)造一個(gè)能夠持有size個(gè)type對(duì)象引用的新數(shù)組快骗。
<type>表示object引用在數(shù)組當(dāng)中是以什么類型被存儲(chǔ)的娜庇。它是class或者interface的名稱或者數(shù)組類型的描述。比如說type是 java/lang/Object方篮,那么任意object引用都能被存儲(chǔ)進(jìn)當(dāng)前數(shù)組中名秀,而在運(yùn)行時(shí)將<type>解析成java類,interface或者數(shù)組藕溅。
一個(gè)新的數(shù)組引用將push到堆棧上匕得,數(shù)組中所有條目都會(huì)被設(shè)置為null
從這里就可以看出,這里泛型數(shù)組的構(gòu)建會(huì)調(diào)用anewarray蜈垮,而anewarray需要明確的type,
那么這樣就可以知道:
1.在gia=(Gener<Integer>[]) new Object[10]; 中耗跛,即使gia看起來是轉(zhuǎn)型為Gener<Integer>[],但是這也只是在編譯期,運(yùn)行時(shí)他仍然是Object[],正是因?yàn)閍newarray運(yùn)行時(shí)已經(jīng)將type定義為Object,你無法對(duì)底層的數(shù)組進(jìn)行更改攒发。所以強(qiáng)制轉(zhuǎn)型會(huì)引起ClassCastException.
2.根據(jù)Oracle的java文檔來看调塌,泛型屬于Non_Reifiable type,而引起這部分的原因也是因?yàn)轭愋筒脸齆on_Reifiable type會(huì)在編譯期被移除泛型信息,所以在運(yùn)行時(shí)無法獲取具體的類型信息惠猿。而java明確規(guī)定數(shù)組內(nèi)的元素必須是reifiable的羔砾,所以類似T[] a=new T[10]這類型的無法通過編譯。
參考例子:
String[] strArray = new String[20];
Object[] objArray = strArray;
objArray[0] = new Integer(1); // throws ArrayStoreException at runtime
那么假如說泛型的數(shù)組可以直接創(chuàng)建:
ArrayList<String>[] a=new ArrayList<String>[];
那么隨后也可以改為Object數(shù)組然后往里面放ArrayList<Integer>,我們?cè)陔S后的代碼中可以把它轉(zhuǎn)型為Object[]然后往里面放Arraylist<Integer>實(shí)例姜凄。
這樣做不但編譯器不能發(fā)現(xiàn)類型錯(cuò)誤政溃,就連運(yùn)行時(shí)的數(shù)組存儲(chǔ)檢查對(duì)它也無能為力,它能看到的是我們往里面放Arraylist的對(duì)象态秧,我們定義的<String>在這個(gè)時(shí)候已經(jīng)被抹掉了.
//下面的代碼使用了泛型的數(shù)組董虱,是無法通過編譯的
GenTest<String> genArr[] = new GenTest<String>[2];
Object[] test = genArr;
GenTest<StringBuffer> strBuf = new GenTest<StringBuffer>();
strBuf.setValue(new StringBuffer());
test[0] = strBuf;
GenTest<String> ref = genArr[0]; //上面兩行相當(dāng)于使用數(shù)組移花接木键兜,讓Java編譯器把GenTest<StringBuffer>當(dāng)作了GenTest<String>
String value = ref.getValue();// 這里是重點(diǎn)惩坑!
最后一行中,根據(jù)之前講到的泛型邊界問題丐膝,取值的時(shí)候會(huì)是這樣
(String)ref.getValue();所以會(huì)有ClassCastException.這個(gè)程序雖然看起來是程序員的錯(cuò)誤捐友,
而且也沒有什么災(zāi)難性后果淫半。但是從另一個(gè)角度看,泛型就是為了消滅ClassCastException出現(xiàn)的
而這個(gè)時(shí)候他自己卻引發(fā)這個(gè)錯(cuò)誤匣砖,這就矛盾了科吭。通常來說如果使用泛型,只要代碼編譯時(shí)沒有警告猴鲫,那么就不會(huì)出現(xiàn)錯(cuò)誤ClassCaseException对人。
究竟泛型數(shù)組應(yīng)該怎么用,我們可以參考ArrayList的源碼
transient Object[] elementData; // non-private to simplify nested class access
它內(nèi)部使用的就是Object[]
get方法對(duì)item使用了強(qiáng)轉(zhuǎn)变隔,才能讓我們獲取到正確的對(duì)象规伐。
// Positional Access Operations
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
add方法涉及到向上轉(zhuǎn)型
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
事實(shí)證明蟹倾,ArrayList大量使用了強(qiáng)轉(zhuǎn)去實(shí)現(xiàn)匣缘。
總的來說數(shù)組是協(xié)變的而泛型不是,如果 Integer擴(kuò)展了 Number(事實(shí)也是如此)鲜棠,那么不僅 Integer是 Number肌厨,
而且 Integer[]也是 Number[],在要求 Number[]的地方完全可以傳遞或者賦予 Integer[]豁陆。
這也是矛盾發(fā)生的根本原因.
###邊界:
簡(jiǎn)單點(diǎn)就是 <T extends Base>這樣在擦除的時(shí)候T就會(huì)轉(zhuǎn)換成Base,而在泛型類或方法中可以直接視同Base
的方法
public class Response {
public <T extends ABC>void f(T t)
{
t.test();
}
}
class ABC{
public void test()
{
System.out.println("asdasd");
}
}
另一種用法就是
interface has{
void test();
}
public class Ges<T extends Object & has> {
T mT;
public T getmT() {
return mT;
}
public void setmT(T mT) {
this.mT = mT;
}
繼承一個(gè)父類與多個(gè)接口的形式柑爸。和類繼承的用法是一樣的,不過class必須在第一個(gè)盒音,接口跟在后面
同樣表鳍,如果希望將某個(gè)類型限制為特定類型或特定類型的超類型,請(qǐng)使用以下表示法:
<T super LowerBoundType>
###通配符:
泛型沒有內(nèi)建的協(xié)變類型祥诽,有時(shí)候想要在兩個(gè)類型之間建立某種類型的向上轉(zhuǎn)型關(guān)系:這正是通配符允許的
public class Ges {
public static void main(String[] args) {
List<? extends fruit> list=new ArrayList<apple>();
// list.add(new fruit());
// list.add(new apple()); all wrong
fruit fruit=list.get(0);//可以獲取到fruit
}
}
class fruit{
}
class apple extends fruit{
}
盡管list類型是List<? extends fruit>但是這并不實(shí)際意味著List將持有任何類型的fruit譬圣。實(shí)際上你不能往這個(gè)list當(dāng)中安全的添加對(duì)象。盡管他可以合法指向一個(gè)list<apple>雄坪,
你無法往里面丟任何對(duì)象厘熟。編譯器只知道list內(nèi)部的任何對(duì)象至少具有fruit類型,但是他具體是什么就不知道了。
每當(dāng)指定add方法的時(shí)候绳姨,![](http://upload-images.jianshu.io/upload_images/3267534-3002ac82f9d98063.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)顯示的都是null登澜,由于不知道,所以干脆不接受任何類型的fruit飘庄。同樣的編譯器將直接拒絕對(duì)參數(shù)列表中
涉及通配符的方法的調(diào)用脑蠕。
當(dāng)然List<? extends fruit> list=new ArrayList<apple>();這種寫法賦予了泛型一種協(xié)變性,
像之前提到過的:List<fruit> list=new ArrayList<apple>()是無法通過的因?yàn)榧偃邕@么寫跪削,
那就意味著可以list.add(new Banana());這就破壞了list定義時(shí)的承諾空郊,它是一個(gè)蘋果列表。
那么針對(duì)這種情況(無法往內(nèi)部添加)可以使用超類型通配符:<? super Class> 切揭,有了超類型通配符狞甚,你就可以進(jìn)行寫入了:
![](http://upload-images.jianshu.io/upload_images/3267534-dd793ecf68699d44.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
這相當(dāng)于定義了一個(gè)下界,無論你傳入什么廓旬,最起碼是apple,或者是他的子類哼审,這樣的類型傳入是安全的.
這種寫法也可以稱之為逆變,同樣的get方法返回的是Object.
###關(guān)于協(xié)變和逆變:
什么是協(xié)變(逆變):
>如果A和B是類型(T)孕豹,f表示類型轉(zhuǎn)換涩盾,≤表示子類型關(guān)系,(例如A≤B励背,表示A是B的子類)那么:
如果A≤B 則f(A) ≤ f(B) 那么 f是協(xié)變的 (假如說T和f(T)序關(guān)系一致春霍,就是協(xié)變)
如果A≤B 則f(B) ≤ f(A) 那么 f 是逆變的(假如說T和f(T)序關(guān)系相反,就是逆變)
如果上面兩種都不成立叶眉,那么f是無關(guān)的
例如:class List<T>{...}址儒,可以理解為輸入一個(gè)類型參數(shù)T,通過類型轉(zhuǎn)換(f)成為衅疙,List<T>(f(T))
所以當(dāng)A=Object,B=String莲趣,那么f(A)=List<Object>,f(B)=List<String>,可是f(A)與f(B)不具備
任何關(guān)系,所以單純的泛型不具備協(xié)變性饱溢。
所以ArrayList<? extends fruit>=ArrayList<fruit>,(? extends fruit)<=fruit
喧伞,f(? extends fruit)=f(fruit),通過這種寫法具備了協(xié)變性。
同樣ArrayList<? super fruit>=ArrayList<fruit>,(? super fruit)>=fruit绩郎,
f(? extends fruit)=f(fruit),這就是逆變性潘鲫。
#### 為什么要有協(xié)變逆變??jī)?yōu)勢(shì)在哪肋杖?
而根據(jù)里氏替換原則:
>派生類(子類)對(duì)象能夠替換其基類(超類)對(duì)象被使用溉仑。當(dāng)子類覆蓋或?qū)崿F(xiàn)父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松兽愤。
當(dāng)子類的方法實(shí)現(xiàn)父類的抽象方法時(shí)彼念,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格挪圾。
從例子來看:
public class f {
// 定義三個(gè)類: Benz -> Car -> Vehicle,它們之間是順次繼承關(guān)系
// 測(cè)試函數(shù)
void test() {
List<Vehicle> vehicles = new ArrayList<>();
List<Benz> benzs = new ArrayList<>();
Utils<Car> carUtils = new Utils<>();
carUtils.put(vehicles, new Car());
Car car = carUtils.get(benzs, 0);
carUtils.copy(vehicles, benzs);
}
}
class Vehicle {}
class Car extends Vehicle {}
class Benz extends Car {}
// 定義一個(gè)util類逐沙,其中用到泛型里的協(xié)變和逆變
class Utils<T> {
T get(List<? extends T> list, int i) {
return list.get(i);
}
void put(List<? super T> list, T item) {
list.add(item);
}
void copy(List<? super T> to, List<? extends T> from) {
for(T item : from) {
to.add(item);
}
}
}
Car car = carUtils.get(benzs, 0);可以看出 List<Benz>對(duì)List<? extends Car>進(jìn)行了替換(協(xié)變)哲思,
List<Benz>的get方法返回Benz對(duì)象,而List<? extends Car>返回的是Car對(duì)象吩案,這符合替換原則棚赔,方法的后置條件(即方法的返回值)要比父類更嚴(yán)格。
carUtils.put(vehicles, new Car());List<Vehicle>對(duì)List<? super Car>進(jìn)行替換(逆變)徘郭,
List<Vehicle>的put方法需要Vehicle對(duì)象作為形參靠益,而List<? super Car>需要的是Car,這就滿足替換原則
的前置條件需求.
最后一個(gè)copy體現(xiàn)的是協(xié)變與逆變的匯總,替換残揉。所以總的來說泛型的協(xié)變與逆變定義上界與下界胧后,同時(shí)也讓程序
能夠在某種程度上滿足替換原則,通過良好的替換讓程序更具拓展性抱环。這也是為什么大量的框架中
例如rxjava壳快,會(huì)在參數(shù)中大量使用這種形式的泛型寫法。
參考:
Thinking in java
http://ybin.cc/programming/java-variance-in-generics/
https://www.zybuluo.com/zhanjindong/note/34147
http://www.cnblogs.com/en-heng/p/5041124.html
http://colobu.com/2015/05/19/Variance-lower-bounds-upper-bounds-in-Scala/#Java中的協(xié)變和逆變
http://blog.csdn.net/hopeztm/article/details/8822545
https://zh.wikipedia.org/wiki/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99
https://www.ibm.com/developerworks/cn/java/j-jtp01255.html
https://www.zhihu.com/question/20928981
http://cs.au.dk/~mis/dOvs/jvmspec/ref-Java.html
http://www.blogjava.net/deepnighttwo/articles/298426.html