亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Java? 教程(類型擦除)

zsy888 / 2818人閱讀

類型擦除

泛型被引入到Java語言中,以便在編譯時提供更嚴(yán)格的類型檢查并支持通用編程,為了實(shí)現(xiàn)泛型,Java編譯器將類型擦除應(yīng)用于:

如果類型參數(shù)是無界的,則用它們的邊界或Object替換泛型類型中的所有類型參數(shù),因此,生成的字節(jié)碼僅包含普通的類、接口和方法。

如有必要,插入類型轉(zhuǎn)換以保持類型安全。

生成橋接方法以保留擴(kuò)展泛型類型中的多態(tài)性。

類型擦除確保不為參數(shù)化類型創(chuàng)建新類,因此,泛型不會產(chǎn)生運(yùn)行時開銷。

泛型類型擦除

在類型擦除過程中,Java編譯器將擦除所有類型參數(shù),并在類型參數(shù)有界時將其每一個替換為第一個邊界,如果類型參數(shù)為無界,則替換為Object。

考慮以下表示單鏈表中節(jié)點(diǎn)的泛型類:

public class Node {

    private T data;
    private Node next;

    public Node(T data, Node next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

因?yàn)轭愋蛥?shù)T是無界的,所以Java編譯器用Object替換它:

public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}

在以下示例中,泛型Node類使用有界類型參數(shù):

public class Node> {

    private T data;
    private Node next;

    public Node(T data, Node next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

Java編譯器將有界類型參數(shù)T替換為第一個邊界類Comparable

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}
泛型方法擦除

Java編譯器還會擦除泛型方法參數(shù)中的類型參數(shù),考慮以下泛型方法:

// Counts the number of occurrences of elem in anArray.
//
public static  int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

因?yàn)?b>T是無界的,所以Java編譯器用Object替換它:

public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

假設(shè)定義了以下類:

class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

你可以編寫一個泛型方法來繪制不同的形狀:

public static  void draw(T shape) { /* ... */ }

Java編譯器將T替換為Shape

public static void draw(Shape shape) { /* ... */ }
類型擦除和橋接方法的影響

有時類型擦除會導(dǎo)致你可能沒有預(yù)料到的情況,以下示例顯示了如何發(fā)生這種情況,該示例(在橋接方法中描述)顯示了編譯器有時如何創(chuàng)建一個稱為橋接方法的合成方法,作為類型擦除過程的一部分。

給出以下兩個類:

public class Node {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {
    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

考慮以下代碼:

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData("Hello");     
Integer x = mn.data;    // Causes a ClassCastException to be thrown.

類型擦除后,此代碼變?yōu)椋?/p>

MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         // A raw type - compiler throws an unchecked warning
n.setData("Hello");
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.

以下是代碼執(zhí)行時發(fā)生的情況:

n.setData("Hello")導(dǎo)致方法setData(Object)在類MyNode的對象上執(zhí)行(MyNode類從Node繼承了setData(Object))。

setData(Object)的方法體中,n引用的對象的data字段被分配給String。

通過mn引用的同一對象的data字段可以被訪問,并且應(yīng)該是一個整數(shù)(因?yàn)?b>mn是MyNode,它是Node)。

嘗試將String分配給Integer會導(dǎo)致Java編譯器在賦值時插入的轉(zhuǎn)換中出現(xiàn)ClassCastException。

橋接方法

在編譯擴(kuò)展參數(shù)化類或?qū)崿F(xiàn)參數(shù)化接口的類或接口時,編譯器可能需要創(chuàng)建一個合成方法,稱為橋接方法,作為類型擦除過程的一部分,你通常不需要擔(dān)心橋接方法,但如果出現(xiàn)在堆棧跟蹤中,你可能會感到困惑。

在類型擦除之后,Node和MyNode類變?yōu)椋?/p>

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

在類型擦除之后,方法簽名不匹配,Node方法變?yōu)?b>setData(Object),MyNode方法變?yōu)?b>setData(Integer),因此,MyNodesetData方法不會覆蓋NodesetData方法。

為了解決這個問題并在類型擦除后保留泛型類型的多態(tài)性,Java編譯器生成一個橋接方法以確保子類型按預(yù)期工作,對于MyNode類,編譯器為setData生成以下橋接方法:

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

如你所見,橋接方法與類型擦除后的Node類的setData方法具有相同的方法簽名,委托給原始的setData方法。

非具體化類型

類型擦除部分討論編譯器移除與類型參數(shù)和類型實(shí)參相關(guān)的信息的過程,類型擦除的結(jié)果與變量參數(shù)(也稱為varargs)方法有關(guān),該方法的varargs形式參數(shù)具有非具體化的類型,有關(guān)varargs方法的更多信息,請參閱將信息傳遞給方法或構(gòu)造函數(shù)的任意數(shù)量的參數(shù)部分。

可具體化類型是類型信息在運(yùn)行時完全可用的類型,這包括基元、非泛型類型、原始類型和無界通配符的調(diào)用。

非具體化類型是指在編譯時通過類型擦除移除信息的類型,即未定義為無界通配符的泛型類型的調(diào)用,非具體化類型在運(yùn)行時不具有所有可用的信息。非具體化類型的例子有ListList,JVM無法在運(yùn)行時區(qū)分這些類型,正如對泛型的限制所示,在某些情況下不能使用非具體化類型:例如,在instanceof表達(dá)式中,或作為數(shù)組中的元素。

堆污染

當(dāng)參數(shù)化類型的變量引用不是該參數(shù)化類型的對象時,會發(fā)生堆污染,如果程序執(zhí)行某些操作,在編譯時產(chǎn)生未經(jīng)檢查的警告,則會出現(xiàn)這種情況。如果在編譯時(在編譯時類型檢查規(guī)則的限制內(nèi))或在運(yùn)行時,無法驗(yàn)證涉及參數(shù)化類型(例如,強(qiáng)制轉(zhuǎn)換或方法調(diào)用)的操作的正確性,將生成未經(jīng)檢查的警告,例如,在混合原始類型和參數(shù)化類型時,或者在執(zhí)行未經(jīng)檢查的強(qiáng)制轉(zhuǎn)換時,會發(fā)生堆污染。

在正常情況下,當(dāng)所有代碼同時編譯時,編譯器會發(fā)出未經(jīng)檢查的警告,以引起你對潛在堆污染的注意,如果多帶帶編譯代碼的各個部分,則很難檢測到堆污染的潛在風(fēng)險,如果確保代碼在沒有警告的情況下編譯,則不會發(fā)生堆污染。

具有非具體化形式參數(shù)的Varargs方法的潛在漏洞

包含vararg輸入?yún)?shù)的泛型方法可能會導(dǎo)致堆污染。

考慮以下ArrayBuilder類:

public class ArrayBuilder {

  public static  void addToList (List listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }

  public static void faultyMethod(List... l) {
    Object[] objectArray = l;     // Valid
    objectArray[0] = Arrays.asList(42);
    String s = l[0].get(0);       // ClassCastException thrown here
  }

}

以下示例HeapPollutionExample使用ArrayBuiler類:

public class HeapPollutionExample {

  public static void main(String[] args) {

    List stringListA = new ArrayList();
    List stringListB = new ArrayList();

    ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine");
    ArrayBuilder.addToList(stringListB, "Ten", "Eleven", "Twelve");
    List> listOfStringLists =
      new ArrayList>();
    ArrayBuilder.addToList(listOfStringLists,
      stringListA, stringListB);

    ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
  }
}

編譯時,ArrayBuilder.addToList方法的定義產(chǎn)生以下警告:

warning: [varargs] Possible heap pollution from parameterized vararg type T

當(dāng)編譯器遇到varargs方法時,它會將varargs形式參數(shù)轉(zhuǎn)換為數(shù)組,但是,Java編程語言不允許創(chuàng)建參數(shù)化類型的數(shù)組,在方法ArrayBuilder.addToList中,編譯器將varargs形式參數(shù)T...元素轉(zhuǎn)換為形式參數(shù)T[]元素,即數(shù)組,但是,由于類型擦除,編譯器會將varargs形式參數(shù)轉(zhuǎn)換為Object[]元素,因此,存在堆污染的可能性。

以下語句將varargs形式參數(shù)l分配給Object數(shù)組objectArgs

Object[] objectArray = l;

這種語句可能會引入堆污染,與varargs形式參數(shù)l的參數(shù)化類型匹配的值可以分配給變量objectArray,因此可以分配給l,但是,編譯器不會在此語句中生成未經(jīng)檢查的警告,編譯器在將varargs形式參數(shù)List ... l轉(zhuǎn)換為形式參數(shù)List[] l時已生成警告,此語句有效,變量l的類型為List[],它是Object[]的子類型。

因此,如果將任何類型的List對象分配給objectArray數(shù)組的任何數(shù)組組件,編譯器不會發(fā)出警告或錯誤,如下所示:

objectArray[0] = Arrays.asList(42);

此語句使用包含一個Integer類型的對象的List對象分配objectArray數(shù)組的第一個數(shù)組組件。

假設(shè)你使用以下語句調(diào)用ArrayBuilder.faultyMethod

ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));

在運(yùn)行時,JVM在以下語句中拋出ClassCastException

// ClassCastException thrown here
String s = l[0].get(0);

存儲在變量l的第一個數(shù)組組件中的對象具有List類型,但此語句需要一個List類型的對象。

防止來自使用非具體化的形式參數(shù)的Varargs方法的警告

如果聲明一個具有參數(shù)化類型參數(shù)的varargs方法,并確保方法體不會因?yàn)閷arargs形式參數(shù)的不正確處理而拋出ClassCastException或其他類似異常,你可以通過向靜態(tài)和非構(gòu)造方法聲明添加以下注解來阻止編譯器為這些類型的varargs方法生成的警告:

@SafeVarargs

@SafeVarargs注解是方法合約的文檔部分,這個注解斷言該方法的實(shí)現(xiàn)不會不正確地處理varargs形式參數(shù)。

盡管不太可取,但通過在方法聲明中添加以下內(nèi)容來抑制此類警告也是可能的:

@SuppressWarnings({"unchecked", "varargs"})

但是,此方法不會抑制從方法的調(diào)用地點(diǎn)生成的警告,如果你不熟悉@SuppressWarnings語法,請參閱注解。

上一篇:泛型通配符使用指南 下一篇:泛型的限制

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/72954.html

相關(guān)文章

  • Java? 教程(泛型的限制)

    泛型的限制 要有效地使用Java泛型,必須考慮以下限制: 無法使用基元類型實(shí)例化泛型類型 無法創(chuàng)建類型參數(shù)的實(shí)例 無法聲明類型為類型參數(shù)的靜態(tài)字段 無法對參數(shù)化類型使用強(qiáng)制類型轉(zhuǎn)換或instanceof 無法創(chuàng)建參數(shù)化類型的數(shù)組 無法創(chuàng)建、捕獲或拋出參數(shù)化類型的對象 無法重載將每個重載的形式參數(shù)類型擦除為相同原始類型的方法 無法使用基元類型實(shí)例化泛型類型 考慮以下參數(shù)化類型: class P...

    Bowman_han 評論0 收藏0
  • Java 泛型總結(jié)(一):基本用法與類型擦除

    摘要:然而中的泛型使用了類型擦除,所以只是偽泛型??偨Y(jié)本文介紹了泛型的使用,以及類型擦除相關(guān)的問題。一般情況下泛型的使用比較簡單,但是某些情況下,尤其是自己編寫使用泛型的類或者方法時要注意類型擦除的問題。 簡介 Java 在 1.5 引入了泛型機(jī)制,泛型本質(zhì)是參數(shù)化類型,也就是說變量的類型是一個參數(shù),在使用時再指定為具體類型。泛型可以用于類、接口、方法,通過使用泛型可以使代碼更簡單、安全。然...

    Java_oldboy 評論0 收藏0
  • Java泛型:類型擦除

    博客地址:Java泛型:類型擦除 前情回顧 Java泛型:泛型類、泛型接口和泛型方法 類型擦除 代碼片段一 Class c1 = new ArrayList().getClass(); Class c2 = new ArrayList().getClass(); System.out.println(c1 == c2); /* Output true */ 顯然在平時使用中,ArrayList...

    Hanks10100 評論0 收藏0
  • 初探Java類型擦除

    摘要:可以看到,如果我們給泛型類制定了上限,泛型擦除之后就會被替換成類型的上限。相應(yīng)的,泛型類中定義的方法的類型也是如此。參考語言類型擦除下界通配符和的區(qū)別 本篇博客主要介紹了Java類型擦除的定義,詳細(xì)的介紹了類型擦除在Java中所出現(xiàn)的場景。 1. 什么是類型擦除 為了讓你們快速的對類型擦除有一個印象,首先舉一個很簡單也很經(jīng)典的例子。 // 指定泛型為String List list1 ...

    DevTalking 評論0 收藏0
  • Java系列之泛型

    摘要:總結(jié)泛型的類型必須是引用類型,不能是基本類型,泛型的個數(shù)可以有多個,可以使用對創(chuàng)建對象時的泛型類型以及方法參數(shù)類型進(jìn)行限制,如使用關(guān)鍵字和對泛型的具體類型進(jìn)行向下限制或向上限制,最后一點(diǎn),可以聲明泛型數(shù)組,但是不能創(chuàng)建泛型數(shù)組的實(shí)例。 自從 JDK 1.5 提供了泛型概念,泛型使得開發(fā)者可以定義較為安全的類型,不至于強(qiáng)制類型轉(zhuǎn)化時出現(xiàn)類型轉(zhuǎn)化異常,在沒有反省之前,可以通過 Object...

    MadPecker 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<