簡體   English   中英

java 類型擦除如何處理'?

[英]How does java type erasure treats '?'

希望在了解通用邊界之后,我正在嘗試了解通配符上限和下限。 我的參考是: https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html的通配符類型可以在各種情況下使用:“我發現的通配符類型可以在多種情況下使用”參數、字段或局部變量; " 字段和局部變量? 無法想象。 為什么如此重要的資料沒有通過一個簡單的例子來強調它?

我試圖了解 java 編譯器用哪個參考替換(擦除時)'?'。 也許我有一個很大的誤解並且發生了任何擦除(因此以下所有示例都不相關)。 在以下示例中: 1。

public static void funcA (List<? extends Number>l)

2.

public static void funcB(List<? super Integer>l)

第二個示例與以下代碼之間是否有區別:3。

public static <T extends Integer> funcC(List<? extends T>l)

如果示例 2 與以下示例有任何不同:4。

public static <T extends Integer> void funcC(List<T>l)

前言:你問了很多關於類型擦除的問題,包括在聊天室里。 雖然此答案將解決此特定問題,但它也可能會回答您的其他一些問題(甚至可能是您尚未問過的一些問題)。

警告:這個答案很長,這篇文章中特別提出的問題只在最后直接解決。


什么是類型擦除?

這在其他問答中已經很好地涵蓋了,所以我將簡單地鏈接到它們:


類型擦除的規則是什么?

類型擦除的規則在Java 語言規范 (JLS)§4.6 類型擦除中指定:

類型擦除是從類型(可能包括參數化類型和類型變量)到類型(從不參數化類型或類型變量)的映射。 我們寫|T| 用於擦除類型T 擦除映射定義如下:

  • 參數化類型 ( §4.5 ) G<T1,...,Tn>的擦除是|G| .

  • 嵌套類型T.C的擦除是|T|.C

  • 數組類型T[]的擦除是|T|[]

  • 類型變量的擦除( 第 4.4 節)是其最左邊界的擦除。

  • 所有其他類型的擦除都是類型本身。

類型擦除還將構造函數或方法的簽名( 第 8.4.2 節)映射到沒有參數化類型或類型變量的簽名。 構造函數或方法簽名s的擦除是由與 s 相同的名稱和s中給出s所有形式參數類型的擦除組成的簽名。

如果方法或構造函數的簽名被擦除,方法的返回類型( §8.4.5 )和泛型方法或構造函數的類型參數( §8.4.4§8.8.4 )也會被擦除。

泛型方法簽名的擦除沒有類型參數。

對於這個答案,我們應該關注第一個和第四個要點:

參數化類型 ( §4.5 ) G<T1,...,Tn>的擦除是|G| .

和:

類型變量的擦除( 第 4.4 節)是其最左邊界的擦除。

分別。 特別要注意第四個要點如何只解釋類型變量的擦除。 為什么這很重要? 我會回到那個。

術語

理解這些術語對於理解如何應用規則很重要:

  • 通用 class

  • 通用接口

  • 通用方法

    • 在 JLS 的§8.4.4 通用方法中指定。
    • 泛型方法是一種聲明一個或多個類型變量的方法。
  • 通用構造函數

  • 類型變量

    • 在 JLS 的§4.4 類型變量中指定。
    • 類型變量由在泛型成員上聲明的類型參數引入。
    • 類型變量語法是: {Annotation} TypeIdentifier
  • 類型參數

    • 在 JLS 的許多部分中指定。 我為上述術語鏈接的每個部分都提到了類型參數
    • 類型參數是泛型成員上類型變量的聲明及其邊界。
    • 類型參數語法是: {TypeParameterModifier} TypeIdentifier [TypeBound]其中TypeParameterModifier擴展為Annotation
  • 參數化類型

  • 類型參數

請注意,類型參數類型參數之間的區別類似於 Java 如何區分方法參數和參數。 當您將方法聲明為void bar(Object obj)時, Object obj是一個參數。 但是,當您調用bar(someObjInstance)之類的方法時, someObjInstance的值就是參數。

術語的代碼示例

查看代碼中的一些示例可以幫助理解每個術語適用於代碼的哪些部分。

具有類型參數的通用 class

public class Foo<T extends CharSequence, U> {
    // class body...
}

有兩種類型參數

  1. T extends Charsequence

    • 類型變量T
    • 類型邊界extends CharSequence
  2. U

    • 類型變量U
    • 類型邊界extends Object (隱式定義)

通用接口的代碼看起來很相似。

帶有類型參數的泛型方法

public void <V extends Number> bar(V obj) {
   // method body...
}

此方法有一個類型參數

  1. V extends Number
    • 類型變量V
    • 類型邊界extends Number

對於泛型構造函數,代碼看起來很相似。

參數化類型(無通配符)

public <E extends Number> void bar(List<E> list) {
    // method body...
}

有一種參數化類型

  1. List<E>
    • 類型參數E

參數化類型(帶通配符)

public void bar(List<? extends Number> list) {
    // method body...
}

有一種參數化類型

  1. List<? extends Number>
    • 類型參數? extends Number ? extends Number

回到規則

正如我所提到的,重要的是要注意規則只提到了類型變量的擦除。 這很重要的原因是因為在可以定義類型變量的地方(即在類型參數中)不允許使用通配符。 通配符只能用在作為參數化類型一部分的類型參數中。

參數化類型的擦除只是原始類型。


什么時候類型擦除很重要?

在通用代碼類型擦除的日常開發中幾乎是無關緊要的。 在使用原始類型時,您必須詳細了解類型擦除是如何工作的唯一一次。 在一個理想且公正的世界中,您只能在處理無法更改的遺留代碼(從 Java 5 之前的日子)時使用原始類型。 換句話說,除非您被迫使用原始類型,否則您應該始終適當地使用 generics。

但是,也許您被迫使用原始類型,或者您只是好奇。 在這種情況下,您需要知道類型擦除是如何工作的,因為擦除決定了使用什么類型。 下面是一個通用 class 的示例:

public class Foo<T extend CharSequence, U> {

    private List<T> listField;
    private U objField;

    public void bar(List<? extends T> listParam) {
        // method body...
    }

    public U baz(T objParam) {
        // method body...
    }

    public <V extends Number> V qux(V objParam) {
        // method body...
    }

}

遵循上述類型擦除規則,上面的 class 如下所示:

// the raw type of Foo
public class Foo {

    private List listField;
    private Object objField;

    public void bar(List listParam) {
        // method body...
    }

    public Object baz(CharSequence objParam) {
        // method body...
    }

    public Number qux(Number objParam) {
        // method body...
    }

}

但同樣,當您使用原始類型時,您只需要了解后一個版本。


將此知識應用於您的問題

到目前為止,我們了解到的一些內容是通配符只能用於arguments 類型,因此僅適用於參數化類型 參數化類型的擦除只是原始類型。 如果我們將這些知識應用於您的示例,您將獲得以下信息:

  1. 示例 #1

    • 原來的

      public static void funcA(List<? extends Number> l)
    • 已刪除

      public static void funcA(List l)
  2. 示例 #2

    • 原來的

      public static void funcB(List<? super Integer> l)
    • 已刪除

      public static void funcB(List l)
  3. 示例#3

    • 原始(忘記指定有問題的返回類型,假設為void

       public static <T extends Integer> void funcC(List<? extends T> l)
    • 已刪除

      public static void funcC(List l)
  4. 示例 #4

    • 原來的

      public static <T extends Integer> void funcC(List<T> l)
    • 已刪除

      public static void funcC(List l)

加強重點

為了真正指出擦除類型變量和擦除參數化類型之間的區別,讓我們看另一個示例。

public class Foo<T extends Number> {

    public void bar(T obj) {
        // method body...
    }

    public void baz(List<? extends T> list) {
        // method body...
    }

}

方法bar有一個T類型的參數。 該參數直接使用類型變量 類型變量的擦除是擦除其最左邊的邊界,在這種情況下是Number 這意味着擦除后方法的參數是Number

方法baz有一個List<? extends T> List<? extends T> 在這里,類型變量T被用作參數化類型類型參數的上限。 換句話說,盡管使用了類型變量,但這里實際使用的擦除是參數化類型的擦除。 這意味着在擦除方法的參數后只是List 即使類型參數是下界通配符(例如List<? super T> )、無界通配符(例如List<?> )或什至是非通配符(例如List<T> ),也會發生這種情況。

類型擦除如何處理通配符

要直接回答您關於類型擦除如何處理通配符的問題,答案是有效的:它不是,不是直接的。 通配符只是消失(當參數化類型被刪除時)並且在生成的原始類型中沒有意義。

通配符為使用通用 API 的用戶提供了靈活性。 以下是一些解決該概念的問答:

希望這些問答也有助於回答您的輔助問題,即您帖子中的第二個和第四個示例之間有什么區別。

示例 2 是說你可以通過List<? super Integer> List<? super Integer> ,所以List<Integer>是可以的,但List<Number>List<Object>也是可以的。

然而,下一個例子說你可以通過List<? extends T> List<? extends T>其中<T extends Integer> 所以真的你只能通過List<Integer>並且如果它們存在Integer的任何子類(不存在,因為Integer是最終的)。

如您所見,它們彼此完全相反。

最后一個例子有點類似。 您沒有在其他任何地方專門使用T ,因此它正在解決同一問題。 如果您需要T作為額外參數或返回值的一部分,這將更有用。

最后不要對類型擦除的作用感到困惑。 ? 是通用類型通配符,與類型擦除無關。

類型擦除只是在運行時泛型類型在執行期間不存在的事實。 Generics 僅強制執行編譯時檢查。 編譯后你將有一個簡單的列表。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM