Java泛型

1、基本應(yīng)用

Java泛型可以用在類、接口和方法上探入〗瓶祝基本使用請(qǐng)參考《on Java 8》.

2、類型擦除

? 泛型時(shí)Java 5以后添加進(jìn)去的新症,為了兼容以前的版本步氏,相對(duì)于其他泛型語(yǔ)言,Java用特別的方法實(shí)現(xiàn)了泛型徒爹,這實(shí)現(xiàn)了Java向上兼容的功能荚醒,但同時(shí)也限制了Java泛型的功能。這個(gè)特別方法就是類型擦除了隆嗅。

? 類型擦除并不是Java語(yǔ)言特性界阁,它是通過編譯器來實(shí)現(xiàn)泛型的。編譯器在做靜態(tài)檢測(cè)時(shí)胖喳,通過一系列的處理來保證泛型實(shí)現(xiàn)泡躯,在靜態(tài)類型檢查之后,程序中的每個(gè)泛型類型都將被擦除丽焊,用非泛型的第一個(gè)邊界替換它(可能會(huì)有多個(gè)邊界)较剃。因此,在泛型類/接口/方法里面技健,你并不能得到關(guān)于類型參數(shù)的具體信息写穴,你能知道的是類型參數(shù)的表示符和他的邊界類型(默認(rèn)邊界時(shí)Object)。例如List<T>被擦除為L(zhǎng)ist雌贱,接口/類中的每個(gè)泛型變量/參數(shù)/返回值類型都被擦除到非泛型邊界啊送,默認(rèn)邊界為Object,List<T>沒有上界欣孤,被擦除為Object馋没。所以,ArrayList<String>和ArrayList<Integer>是一種類型降传。他們都被擦除為原始類型List篷朵。

? 類型的擦除的代價(jià)是巨大的。泛型類型不能用于顯式引用運(yùn)行時(shí)類型的操作婆排,如強(qiáng)制轉(zhuǎn)換款票、instanceof操作和new表達(dá)式。因?yàn)樗嘘P(guān)于參數(shù)的類型信息都丟失了泽论。在編寫泛型代碼時(shí)艾少,你只知道有這么一個(gè)類型參數(shù),以及這個(gè)類型參數(shù)的類型上界是什么翼悴,并不知道這個(gè)參數(shù)的具體信息缚够。在使用泛型類時(shí)幔妨,您必須不斷地提醒自己,你只是看起來好像知道有關(guān)參數(shù)的具體信息而已谍椅。

? 比如:ArrayList<String> list = new ArrayList<>()误堡,在使用list時(shí),你好像是知道它是一個(gè)String數(shù)組雏吭,而泛型語(yǔ)法也在強(qiáng)烈暗示你锁施,在整個(gè)類的各個(gè)地方,類型E都被替換了杖们。但是事實(shí)并不是如此悉抵。在ArrayList類的編寫過程中,你實(shí)際上知道的只是摘完,有個(gè)參數(shù)類型是E姥饰,他的類型上界是Object。

? 那么問題來了孝治,既然在運(yùn)行時(shí)ArrayList<String>的類型信息被擦除了列粪,那么我們?cè)谟胓et方法時(shí),得到對(duì)象時(shí)一個(gè)String谈飒,不用cast岂座,且我們用set方法時(shí),參數(shù)必須時(shí)一個(gè)字符串杭措,或者能調(diào)用toString方法的對(duì)象呢费什。下面我們先探討另外一個(gè)問題。泛型的邊界問題瓤介。

3、泛型的出入口

? 泛型類型可以時(shí)無任何意義的字符赘那,比如E刑桑。在靜態(tài)檢測(cè)后,類型E就被擦除為非泛型上界了募舟,所以在運(yùn)行時(shí)祠斧,時(shí)無法知道泛型的具體信息的。

import java.util.*;
public class ListMaker<T> {
    private Class<T> kind;
    @SuppressWarnings("unchecked")
    T[] createArray(int size) {
        return (T[])Array.newInstance(kind, size);
    }
    List<T> createList() { return new ArrayList<>(); }
    public static void main(String[] args) {
         ListMaker<String> stringMaker = new ListMaker<>();
         String[] stringArray = stringMaker.createArray(9);
        List<String> stringList = stringMaker.createList();
    }
}

? 上面的代碼段中拱礁,T會(huì)被編譯器擦除琢锋,Class<T>最終存儲(chǔ)的只是Class,new ArrayList<>()變?yōu)閚ew ArrayList()呢灶。

? 在createArray()方法中吴超,Array.newInstance()方法不知道T的任何信息,所以在運(yùn)行時(shí)鸯乃,并不能產(chǎn)生T類型的數(shù)組鲸阻,所以需要轉(zhuǎn)cast。

? createList方法中,盡管最終鸟悴,new ArrayList<>()變?yōu)閚ew ArrayList()陈辱,但是如果我們把new ArrayList<>()直接更換位new ArrayList(),編譯器就會(huì)給出警告细诸。

? 所以類型T不是沒有意義的沛贪,那么它的意義在哪呢?看下面代碼震贵。

import java.util.*;
import java.util.function.*;
import onjava.*;
public class FilledList<T> extends ArrayList<T> {
    FilledList(Supplier<T> gen, int size) {
        Suppliers.fill(this, gen, size);
    }
    public FilledList(T t, int size) {
        for(int i = 0; i < size; i++)
            this.add(t);
    }
    public static void main(String[] args) {
        List<String> list = new FilledList<>("Hello", 4);
        System.out.println(list);
        // Supplier version:
        List<Integer> ilist = new FilledList<>(() -> 47, 4);
        System.out.println(ilist);
    }
}

? 在main方法中利赋,我們?cè)趧?chuàng)建FilledList的同時(shí)往list里面添加對(duì)象,雖然編譯器并不知道關(guān)于T的任何東西屏歹,但是編譯器在編譯期間能確保放到FilledList里面的類型時(shí)T隐砸。所以,雖然在方法/類/接口里面T的相關(guān)信息被擦除了蝙眶,編譯器仍然可以確保方法或類中使用類型的方式的內(nèi)部一致性季希。換句話說,編譯器能保證泛型在其作用域內(nèi)使用方式一致性幽纷。泛型作用域式塌,如果聲名在接口/類上,那么就是在類和類方法的內(nèi)部友浸,如果是靜態(tài)方法峰尝,那就是靜態(tài)方法內(nèi)部。

? 擦除雖然刪除了方法主體中的類型信息收恢,但是編譯器能保證泛型作用域內(nèi)使用方式的一致性武学,那么泛型進(jìn)出作用域時(shí),編譯器是這么保證它的一致性呢伦意?接口/類/方法運(yùn)行時(shí)邊界:泛型對(duì)象進(jìn)入和離開主體的點(diǎn)火窒。

? 接口/類的進(jìn)出點(diǎn):field的取值和賦值,方法的參數(shù)和返回值驮肉。

? 方法的進(jìn)出口:參數(shù)和返回值熏矿。

? 編譯器在編譯時(shí)執(zhí)行類型檢查并插入類型轉(zhuǎn)換代碼。這也是編譯器保證泛型類/方法內(nèi)外泛型使用一致性的方式离钝。

下面的代碼:

public class GenericHolder2<T> {
  //  靜態(tài)變量不能使用泛型
  // public static T test;
  public T genericBound;
  private T obj;
  public void set(T obj) { this.obj = obj; }
  public T get() { return obj; }
  public static void main(String[] args) {
    GenericHolder2<String> holder =
      new GenericHolder2<>();
    holder.set("Item");
    String genericBound = holder.genericBound;
    String s = holder.get();
  }
}

用javap命令反編譯上面代碼的字節(jié)碼:

PS D:\Study\githup\helloWorld\sourceCode\testMaven\onjava8\src\main\java\generics> javap -s -c -private  GenericHolder2
警告: 二進(jìn)制文件GenericHolder2包含generics.GenericHolder2
Compiled from "GenericHolder2.java"
public class generics.GenericHolder2<T> {
  public T genericBound;
    descriptor: Ljava/lang/Object;

  private T obj;
    descriptor: Ljava/lang/Object;

  public generics.GenericHolder2();
    descriptor: ()V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void set(T);
    descriptor: (Ljava/lang/Object;)V
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #2                  // Field obj:Ljava/lang/Object;
       5: return

  public T get();
    descriptor: ()Ljava/lang/Object;
    Code:
       0: aload_0
       1: getfield      #2                  // Field obj:Ljava/lang/Object;
       4: areturn

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    Code:
       0: new           #3                  // class generics/GenericHolder2
       3: dup
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: ldc           #5                  // String Item
      11: invokevirtual #6                  // Method set:(Ljava/lang/Object;)V
      14: aload_1
      15: getfield      #7                  // Field genericBound:Ljava/lang/Object;
      18: checkcast     #8                  // class java/lang/String
      21: astore_2
      22: aload_1
      23: invokevirtual #9                  // Method get:()Ljava/lang/Object;
      26: checkcast     #8                  // class java/lang/String
      29: astore_3
      30: return
}

? 我們可以看到票编,發(fā)編譯后,類變量T的descriptor是Ljava/lang/Object卵渴,即他是引用類型Object慧域;方法set的descriptor是(Ljava/lang/Object;)V,即參數(shù)是object浪读,無返回值吊趾;方法get的descriptor是()Ljava/lang/Object宛裕,無參數(shù),返回object類型论泛。在編譯階段揩尸,會(huì)做類型檢測(cè),如果類內(nèi)部T的使用方式不一致屁奏,將報(bào)錯(cuò)岩榆。

? 既然變量genericBound,get方法返回的是object類型坟瓢,那我們?cè)谑褂玫臅r(shí)候勇边,返回值的接受類型又怎么變?yōu)檫\(yùn)行時(shí)指定的類型呢,在main方法中我們可以看到折联,在getfield和invokevirtual(get)命名后都緊跟著類型轉(zhuǎn)換命令粒褒,checkcast,這個(gè)時(shí)編譯階段編譯器自動(dòng)添加的诚镰。但是set方法調(diào)用呢奕坟,我們并沒有發(fā)現(xiàn)set方法調(diào)用時(shí)又上面特殊操作保證他是String啊清笨?其實(shí)月杉,這個(gè)工作時(shí)在編譯階段類型檢測(cè)所作的事情,類型檢測(cè)保證了set方法的參數(shù)時(shí)object抠艾,所以反編譯后就不需要特殊處理了苛萎。

? 利用類型檢測(cè),編譯器保證了方法調(diào)用參數(shù)等類入口的泛型一致性检号,以及類內(nèi)部泛型使用方式的一致性腌歉。利用cast自動(dòng)插入,保證了方法調(diào)用返回值等泛型出口的泛型一致性齐苛。

4翘盖、泛型的補(bǔ)償

? 在類/方法內(nèi)部,泛型擦除了方法信息脸狸,這就限制了泛型的使用最仑。比如藐俺,不能instanceof操作炊甲,不能使用new,不能創(chuàng)建數(shù)組(可以聲名泛型數(shù)組)欲芹,這些都可以通過適當(dāng)?shù)姆椒ㄟM(jìn)行彌補(bǔ)卿啡。

? instanceOf可以用isInstance()方法替代。

? new的替代方式:

  • 利用Class對(duì)象的newInstance()方法菱父,該方法要保證class有無參構(gòu)造器
  • 提供一個(gè)工廠對(duì)象(supplier)颈娜,利用lamda表達(dá)式的::new

? 數(shù)組的創(chuàng)建方式:

  • 用List替代
  • (T[])new Object[sz]剑逃。內(nèi)部維護(hù)T[],創(chuàng)建一個(gè)Object[]數(shù)組進(jìn)行強(qiáng)轉(zhuǎn)官辽。但是這種方法如果把數(shù)組暴露給外部蛹磺,接受對(duì)象如果不是Object[],會(huì)產(chǎn)生ClassCastException同仆,因?yàn)楦笇?duì)象時(shí)不能賦值給子對(duì)象的萤捆,這條規(guī)則同樣適用于數(shù)組。所以此方法只使用數(shù)組只是內(nèi)部使用的情況俗批。為什么(T[])new Object[sz]這里可以強(qiáng)轉(zhuǎn)俗或,用其他數(shù)組接收卻不可以,因?yàn)門最終被擦除了岁忘,變?yōu)榱薕bject辛慰,所以實(shí)際上(T[])new Object[sz]等價(jià)于(Object[])new Object[sz]。
  • new Object[sz]干像,內(nèi)部維護(hù)的數(shù)組就是Object[]帅腌,在獲取數(shù)組原始后進(jìn)行類型轉(zhuǎn)換,這個(gè)方法需要在每個(gè)獲取數(shù)組元素的地方進(jìn)行強(qiáng)轉(zhuǎn)蝠筑。這個(gè)方法暴露數(shù)組對(duì)象給外部的時(shí)候只能用Object[]接收狞膘,依然不能轉(zhuǎn)換為其他對(duì)象。將數(shù)組內(nèi)部處理為Object[]而不是T[]的優(yōu)點(diǎn)是什乙,您不太可能忘記數(shù)組的運(yùn)行時(shí)類型挽封,從而意外地引入錯(cuò)誤(盡管在運(yùn)行時(shí)可以快速檢測(cè)到大多數(shù)(也許所有)此類bug)。
  • (T[])Array.newInstance(type, sz);這個(gè)是創(chuàng)建泛型數(shù)組的較好的選擇臣镣。創(chuàng)建的數(shù)組就是實(shí)際類型的數(shù)組辅愿,不必?fù)?dān)心類型轉(zhuǎn)換,還可以將數(shù)組暴露給外部忆某,并用真正類型數(shù)組接收点待。

5、邊界

? 邊界是將泛型參數(shù)約束到一個(gè)子類型弃舒,然后可以使用被約束的子類型的功能癞埠。

package generics;// generics/BasicBounds.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.

interface HasColor {
    java.awt.Color getColor();
}

class WithColor<T extends HasColor> {
    T item;
    WithColor(T item) { this.item = item; }
    T getItem() { return item; }

    // The bound allows you to call a method:
    java.awt.Color color() {  return item.getColor(); }
}

class Coord {
    public int x, y, z;
}

// This fails. Class must be first, then interfaces:
// class WithColorCoord<T extends HasColor & Coord> {
// Multiple bounds:
class WithColorCoord<T extends Coord & HasColor> {
    T item;

    WithColorCoord(T item) { this.item = item; }
    T getItem() { return item; }
    java.awt.Color color() {  return item.getColor(); }
    int getX() {  return item.x; }
    int getY() { return item.y; }
    int getZ() { return item.z; }
}

interface Weight {
    int weight();
}

// As with inheritance, you can have only one
// concrete class but multiple interfaces:
class Solid<T extends Coord & HasColor & Weight> {
    T item;

    Solid(T item) { this.item = item; }
    T getItem() { return item; }
    java.awt.Color color() { return item.getColor(); }
    int getX() { return item.x; }
    int getY() { return item.y; }
    int getZ() {  return item.z; }
    int weight() { return item.weight(); }
}

class Bounded extends Coord implements HasColor, Weight {
    @Override
    public java.awt.Color getColor() { return null; }

    @Override
    public int weight() { return 0; }
}

public class BasicBounds {
    public static void main(String[] args) {
        Solid<Bounded> solid = new Solid<>(new Bounded());
        solid.color();
        solid.getY();
        solid.weight();
    }
}

我們將Solid類進(jìn)行反編譯如下:

PS D:\Study\githup\helloWorld\sourceCode\testMaven\onjava8\src\main\java\generics> javap -c -s -private Solid
警告: 二進(jìn)制文件Solid包含generics.Solid
Compiled from "BasicBounds.java"
class generics.Solid<T extends generics.Coord & generics.HasColor & generics.Weight> {
  T item;
    descriptor: Lgenerics/Coord;

  generics.Solid(T);
    descriptor: (Lgenerics/Coord;)V
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: putfield      #2                  // Field item:Lgenerics/Coord;
       9: return

  T getItem();
    descriptor: ()Lgenerics/Coord;
    Code:
       0: aload_0
       1: getfield      #2                  // Field item:Lgenerics/Coord;
       4: areturn

  java.awt.Color color();
    descriptor: ()Ljava/awt/Color;
    Code:
       0: aload_0
       1: getfield      #2                  // Field item:Lgenerics/Coord;
       4: checkcast     #3                  // class generics/HasColor
       7: invokeinterface #4,  1            // InterfaceMethod generics/HasColor.getColor:()Ljava/awt/Color;
      12: areturn

  int getX();
    descriptor: ()I
    Code:
       0: aload_0
       1: getfield      #2                  // Field item:Lgenerics/Coord;
       4: getfield      #5                  // Field generics/Coord.x:I
       7: ireturn

  int getY();
    descriptor: ()I
    Code:
       0: aload_0
       1: getfield      #2                  // Field item:Lgenerics/Coord;
       4: getfield      #6                  // Field generics/Coord.y:I
       7: ireturn

  int getZ();
    descriptor: ()I
    Code:
       0: aload_0
       1: getfield      #2                  // Field item:Lgenerics/Coord;
       4: getfield      #7                  // Field generics/Coord.z:I
       7: ireturn

  int weight();
    descriptor: ()I
    Code:
       0: aload_0
       1: getfield      #2                  // Field item:Lgenerics/Coord;
       4: checkcast     #8                  // class generics/Weight
       7: invokeinterface #9,  1            // InterfaceMethod generics/Weight.weight:()I
      12: ireturn
}

? 可以看到T已經(jīng)被擦除到了Coord。而在類中聋呢,可以使用Coord/HasColor/Weight的屬性和方法了苗踪。但是同時(shí),你使用Solid的時(shí)候削锰,他的泛型參數(shù)必須是Coord/HasColor/Weight的共同子類通铲。這就是邊界的限制。

? 可是T被擦除到了Coord器贩,為什么可以使用HasColor/Weight的屬性和方法呢颅夺?在反編譯代碼中可以看到朋截,在調(diào)用HasColor和Weight的時(shí)候,編譯器插入了強(qiáng)轉(zhuǎn)代碼吧黄,把Coord強(qiáng)轉(zhuǎn)為了HasColor/Weight部服。

上面的類可以利用繼承重寫,簡(jiǎn)化相關(guān)代碼:

class HoldItem<T> {
  T item;
  HoldItem(T item) { this.item = item; }
  T getItem() { return item; }
}

class WithColor2<T extends HasColor> extends HoldItem<T> {
  WithColor2(T item) { super(item); }
  java.awt.Color color() { return item.getColor(); }
}

class WithColorCoord2<T extends Coord & HasColor> extends WithColor2<T> {
  WithColorCoord2(T item) {  super(item); }
  int getX() { return item.x; }
  int getY() { return item.y; }
  int getZ() { return item.z; }
}

class Solid2<T extends Coord & HasColor & Weight>
extends WithColorCoord2<T> {
  Solid2(T item) {  super(item); }
  int weight() { return item.weight(); }
}

public class InheritBounds {
  public static void main(String[] args) {
    Solid2<Bounded> solid2 =
      new Solid2<>(new Bounded());
    solid2.color();
    solid2.getY();
    solid2.weight();
  }
}

6拗慨、通配符

? 我們指定在數(shù)組中饲宿,子類型的數(shù)組Sub[]是可以賦值給父類型的數(shù)組Sup[]。因?yàn)镾ub是Sup的子類胆描,而數(shù)組是有他的元素的具體信息的瘫想,比如Sub[]數(shù)組知道自己存放的元素就是Sub。子類型可以放到一個(gè)父類型數(shù)組中昌讲,不管父數(shù)組的實(shí)際類型是啥国夜,但是如果父類型的實(shí)際類型時(shí)子類型2,你如果此時(shí)把子類型1放到父類型數(shù)組中短绸,在編譯器沒事车吹,在運(yùn)行時(shí)會(huì)報(bào)錯(cuò)。數(shù)組的這個(gè)規(guī)則如果濫用醋闭,會(huì)導(dǎo)致一些錯(cuò)誤窄驹。泛型可以解決這個(gè)問題。

? 但是在泛型中证逻,因?yàn)椴脸植海盒皖愂遣恢婪盒偷木唧w信息的。所以囚企,雖然Sub時(shí)Sup的子類丈咐,但是Sub的集合ArrayList<Sub>不是Sub的集合ArrayList<Sup>的子類,所以ArrayList<Sub>不能"向上轉(zhuǎn)型"為ArrayList<Sup>龙宏!因?yàn)榉盒皖愒趈ava中只有一種類型棵逊,那就是原型ArrayList,ArrayList<Sub>和ArrayList<Sup>都會(huì)被擦除為ArrayList银酗。所以這樣的賦值操作是不可以的辆影,因?yàn)锳rrayList并不知道泛型參數(shù)的實(shí)際類型的具體信息。

? 但是黍特,有時(shí)候我們又需要集合的"向上轉(zhuǎn)型"這種操作蛙讥,來建立兩類集合的這種關(guān)系。比如接收一個(gè)方法返回值的時(shí)候衅澈。泛型通過另一個(gè)方法實(shí)現(xiàn)了這個(gè)功能键菱,那就是參數(shù)通配符"?”谬墙。

? extends T

? List<? extends Fruit>今布,您可以將其讀作“從水果繼承的任何類型的列表”经备。然而,這并不意味著這個(gè)清單里會(huì)有任何種類的水果部默。通配符指的是一個(gè)確定的類型侵蒙,所以它的意思是list引用沒有指定的某個(gè)特定類型”。因此傅蹂,被分配的列表必須包含某些特定類型纷闺,如Fruit或Apple,但是list并不關(guān)心它是由那種子類"向上轉(zhuǎn)型"過來的份蝴。

? 那么你用List<? extends Fruit>接收到一個(gè)List以后犁功,你能干什么呢?前面說過婚夫,通配符是為了解決數(shù)組“向上轉(zhuǎn)型”的問題的浸卦。這個(gè)是什么問題呢?看下面的代碼:

    List<Orange> oranges = new ArrayList<>();
    List<? extends Fruit> flist = oranges;
    // flist.add(new Apple()) 編譯錯(cuò)誤
    Orange orange = oranges.get(0);

? 你現(xiàn)在知道的是flist中保存的是一種Fruit的子類型案糙,但是不知道是哪一種類型限嫌。如果第三行代碼,向flist添加一個(gè)apple成功时捌,那么第四行代碼orange實(shí)際上拿到的就是一個(gè)apple怒医,這個(gè)就會(huì)造成類型轉(zhuǎn)換異常,把list換位數(shù)組奢讨,就是我說的數(shù)組問題稚叹。為了解決這個(gè)異常,編譯器直接再編譯階段就給出了錯(cuò)誤提示拿诸。

? 所以List<? extends Sup>禁止你往作用域中添加null之外的任何元素入录。這里注意,flist.add(new Apple())無法編譯的原因是ArrayList中佳镜,add方法的參數(shù)是泛型僚稿。

? 也就是說,如果一個(gè)泛型類蟀伸,你指定他的泛型是? extends Sup蚀同,那么在任何泛型作用域內(nèi),任何泛型入口都是被編譯器禁止的啊掏。

? 但是你可以從flist中獲取一個(gè)元素蠢络,獲取的元素類型是fruit。

? 從泛型擦除的概念理解:根據(jù)擦除的規(guī)則迟蜜,? extends T會(huì)被查出到第一個(gè)邊界刹孔,也就是T。所以娜睛,在進(jìn)出口邊界髓霞,泛型會(huì)被T類型所替換卦睹。進(jìn)行相應(yīng)強(qiáng)轉(zhuǎn)和類型檢測(cè)。在出口處方库,因?yàn)? extends T是T的一個(gè)子類型结序,所以強(qiáng)轉(zhuǎn)為T是沒問題的。但是在入口處進(jìn)行檢測(cè)時(shí)纵潦,編譯器知道泛型類要求T的一種子類型徐鹤,但不知道具體時(shí)哪一種子類型,所以無論你傳什么邀层,為了安全起見返敬,他都會(huì)拒絕你傳入。

? 所以寥院,有一種說法救赐,? extends Sup是消費(fèi)者泛型。

? Supper T

? 那么我們?cè)趺聪蛲ㄅ浞盒屠锩尜x值呢只磷?與extends相對(duì)的经磅,泛型中也有supper關(guān)鍵字,<? super MyClass>或者<? super T>钮追,意思是一個(gè)類型预厌,他是MyClass/T的父類,這個(gè)類型可以通過泛型入口進(jìn)入泛型作用域元媚。這樣轧叽,泛型在往里面增加值的時(shí)候,只要保證增加的都是MyClass/T的子類就行刊棕。

? 根據(jù)泛型擦除規(guī)則炭晒,? Supper T被擦除為T。所以甥角,在進(jìn)出口邊界网严,泛型會(huì)被T類型所替換。進(jìn)行相應(yīng)強(qiáng)轉(zhuǎn)和類型檢測(cè)嗤无。在出口處震束,因?yàn)? super T是T的一個(gè)父類型,編譯器并不知道T是什么類型当犯,為了安全起見垢村,所以將T強(qiáng)轉(zhuǎn)為了Object。在入口處進(jìn)行檢測(cè)時(shí)嚎卫,編譯器知道泛型類要求T的一種父類型嘉栓,但不知道具體時(shí)哪一種父類型,為了安全起見,它要求你傳一個(gè)大范圍的類型侵佃,就是T或者T的子類型麻昼。

? 所以,有一種說法趣钱,? extends Sup是生產(chǎn)者泛型。

?

? 這個(gè)通配符使用效果和使用原泛型效果一樣胚宦,即:ArrayList<?>等價(jià)于ArrayList首有。

? 它有一個(gè)作用就是泛型類型推斷轉(zhuǎn)換,就是說你如果將一個(gè)原類型類實(shí)例傳入一個(gè)參數(shù)為無界通配符的方法中枢劝,那么編譯器可以推斷出原類型的泛型的實(shí)際類型井联,這個(gè)方法可以用具體類型調(diào)用另一個(gè)泛型方法。

? 這個(gè)功能點(diǎn)沒做深究您旁,有機(jī)會(huì)補(bǔ)充烙常。

7、泛型缺點(diǎn):

  • 不能使用基本類型鹤盒。
  • 不能同時(shí)實(shí)現(xiàn)兩個(gè)相同的泛型接口蚕脏,即使他們的泛型不同。
  • 不能使用instanceof
  • 兩個(gè)方法侦锯,除了參數(shù)的泛型類型不同外驼鞭,其他都相同,他們的方法簽名相同尺碰,不算重載挣棕。

8、總結(jié)

? 泛型擦除有兩種亲桥,一種是定義擦除洛心,一種是使用擦除。這兩種擦除都又都包含兩種:邊界擦除和非邊界擦除题篷。

? 非邊界查出是在使用的時(shí)候直接指定了泛型類的具體類型词身,不使用extend和super關(guān)鍵字,編譯器在你使用泛型類的地方將泛型擦除為你指定的類型番枚。

? 邊界擦除是指含有extend和super關(guān)鍵字的泛型的擦除偿枕,extend和supper關(guān)鍵字為了約束泛型的類型并使用被約束類型的功能。邊界擦除將泛型擦除為第一個(gè)邊界類型户辫。

? 知道了邊界查出和非邊界擦除渐夸,我們?cè)倩貋砜炊x擦除和使用擦除。

? 定義擦除:是指編譯器在編譯定義的泛型類時(shí)渔欢,將泛型類總的泛型(邊界和非邊界)的類型信息擦除墓塌,并做相應(yīng)的類型檢測(cè)。

? 使用擦除:是指在使用泛型類的時(shí)候,將你指定的放心的具體類型(邊界和非邊界)查出苫幢,再在泛型類的入口做相應(yīng)的類型檢測(cè)访诱,在出口做強(qiáng)轉(zhuǎn)。

? 使用擦除中有一種比較特殊的情況韩肝,那就是使用通配符"?"的使用触菜,通配符只用在使用泛型使用。? extend T哀峻,這種通配符邊界擦除涡相,會(huì)在泛型出口的所有地方將泛型擦除為T,并強(qiáng)轉(zhuǎn)剩蟀,在入口處進(jìn)行類型檢測(cè)的時(shí)候直接報(bào)錯(cuò)催蝗。? super T育特,會(huì)在泛型出口的所有地方將泛型擦除為Object丙号,并強(qiáng)轉(zhuǎn)為Object,在入口做類型檢測(cè)缰冤,保證入口時(shí)T或T子類型犬缨。?會(huì)在泛型出口的所有地方將泛型擦除為Object棉浸,并強(qiáng)轉(zhuǎn)為Object遍尺,在入口在入口做類型檢測(cè),保證他是Object類型即可涮拗,所以乾戏?和不適用泛型的原類型時(shí)相同的效果。

參考資料

《on Java 8》 Bruce Eckel三热。注:Java編程思想第五版鼓择。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市就漾,隨后出現(xiàn)的幾起案子呐能,更是在濱河造成了極大的恐慌,老刑警劉巖抑堡,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摆出,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡首妖,警方通過查閱死者的電腦和手機(jī)偎漫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來有缆,“玉大人象踊,你說我怎么就攤上這事温亲。” “怎么了杯矩?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵栈虚,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我史隆,道長(zhǎng)魂务,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任泌射,我火速辦了婚禮粘姜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘魄幕。我一直安慰自己相艇,他們只是感情好颖杏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布纯陨。 她就那樣靜靜地躺著,像睡著了一般留储。 火紅的嫁衣襯著肌膚如雪翼抠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天获讳,我揣著相機(jī)與錄音阴颖,去河邊找鬼。 笑死丐膝,一個(gè)胖子當(dāng)著我的面吹牛量愧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帅矗,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼偎肃,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了浑此?” 一聲冷哼從身側(cè)響起累颂,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凛俱,沒想到半個(gè)月后紊馏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蒲犬,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年朱监,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片原叮。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赌朋,死狀恐怖凰狞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沛慢,我是刑警寧澤赡若,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站团甲,受9級(jí)特大地震影響逾冬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜躺苦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一身腻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧匹厘,春花似錦嘀趟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至炕柔,卻和暖如春酌泰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匕累。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工陵刹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人欢嘿。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓衰琐,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親炼蹦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子羡宙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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