預(yù)熱:泛型

本文大量參考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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末镇草,一起剝皮案震驚了整個(gè)濱河市眶痰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梯啤,老刑警劉巖竖伯,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異因宇,居然都是意外死亡七婴,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門羽嫡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來本姥,“玉大人,你說我怎么就攤上這事杭棵。” “怎么了氛赐?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵魂爪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我艰管,道長(zhǎng)滓侍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任牲芋,我火速辦了婚禮撩笆,結(jié)果婚禮上捺球,老公的妹妹穿的比我還像新娘。我一直安慰自己夕冲,他們只是感情好氮兵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著歹鱼,像睡著了一般泣栈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上弥姻,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天南片,我揣著相機(jī)與錄音,去河邊找鬼庭敦。 笑死疼进,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的秧廉。 我是一名探鬼主播颠悬,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼定血!你這毒婦竟也來了赔癌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤澜沟,失蹤者是張志新(化名)和其女友劉穎灾票,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體茫虽,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刊苍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了濒析。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片正什。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖号杏,靈堂內(nèi)的尸體忽然破棺而出婴氮,到底是詐尸還是另有隱情,我是刑警寧澤盾致,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布主经,位于F島的核電站,受9級(jí)特大地震影響庭惜,放射性物質(zhì)發(fā)生泄漏罩驻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一护赊、第九天 我趴在偏房一處隱蔽的房頂上張望惠遏。 院中可真熱鬧砾跃,春花似錦、人聲如沸节吮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽课锌。三九已至厨内,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間渺贤,已是汗流浹背雏胃。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留志鞍,地道東北人瞭亮。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像固棚,于是被迫代替她去往敵國(guó)和親统翩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 第8章 泛型 通常情況的類和函數(shù)此洲,我們只需要使用具體的類型即可:要么是基本類型厂汗,要么是自定義的類。但是在集合類的場(chǎng)...
    光劍書架上的書閱讀 2,143評(píng)論 6 10
  • 引言 泛型是Java中一個(gè)非常重要的知識(shí)點(diǎn)呜师,在Java集合類框架中泛型被廣泛應(yīng)用娶桦。本文我們將從零開始來看一下Jav...
    橫沖直撞666閱讀 424評(píng)論 0 0
  • 2.簡(jiǎn)單泛型 -********Java泛型的核心概念:告訴編譯器想使用什么類型, 然后編譯器幫你處理一切細(xì)節(jié) 2...
    CodingHou閱讀 388評(píng)論 0 0
  • 開發(fā)人員在使用泛型的時(shí)候,很容易根據(jù)自己的直覺而犯一些錯(cuò)誤汁汗。比如一個(gè)方法如果接收List作為形式參數(shù)衷畦,那么如果嘗試...
    時(shí)待吾閱讀 1,042評(píng)論 0 3
  • 登高 杜甫 風(fēng)急天高猿嘯哀,渚[1]清沙白鳥飛回知牌。 無邊落木[2]蕭蕭[3]下祈争,不盡長(zhǎng)江滾滾來。 萬里悲秋常作客角寸,...
    古詩新讀閱讀 436評(píng)論 0 2