<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>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 補償擦除 因為擦除,我們將失去執行泛型代碼中某些操作的能力。無法在運行時知道確切類型: ```java // generics/Erased.java // {WillNotCompile} public class Erased<T> { private final int SIZE = 100; public void f(Object arg) { // error: illegal generic type for instanceof if (arg instanceof T) { } // error: unexpected type T var = new T(); // error: generic array creation T[] array = new T[SIZE]; // warning: [unchecked] unchecked cast T[] array = (T[]) new Object[SIZE]; } } ``` 有時,我們可以對這些問題進行編程,但是有時必須通過引入類型標簽來補償擦除。這意味著為所需的類型顯式傳遞一個 **Class** 對象,以在類型表達式中使用它。 例如,由于擦除了類型信息,因此在上一個程序中嘗試使用 **instanceof** 將會失敗。類型標簽可以使用動態 `isInstance()` : ```java // generics/ClassTypeCapture.java class Building { } class House extends Building { } public class ClassTypeCapture<T> { Class<T> kind; public ClassTypeCapture(Class<T> kind) { this.kind = kind; } public boolean f(Object arg) { return kind.isInstance(arg); } public static void main(String[] args) { ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<>(Building.class); System.out.println(ctt1.f(new Building())); System.out.println(ctt1.f(new House())); ClassTypeCapture<House> ctt2 = new ClassTypeCapture<>(House.class); System.out.println(ctt2.f(new Building())); System.out.println(ctt2.f(new House())); } } /* Output: true true false true */ ``` 編譯器來保證類型標簽與泛型參數相匹配。 <!-- Creating Instances of Types --> ### 創建類型的實例 試圖在 **Erased.java** 中 `new T()` 是行不通的,部分原因是由于擦除,部分原因是編譯器無法驗證 **T** 是否具有默認(無參)構造函數。但是在 C++ 中,此操作自然,直接且安全(在編譯時檢查): ```C++ // generics/InstantiateGenericType.cpp // C++, not Java! template<class T> class Foo { T x; // Create a field of type T T* y; // Pointer to T public: // Initialize the pointer: Foo() { y = new T(); } }; class Bar {}; int main() { Foo<Bar> fb; Foo<int> fi; // ... and it works with primitives } ``` Java 中的解決方案是傳入一個工廠對象,并使用該對象創建新實例。方便的工廠對象只是 **Class** 對象,因此,如果使用類型標記,則可以使用 `newInstance()` 創建該類型的新對象: ```java // generics/InstantiateGenericType.java import java.util.function.Supplier; class ClassAsFactory<T> implements Supplier<T> { Class<T> kind; ClassAsFactory(Class<T> kind) { this.kind = kind; } @Override public T get() { try { return kind.newInstance(); } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } } class Employee { @Override public String toString() { return "Employee"; } } public class InstantiateGenericType { public static void main(String[] args) { ClassAsFactory<Employee> fe = new ClassAsFactory<>(Employee.class); System.out.println(fe.get()); ClassAsFactory<Integer> fi = new ClassAsFactory<>(Integer.class); try { System.out.println(fi.get()); } catch (Exception e) { System.out.println(e.getMessage()); } } } /* Output: Employee java.lang.InstantiationException: java.lang.Integer */ ``` 這樣可以編譯,但對于 `ClassAsFactory<Integer>` 會失敗,這是因為 **Integer** 沒有無參構造函數。由于錯誤不是在編譯時捕獲的,因此語言創建者不贊成這種方法。他們建議使用顯式工廠(**Supplier**)并約束類型,以便只有實現該工廠的類可以這樣創建對象。這是創建工廠的兩種不同方法: ```java // generics/FactoryConstraint.java import onjava.Suppliers; import java.util.ArrayList; import java.util.List; import java.util.function.Supplier; class IntegerFactory implements Supplier<Integer> { private int i = 0; @Override public Integer get() { return ++i; } } class Widget { private int id; Widget(int n) { id = n; } @Override public String toString() { return "Widget " + id; } public static class Factory implements Supplier<Widget> { private int i = 0; @Override public Widget get() { return new Widget(++i); } } } class Fudge { private static int count = 1; private int n = count++; @Override public String toString() { return "Fudge " + n; } } class Foo2<T> { private List<T> x = new ArrayList<>(); Foo2(Supplier<T> factory) { Suppliers.fill(x, factory, 5); } @Override public String toString() { return x.toString(); } } public class FactoryConstraint { public static void main(String[] args) { System.out.println( new Foo2<>(new IntegerFactory())); System.out.println( new Foo2<>(new Widget.Factory())); System.out.println( new Foo2<>(Fudge::new)); } } /* Output: [1, 2, 3, 4, 5] [Widget 1, Widget 2, Widget 3, Widget 4, Widget 5] [Fudge 1, Fudge 2, Fudge 3, Fudge 4, Fudge 5] */ ``` **IntegerFactory** 本身就是通過實現 `Supplier<Integer>` 的工廠。 **Widget** 包含一個內部類,它是一個工廠。還要注意,**Fudge** 并沒有做任何類似于工廠的操作,并且傳遞 `Fudge::new` 仍然會產生工廠行為,因為編譯器將對函數方法 `::new` 的調用轉換為對 `get()` 的調用。 另一種方法是模板方法設計模式。在以下示例中,`create()` 是模板方法,在子類中被重寫以生成該類型的對象: ```java // generics/CreatorGeneric.java abstract class GenericWithCreate<T> { final T element; GenericWithCreate() { element = create(); } abstract T create(); } class X { } class XCreator extends GenericWithCreate<X> { @Override X create() { return new X(); } void f() { System.out.println( element.getClass().getSimpleName()); } } public class CreatorGeneric { public static void main(String[] args) { XCreator xc = new XCreator(); xc.f(); } } /* Output: X */ ``` **GenericWithCreate** 包含 `element` 字段,并通過無參構造函數強制其初始化,該構造函數又調用抽象的 `create()` 方法。這種創建方式可以在子類中定義,同時建立 **T** 的類型。 <!-- Arrays of Generics --> ### 泛型數組 正如在 **Erased.java** 中所看到的,我們無法創建泛型數組。通用解決方案是在試圖創建泛型數組的時候使用 **ArrayList** : ```java // generics/ListOfGenerics.java import java.util.ArrayList; import java.util.List; public class ListOfGenerics<T> { private List<T> array = new ArrayList<>(); public void add(T item) { array.add(item); } public T get(int index) { return array.get(index); } } ``` 這樣做可以獲得數組的行為,并且還具有泛型提供的編譯時類型安全性。 有時,仍然會創建泛型類型的數組(例如, **ArrayList** 在內部使用數組)。可以通過使編譯器滿意的方式定義對數組的通用引用: ```java // generics/ArrayOfGenericReference.java class Generic<T> { } public class ArrayOfGenericReference { static Generic<Integer>[] gia; } ``` 編譯器接受此操作而不產生警告。但是我們永遠無法創建具有該確切類型(包括類型參數)的數組,因此有點令人困惑。由于所有數組,無論它們持有什么類型,都具有相同的結構(每個數組插槽的大小和數組布局),因此似乎可以創建一個 **Object** 數組并將其轉換為所需的數組類型。實際上,這確實可以編譯,但是會產生 **ClassCastException** : ```java // generics/ArrayOfGeneric.java public class ArrayOfGeneric { static final int SIZE = 100; static Generic<Integer>[] gia; @SuppressWarnings("unchecked") public static void main(String[] args) { try { gia = (Generic<Integer>[]) new Object[SIZE]; } catch (ClassCastException e) { System.out.println(e.getMessage()); } // Runtime type is the raw (erased) type: gia = (Generic<Integer>[]) new Generic[SIZE]; System.out.println(gia.getClass().getSimpleName()); gia[0] = new Generic<>(); //- gia[1] = new Object(); // Compile-time error // Discovers type mismatch at compile time: //- gia[2] = new Generic<Double>(); } } /* Output: [Ljava.lang.Object; cannot be cast to [LGeneric; Generic[] */ ``` 問題在于數組會跟蹤其實際類型,而該類型是在創建數組時建立的。因此,即使 `gia` 被強制轉換為 `Generic<Integer>[]` ,該信息也僅在編譯時存在(并且沒有 **@SuppressWarnings** 注解,將會收到有關該強制轉換的警告)。在運行時,它仍然是一個 **Object** 數組,這會引起問題。成功創建泛型類型的數組的唯一方法是創建一個已擦除類型的新數組,并將其強制轉換。 讓我們看一個更復雜的示例。考慮一個包裝數組的簡單泛型包裝器: ```java // generics/GenericArray.java public class GenericArray<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArray(int sz) { array = (T[]) new Object[sz]; } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Method that exposes the underlying representation: public T[] rep() { return array; } public static void main(String[] args) { GenericArray<Integer> gai = new GenericArray<>(10); try { Integer[] ia = gai.rep(); } catch (ClassCastException e) { System.out.println(e.getMessage()); } // This is OK: Object[] oa = gai.rep(); } } /* Output: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; */ ``` 和以前一樣,我們不能說 `T[] array = new T[sz]` ,所以我們創建了一個 **Object** 數組并將其強制轉換。 `rep()` 方法返回一個 `T[]` ,在主方法中它應該是 `gai` 的 `Integer[]`,但是如果調用它并嘗試將結果轉換為 `Integer[]` 引用,則會得到 **ClassCastException** ,這再次是因為實際的運行時類型為 `Object[]` 。 如果再注釋掉 **@SuppressWarnings** 注解后編譯 **GenericArray.java** ,則編譯器會產生警告: ```java GenericArray.java uses unchecked or unsafe operations. Recompile with -Xlint:unchecked for details. ``` 在這里,我們收到了一個警告,我們認為這是有關強制轉換的。 但是要真正確定,請使用 `-Xlint:unchecked` 進行編譯: ```java GenericArray.java:7: warning: [unchecked] unchecked cast array = (T[])new Object[sz]; ^ required: T[] found: Object[] where T is a type-variable: T extends Object declared in class GenericArray 1 warning ``` 確實是在抱怨那個強制轉換。由于警告會變成噪音,因此,一旦我們確認預期會出現特定警告,我們可以做的最好的辦法就是使用 **@SuppressWarnings** 將其關閉。這樣,當警告確實出現時,我們將進行實際調查。 由于擦除,數組的運行時類型只能是 `Object[]` 。 如果我們立即將其轉換為 `T[]` ,則在編譯時會丟失數組的實際類型,并且編譯器可能會錯過一些潛在的錯誤檢查。因此,最好在集合中使用 `Object[]` ,并在使用數組元素時向 **T** 添加強制類型轉換。讓我們來看看在 **GenericArray.java** 示例中會是怎么樣的: ```java // generics/GenericArray2.java public class GenericArray2<T> { private Object[] array; public GenericArray2(int sz) { array = new Object[sz]; } public void put(int index, T item) { array[index] = item; } @SuppressWarnings("unchecked") public T get(int index) { return (T) array[index]; } @SuppressWarnings("unchecked") public T[] rep() { return (T[]) array; // Unchecked cast } public static void main(String[] args) { GenericArray2<Integer> gai = new GenericArray2<>(10); for (int i = 0; i < 10; i++) gai.put(i, i); for (int i = 0; i < 10; i++) System.out.print(gai.get(i) + " "); System.out.println(); try { Integer[] ia = gai.rep(); } catch (Exception e) { System.out.println(e); } } } /* Output: 0 1 2 3 4 5 6 7 8 9 java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer; */ ``` 最初,看起來并沒有太大不同,只是轉換的位置移動了。沒有 **@SuppressWarnings** 注解,仍然會收到“unchecked”警告。但是,內部表示現在是 `Object[]` 而不是 `T[]` 。 調用 `get()` 時,它將對象強制轉換為 **T** ,實際上這是正確的類型,因此很安全。但是,如果調用 `rep()` ,它將再次嘗試將 `Object[]` 強制轉換為 `T[]` ,但仍然不正確,并在編譯時生成警告,并在運行時生成異常。因此,無法破壞基礎數組的類型,該基礎數組只能是 `Object[]` 。在內部將數組視為 `Object[]` 而不是 `T[]` 的優點是,我們不太可能會忘記數組的運行時類型并意外地引入了bug,盡管大多數(也許是全部)此類錯誤會在運行時被迅速檢測到。 對于新代碼,請傳入類型標記。在這種情況下,**GenericArray** 如下所示: ```java // generics/GenericArrayWithTypeToken.java import java.lang.reflect.Array; public class GenericArrayWithTypeToken<T> { private T[] array; @SuppressWarnings("unchecked") public GenericArrayWithTypeToken(Class<T> type, int sz) { array = (T[]) Array.newInstance(type, sz); } public void put(int index, T item) { array[index] = item; } public T get(int index) { return array[index]; } // Expose the underlying representation: public T[] rep() { return array; } public static void main(String[] args) { GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<>( Integer.class, 10); // This now works: Integer[] ia = gai.rep(); } } ``` 類型標記 **Class\<T\>** 被傳遞到構造函數中以從擦除中恢復,因此盡管必須使用 **@SuppressWarnings** 關閉來自強制類型轉換的警告,但我們仍可以創建所需的實際數組類型。一旦獲得了實際的類型,就可以返回它并產生所需的結果,如在主方法中看到的那樣。數組的運行時類型是確切的類型 `T[]` 。 不幸的是,如果查看 Java 標準庫中的源代碼,你會發現到處都有從 **Object** 數組到參數化類型的轉換。例如,這是**ArrayList** 中,復制一個 **Collection** 的構造函數,這里為了簡化,去除了源碼中對此不重要的代碼: ```java public ArrayList(Collection c) { size = c.size(); elementData = (E[])new Object[size]; c.toArray(elementData); } ``` 如果你瀏覽 **ArrayList.java** 的代碼,將會發現很多此類強制轉換。當我們編譯它時會發生什么? ```java Note: ArrayList.java uses unchecked or unsafe operations Note: Recompile with -Xlint:unchecked for details. ``` 果然,標準庫會產生很多警告。如果你使用過 C 語言,尤其是使用 ANSI C 之前的語言,你會記住警告的特殊效果:發現警告后,可以忽略它們。因此,除非程序員必須對其進行處理,否則最好不要從編譯器發出任何類型的消息。 Neal Gafter(Java 5 的主要開發人員之一)在他的博客中[^2]指出,他在重寫 Java 庫時是很隨意、馬虎的,我們不應該像他那樣做。Neal 還指出,他在不破壞現有接口的情況下無法修復某些 Java 庫代碼。因此,即使在 Java 庫源代碼中出現了一些習慣用法,它們也不一定是正確的做法。當查看庫代碼時,我們不能認為這就是要在自己代碼中必須遵循的示例。 請注意,在 Java 文獻中推薦使用類型標記技術,例如 Gilad Bracha 的論文《Generics in the Java Programming Language》[^3],他指出:“例如,這種用法已廣泛用于新的 API 中以處理注解。” 我發現此技術在人們對于舒適度的看法方面存在一些不一致之處;有些人強烈喜歡本章前面介紹的工廠方法。
                  <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>

                              哎呀哎呀视频在线观看