<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ## 泛型擦除 當你開始更深入地鉆研泛型時,會發現有大量的東西初看起來是沒有意義的。例如,盡管可以說 `ArrayList.class`,但不能說成 `ArrayList<Integer>.class`。考慮下面的情況: ```java // generics/ErasedTypeEquivalence.java import java.util.*; public class ErasedTypeEquivalence { public static void main(String[] args) { Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2); } } /* Output: true */ ``` `ArrayList<String>` 和 `ArrayList<Integer>` 應該是不同的類型。不同的類型會有不同的行為。例如,如果嘗試向 `ArrayList<String>` 中放入一個 `Integer`,所得到的行為(失敗)和向 `ArrayList<Integer>` 中放入一個 `Integer` 所得到的行為(成功)完全不同。然而上面的程序認為它們是相同的類型。 下面的例子是對該謎題的補充: ```java // generics/LostInformation.java import java.util.*; class Frob {} class Fnorkle {} class Quark<Q> {} class Particle<POSITION, MOMENTUM> {} public class LostInformation { public static void main(String[] args) { List<Frob> list = new ArrayList<>(); Map<Frob, Fnorkle> map = new HashMap<>(); Quark<Fnorkle> quark = new Quark<>(); Particle<Long, Double> p = new Particle<>(); System.out.println(Arrays.toString(list.getClass().getTypeParameters())); System.out.println(Arrays.toString(map.getClass().getTypeParameters())); System.out.println(Arrays.toString(quark.getClass().getTypeParameters())); System.out.println(Arrays.toString(p.getClass().getTypeParameters())); } } /* Output: [E] [K,V] [Q] [POSITION,MOMENTUM] */ ``` 根據 JDK 文檔,**Class.getTypeParameters()** “返回一個 **TypeVariable** 對象數組,表示泛型聲明中聲明的類型參數...” 這暗示你可以發現這些參數類型。但是正如上例中輸出所示,你只能看到用作參數占位符的標識符,這并非有用的信息。 殘酷的現實是: 在泛型代碼內部,無法獲取任何有關泛型參數類型的信息。 因此,你可以知道如類型參數標識符和泛型邊界這些信息,但無法得知實際的類型參數從而用來創建特定的實例。如果你曾是 C++ 程序員,那么這個事實會讓你很沮喪,在使用 Java 泛型工作時,它是必須處理的最基本的問題。 Java 泛型是使用擦除實現的。這意味著當你在使用泛型時,任何具體的類型信息都被擦除了,你唯一知道的就是你在使用一個對象。因此,`List<String>` 和 `List<Integer>` 在運行時實際上是相同的類型。它們都被擦除成原生類型 `List`。 理解擦除并知道如何處理它,是你在學習 Java 泛型時面臨的最大障礙之一。這也是本節將要探討的內容。 ### C++ 的方式 下面是使用模版的 C++ 示例。你會看到類型參數的語法十分相似,因為 Java 是受 C++ 啟發的: ```c++ // generics/Templates.cpp #include <iostream> using namespace std; template<class T> class Manipulator { T obj; public: Manipulator(T x) { obj = x; } void manipulate() { obj.f(); } }; class HasF { public: void f() { cout << "HasF::f()" << endl; } }; int main() { HasF hf; Manipulator<HasF> manipulator(hf); manipulator.manipulate(); } /* Output: HasF::f() */ ``` **Manipulator** 類存儲了一個 **T** 類型的對象。`manipulate()` 方法會調用 **obj** 上的 `f()` 方法。它是如何知道類型參數 **T** 中存在 `f()` 方法的呢?C++ 編譯器會在你實例化模版時進行檢查,所以在 `Manipulator<HasF>` 實例化的那一刻,它看到 **HasF** 中含有一個方法 `f()`。如果情況并非如此,你就會得到一個編譯期錯誤,保持類型安全。 用 C++ 編寫這種代碼很簡單,因為當模版被實例化時,模版代碼就知道模版參數的類型。Java 泛型就不同了。下面是 **HasF** 的 Java 版本: ```java // generics/HasF.java public class HasF { public void f() { System.out.println("HasF.f()"); } } ``` 如果我們將示例的其余代碼用 Java 實現,就不會通過編譯: ```java // generics/Manipulation.java // {WillNotCompile} class Manipulator<T> { private T obj; Manipulator(T x) { obj = x; } // Error: cannot find symbol: method f(): public void manipulate() { obj.f(); } } public class Manipulation { public static void main(String[] args) { HasF hf = new HasF(); Manipulator<HasF> manipulator = new Manipulator<>(hf); manipulator.manipulate(); } } ``` 因為擦除,Java 編譯器無法將 `manipulate()` 方法必須能調用 **obj** 的 `f()` 方法這一需求映射到 HasF 具有 `f()` 方法這個事實上。為了調用 `f()`,我們必須協助泛型類,給定泛型類一個邊界,以此告訴編譯器只能接受遵循這個邊界的類型。這里重用了 **extends** 關鍵字。由于有了邊界,下面的代碼就能通過編譯: ```java public class Manipulator2<T extends HasF> { private T obj; Manipulator2(T x) { obj = x; } public void manipulate() { obj.f(); } } ``` 邊界 `<T extends HasF>` 聲明 T 必須是 HasF 類型或其子類。如果情況確實如此,就可以安全地在 **obj** 上調用 `f()` 方法。 我們說泛型類型參數會擦除到它的第一個邊界(可能有多個邊界,稍后你將看到)。我們還提到了類型參數的擦除。編譯器實際上會把類型參數替換為它的擦除,就像上面的示例,**T** 擦除到了 **HasF**,就像在類的聲明中用 **HasF** 替換了 **T** 一樣。 你可能正確地觀察到了泛型在 **Manipulator2.java** 中沒有貢獻任何事。你可以很輕松地自己去執行擦除,生成沒有泛型的類: ```java // generics/Manipulator3.java class Manipulator3 { private HasF obj; Manipulator3(HasF x) { obj = x; } public void manipulate() { obj.f(); } } ``` 這提出了很重要的一點:泛型只有在類型參數比某個具體類型(以及其子類)更加“泛化”——代碼能跨多個類工作時才有用。因此,類型參數和它們在有用的泛型代碼中的應用,通常比簡單的類替換更加復雜。但是,不能因此認為使用 `<T extends HasF>` 形式就是有缺陷的。例如,如果某個類有一個返回 **T** 的方法,那么泛型就有所幫助,因為它們之后將返回確切的類型: ```java // generics/ReturnGenericType.java public class ReturnGenericType<T extends HasF> { private T obj; ReturnGenericType(T x) { obj = x; } public T get() { return obj; } } ``` 你必須查看所有的代碼,從而確定代碼是否復雜到必須使用泛型的程度。 我們將在本章稍后看到有關邊界的更多細節。 ### 遷移兼容性 為了減少潛在的關于擦除的困惑,你必須清楚地認識到這不是一個語言特性。它是 Java 實現泛型的一種妥協,因為泛型不是 Java 語言出現時就有的,所以就有了這種妥協。它會使你痛苦,因此你需要盡早習慣它并了解為什么它會這樣。 如果 Java 1.0 就含有泛型的話,那么這個特性就不會使用擦除來實現——它會使用具體化,保持參數類型為第一類實體,因此你就能在類型參數上執行基于類型的語言操作和反射操作。本章稍后你會看到,擦除減少了泛型的泛化性。泛型在 Java 中仍然是有用的,只是不如它們本來設想的那么有用,而原因就是擦除。 在基于擦除的實現中,泛型類型被當作第二類類型處理,即不能在某些重要的上下文使用泛型類型。泛型類型只有在靜態類型檢測期間才出現,在此之后,程序中的所有泛型類型都將被擦除,替換為它們的非泛型上界。例如, `List<T>` 這樣的類型注解會被擦除為 **List**,普通的類型變量在未指定邊界的情況下會被擦除為 **Object**。 擦除的核心動機是你可以在泛化的客戶端上使用非泛型的類庫,反之亦然。這經常被稱為“遷移兼容性”。在理想情況下,所有事物將在指定的某天被泛化。在現實中,即使程序員只編寫泛型代碼,他們也必須處理 Java 5 之前編寫的非泛型類庫。這些類庫的作者可能從沒想過要泛化他們的代碼,或許他們可能剛剛開始接觸泛型。 因此 Java 泛型不僅必須支持向后兼容性——現有的代碼和類文件仍然合法,繼續保持之前的含義——而且還必須支持遷移兼容性,使得類庫能按照它們自己的步調變為泛型,當某個類庫變為泛型時,不會破壞依賴于它的代碼和應用。在確定了這個目標后,Java 設計者們和從事此問題相關工作的各個團隊決策認為擦除是唯一可行的解決方案。擦除使得這種向泛型的遷移成為可能,允許非泛型的代碼和泛型代碼共存。 例如,假設一個應用使用了兩個類庫 **X** 和 **Y**,**Y** 使用了類庫 **Z**。隨著 Java 5 的出現,這個應用和這些類庫的創建者最終可能希望遷移到泛型上。但是當進行遷移時,它們有著不同的動機和限制。為了實現遷移兼容性,每個類庫與應用必須與其他所有的部分是否使用泛型無關。因此,它們不能探測其他類庫是否使用了泛型。因此,某個特定的類庫使用了泛型這樣的證據必須被”擦除“。 如果沒有某種類型的遷移途徑,所有已經構建了很長時間的類庫就需要與希望遷移到 Java 泛型上的開發者們說再見了。類庫毫無爭議是編程語言的一部分,對生產效率有著極大的影響,所以這種代碼無法接受。擦除是否是最佳的或唯一的遷移途徑,還待時間來證明。 ### 擦除的問題 因此,擦除主要的正當理由是從非泛化代碼到泛化代碼的轉變過程,以及在不破壞現有類庫的情況下將泛型融入到語言中。擦除允許你繼續使用現有的非泛型客戶端代碼,直至客戶端準備好用泛型重寫這些代碼。這是一個崇高的動機,因為它不會驟然破壞所有現有的代碼。 擦除的代價是顯著的。泛型不能用于顯式地引用運行時類型的操作中,例如轉型、**instanceof** 操作和 **new** 表達式。因為所有關于參數的類型信息都丟失了,當你在編寫泛型代碼時,必須時刻提醒自己,你只是看起來擁有有關參數的類型信息而已。 考慮如下的代碼段: ```java class Foo<T> { T var; } ``` 看上去當你創建一個 **Foo** 實例時: ```java Foo<Cat> f = new Foo<>(); ``` **class** **Foo** 中的代碼應該知道現在工作于 **Cat** 之上。泛型語法也在強烈暗示整個類中所有 T 出現的地方都被替換,就像在 C++ 中一樣。但是事實并非如此,當你在編寫這個類的代碼時,必須提醒自己:“不,這只是一個 **Object**“。 另外,擦除和遷移兼容性意味著,使用泛型并不是強制的,盡管你可能希望這樣: ```java // generics/ErasureAndInheritance.java class GenericBase<T> { private T element; public void set(T arg) { element = arg; } public T get() { return element; } } class Derived1<T> extends GenericBase<T> {} class Derived2 extends GenericBase {} // No warning // class Derived3 extends GenericBase<?> {} // Strange error: // unexpected type // required: class or interface without bounds public class ErasureAndInteritance { @SuppressWarnings("unchecked") public static void main(String[] args) { Derived2 d2 = new Derived2(); Object obj = d2.get(); d2.set(obj); // Warning here! } } ``` **Derived2** 繼承自 **GenericBase**,但是沒有任何類型參數,編譯器沒有發出任何警告。直到調用 `set()` 方法時才出現警告。 為了關閉警告,Java 提供了一個注解,我們可以在列表中看到它: ```java @SuppressWarnings("unchecked") ``` 這個注解放置在產生警告的方法上,而不是整個類上。當你要關閉警告時,最好盡可能地“聚焦”,這樣就不會因為過于寬泛地關閉警告,而導致意外地遮蔽掉真正的問題。 可以推斷,**Derived3** 產生的錯誤意味著編譯器期望得到一個原生基類。 當你希望將類型參數不僅僅當作 Object 處理時,就需要付出額外努力來管理邊界,并且與在 C++、Ada 和 Eiffel 這樣的語言中獲得參數化類型相比,你需要付出多得多的努力來獲得少得多的回報。這并不是說,對于大多數的編程問題而言,這些語言通常都會比 Java 更得心應手,只是說它們的參數化類型機制相比 Java 更靈活、更強大。 ### 邊界處的動作 因為擦除,我發現了泛型最令人困惑的方面是可以表示沒有任何意義的事物。例如: ```java // generics/ArrayMaker.java import java.lang.reflect.*; import java.util.*; public class ArrayMaker<T> { private Class<T> kind; public ArrayMaker(Class<T> kind) { this.kind = kind; } @SuppressWarnings("unchecked") T[] create(int size) { return (T[]) Array.newInstance(kind, size); } public static void main(String[] args) { ArrayMaker<String> stringMaker = new ArrayMaker<>(String.class); String[] stringArray = stringMaker.create(9); System.out.println(Arrays.toString(stringArray)); } } /* Output [null,null,null,null,null,null,null,null,null] */ ``` 即使 **kind** 被存儲為 `Class<T>`,擦除也意味著它實際被存儲為沒有任何參數的 **Class**。因此,當你在使用它時,例如創建數組,`Array.newInstance()` 實際上并未擁有 **kind** 所蘊含的類型信息。所以它不會產生具體的結果,因而必須轉型,這會產生一條令你無法滿意的警告。 注意,對于在泛型中創建數組,使用 `Array.newInstance()` 是推薦的方式。 如果我們創建一個集合而不是數組,情況就不同了: ```java // generics/ListMaker.java import java.util.*; public class ListMaker<T> { List<T> create() { return new ArrayList<>(); } public static void main(String[] args) { ListMaker<String> stringMaker = new ListMaker<>(); List<String> stringList = stringMaker.create(); } } ``` 編譯器不會給出任何警告,盡管我們知道(從擦除中)在 `create()` 內部的 `new ArrayList<>()` 中的 `<T>` 被移除了——在運行時,類內部沒有任何 `<T>`,因此這看起來毫無意義。但是如果你遵從這種思路,并將這個表達式改為 `new ArrayList()`,編譯器就會發出警告。 本例中這么做真的毫無意義嗎?如果在創建 **List** 的同時向其中放入一些對象呢,像這樣: ```java // generics/FilledList.java 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); } } /* Output: [Hello,Hello,Hello,Hello] [47,47,47,47] */ ``` 即使編譯器無法得知 `add()` 中的 **T** 的任何信息,但它仍可以在編譯期確保你放入 **FilledList** 中的對象是 **T** 類型。因此,即使擦除移除了方法或類中的實際類型的信息,編譯器仍可以確保方法或類中使用的類型的內部一致性。 因為擦除移除了方法體中的類型信息,所以在運行時的問題就是*邊界*:即對象進入和離開方法的地點。這些正是編譯器在編譯期執行類型檢查并插入轉型代碼的地點。 考慮如下這段非泛型示例: ```java // generics/SimpleHolder.java public class SimpleHolder { private Object obj; public void set(Object obj) { this.obj = obj; } public Object get() { return obj; } public static void main(String[] args) { SimpleHolder holder = new SimpleHolder(); holder.set("Item"); String s = (String) holder.get(); } } ``` 如果用 **javap -c SimpleHolder** 反編譯這個類,會得到如下內容(經過編輯): ```java public void set(java.lang.Object); 0: aload_0 1: aload_1 2: putfield #2; // Field obj:Object; 5: return public java.lang.Object get(); 0: aload_0 1: getfield #2; // Field obj:Object; 4: areturn public static void main(java.lang.String[]); 0: new #3; // class SimpleHolder 3: dup 4: invokespecial #4; // Method "<init>":()V 7: astore_1 8: aload_1 9: ldc #5; // String Item 11: invokevirtual #6; // Method set:(Object;)V 14: aload_1 15: invokevirtual #7; // Method get:()Object; 18: checkcast #8; // class java/lang/String 21: astore_2 22: return ``` `set()` 和 `get()` 方法存儲和產生值,轉型在調用 `get()` 時接受檢查。 現在將泛型融入上例代碼中: ```java // generics/GenericHolder2.java public class GenericHolder2<T> { 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 s = holder.get(); } } ``` 從 `get()` 返回后的轉型消失了,但是我們還知道傳遞給 `set()` 的值在編譯期會被檢查。下面是相關的字節碼: ```java public void set(java.lang.Object); 0: aload_0 1: aload_1 2: putfield #2; // Field obj:Object; 5: return public java.lang.Object get(); 0: aload_0 1: getfield #2; // Field obj:Object; 4: areturn public static void main(java.lang.String[]); 0: new #3; // class 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:(Object;)V 14: aload_1 15: invokevirtual #7; // Method get:()Object; 18: checkcast #8; // class java/lang/String 21: astore_2 22: return ``` 所產生的字節碼是相同的。對進入 `set()` 的類型進行檢查是不需要的,因為這將由編譯器執行。而對 `get()` 返回的值進行轉型仍然是需要的,只不過不需要你來操作,它由編譯器自動插入,這樣你就不用編寫(閱讀)雜亂的代碼。 `get()` 和 `set()` 產生了相同的字節碼,這就告訴我們泛型的所有動作都發生在邊界處——對入參的編譯器檢查和對返回值的轉型。這有助于澄清對擦除的困惑,記住:“邊界就是動作發生的地方”。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看