<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智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                ## 通配符 你已經在 [集合](book/12-Collections.md) 章節中看到了一些簡單示例使用了通配符——在泛型參數表達式中的問號,在 [類型信息](book/19-Type-Information.md) 一章中這種示例更多。本節將更深入地探討這個特性。 我們的起始示例要展示數組的一種特殊行為:你可以將派生類的數組賦值給基類的引用: ```java // generics/CovariantArrays.java class Fruit {} class Apple extends Fruit {} class Jonathan extends Apple {} class Orange extends Fruit {} public class CovariantArrays { public static void main(String[] args) { Fruit[] fruit = new Apple[10]; fruit[0] = new Apple(); // OK fruit[1] = new Jonathan(); // OK // Runtime type is Apple[], not Fruit[] or Orange[]: try { // Compiler allows you to add Fruit: fruit[0] = new Fruit(); // ArrayStoreException } catch (Exception e) { System.out.println(e); } try { // Compiler allows you to add Oranges: fruit[0] = new Orange(); // ArrayStoreException } catch (Exception e) { System.out.println(e); } } } /* Output: java.lang.ArrayStoreException: Fruit java.lang.ArrayStoreException: Orange ``` `main()` 中的第一行創建了 **Apple** 數組,并賦值給一個 **Fruit** 數組引用。這是有意義的,因為 **Apple** 也是一種 **Fruit**,因此 **Apple** 數組應該也是一個 **Fruit** 數組。 但是,如果實際的數組類型是 **Apple[]**,你可以在其中放置 **Apple** 或 **Apple** 的子類型,這在編譯期和運行時都可以工作。但是你也可以在數組中放置 **Fruit** 對象。這對編譯器來說是有意義的,因為它有一個 **Fruit[]** 引用——它有什么理由不允許將 **Fruit** 對象或任何從 **Fruit** 繼承出來的對象(比如 **Orange**),放置到這個數組中呢?因此在編譯期,這是允許的。然而,運行時的數組機制知道它處理的是 **Apple[]**,因此會在向數組中放置異構類型時拋出異常。 向上轉型用在這里不合適。你真正在做的是將一個數組賦值給另一個數組。數組的行為是持有其他對象,這里只是因為我們能夠向上轉型而已,所以很明顯,數組對象可以保留有關它們包含的對象類型的規則。看起來就像數組對它們持有的對象是有意識的,因此在編譯期檢查和運行時檢查之間,你不能濫用它們。 數組的這種賦值并不是那么可怕,因為在運行時你可以發現插入了錯誤的類型。但是泛型的主要目標之一是將這種錯誤檢測移到編譯期。所以當我們試圖使用泛型集合代替數組時,會發生什么呢? ```java // generics/NonCovariantGenerics.java // {WillNotCompile} import java.util.*; public class NonCovariantGenerics { // Compile Error: incompatible types: List<Fruit> flist = new ArrayList<Apple>(); } ``` 盡管你在首次閱讀這段代碼時會認為“不能將一個 **Apple** 集合賦值給一個 **Fruit** 集合”。記住,泛型不僅僅是關于集合,它真正要表達的是“不能把一個涉及 **Apple** 的泛型賦值給一個涉及 **Fruit** 的泛型”。如果像在數組中的情況一樣,編譯器對代碼的了解足夠多,可以確定所涉及到的集合,那么它可能會留下一些余地。但是它不知道任何有關這方面的信息,因此它拒絕向上轉型。然而實際上這也不是向上轉型—— **Apple** 的 **List** 不是 **Fruit** 的 **List**。**Apple** 的 **List** 將持有 **Apple** 和 **Apple** 的子類型,**Fruit** 的 **List** 將持有任何類型的 **Fruit**。是的,這包括 **Apple**,但是它不是一個 **Apple** 的 **List**,它仍然是 **Fruit** 的 **List**。**Apple** 的 **List** 在類型上不等價于 **Fruit** 的 **List**,即使 **Apple** 是一種 **Fruit** 類型。 真正的問題是我們在討論的集合類型,而不是集合持有對象的類型。與數組不同,泛型沒有內建的協變類型。這是因為數組是完全在語言中定義的,因此可以具有編譯期和運行時的內建檢查,但是在使用泛型時,編譯器和運行時系統不知道你想用類型做什么,以及應該采用什么規則。 但是,有時你想在兩個類型間建立某種向上轉型關系。通配符可以產生這種關系。 ```java // generics/GenericsAndCovariance.java import java.util.*; public class GenericsAndCovariance { public static void main(String[] args) { // Wildcards allow covariance: List<? extends Fruit> flist = new ArrayList<>(); // Compile Error: can't add any type of object: // flist.add(new Apple()); // flist.add(new Fruit()); // flist.add(new Object()); flist.add(null); // Legal but uninteresting // We know it returns at least Fruit: Fruit f = flist.get(0); } } ``` **flist** 的類型現在是 `List<? extends Fruit>`,你可以讀作“一個具有任何從 **Fruit** 繼承的類型的列表”。然而,這實際上并不意味著這個 **List** 將持有任何類型的 **Fruit**。通配符引用的是明確的類型,因此它意味著“某種 **flist** 引用沒有指定的具體類型”。因此這個被賦值的 **List** 必須持有諸如 **Fruit** 或 **Apple** 這樣的指定類型,但是為了向上轉型為 **Fruit**,這個類型是什么沒人在意。 **List** 必須持有一種具體的 **Fruit** 或 **Fruit** 的子類型,但是如果你不關心具體的類型是什么,那么你能對這樣的 **List** 做什么呢?如果不知道 **List** 中持有的對象是什么類型,你怎能保證安全地向其中添加對象呢?就像在 **CovariantArrays.java** 中向上轉型一樣,你不能,除非編譯器而不是運行時系統可以阻止這種操作的發生。你很快就會發現這個問題。 你可能認為事情開始變得有點走極端了,因為現在你甚至不能向剛剛聲明過將持有 **Apple** 對象的 **List** 中放入一個 **Apple** 對象。是的,但編譯器并不知道這一點。`List<? extends Fruit>` 可能合法地指向一個 `List<Orange>`。一旦執行這種類型的向上轉型,你就丟失了向其中傳遞任何對象的能力,甚至傳遞 **Object** 也不行。 另一方面,如果你調用了一個返回 **Fruit** 的方法,則是安全的,因為你知道這個 **List** 中的任何對象至少具有 **Fruit** 類型,因此編譯器允許這么做。 ### 編譯器有多聰明 現在你可能會猜想自己不能去調用任何接受參數的方法,但是考慮下面的代碼: ```java // generics/CompilerIntelligence.java import java.util.*; public class CompilerIntelligence { public static void main(String[] args) { List<? extends Fruit> flist = Arrays.asList(new Apple()); Apple a = (Apple) flist.get(0); // No warning flist.contains(new Apple()); // Argument is 'Object' flist.indexOf(new Apple()); // Argument is 'Object' } } ``` 這里對 `contains()` 和 `indexOf()` 的調用接受 **Apple** 對象作為參數,執行沒問題。這是否意味著編譯器實際上會檢查代碼,以查看是否有某個特定的方法修改了它的對象? 通過查看 **ArrayList** 的文檔,我們發現編譯器沒有那么聰明。盡管 `add()` 接受一個泛型參數類型的參數,但 `contains()` 和 `indexOf()` 接受的參數類型是 **Object**。因此當你指定一個 `ArrayList<? extends Fruit>` 時,`add()` 的參數就變成了"**? extends Fruit**"。從這個描述中,編譯器無法得知這里需要 **Fruit** 的哪個具體子類型,因此它不會接受任何類型的 **Fruit**。如果你先把 **Apple** 向上轉型為 **Fruit**,也沒有關系——編譯器僅僅會拒絕調用像 `add()` 這樣參數列表中涉及通配符的方法。 `contains()` 和 `indexOf()` 的參數類型是 **Object**,不涉及通配符,所以編譯器允許調用它們。這意味著將由泛型類的設計者來決定哪些調用是“安全的”,并使用 **Object** 類作為它們的參數類型。為了禁止對類型中使用了通配符的方法調用,需要在參數列表中使用類型參數。 下面展示一個簡單的 **Holder** 類: ```java // generics/Holder.java public class Holder<T> { private T value; public Holder() {} public Holder(T val) { value = val; } public void set(T val) { value = val; } public T get() { return value; } @Override public boolean equals(Object o) { return o instanceof Holder && Objects.equals(value, ((Holder) o).value); } @Override public int hashCode() { return Objects.hashCode(value); } public static void main(String[] args) { Holder<Apple> apple = new Holder<>(new Apple()); Apple d = apple.get(); apple.set(d); // Holder<Fruit> fruit = apple; // Cannot upcast Holder<? extends Fruit> fruit = apple; // OK Fruit p = fruit.get(); d = (Apple) fruit.get(); try { Orange c = (Orange) fruit.get(); // No warning } catch (Exception e) { System.out.println(e); } // fruit.set(new Apple()); // Cannot call set() // fruit.set(new Fruit()); // Cannot call set() System.out.println(fruit.equals(d)); // OK } } /* Output java.lang.ClassCastException: Apple cannot be cast to Orange false */ ``` **Holder** 有一個接受 **T** 類型對象的 `set()` 方法,一個返回 T 對象的 `get()` 方法和一個接受 Object 對象的 `equals()` 方法。正如你所見,如果創建了一個 `Holder<Apple>`,就不能將其向上轉型為 `Holder<Fruit>`,但是可以向上轉型為 `Holder<? extends Fruit>`。如果調用 `get()`,只能返回一個 **Fruit**——這就是在給定“任何擴展自 **Fruit** 的對象”這一邊界后,它所能知道的一切了。如果你知道更多的信息,就可以將其轉型到某種具體的 **Fruit** 而不會導致任何警告,但是存在得到 **ClassCastException** 的風險。`set()` 方法不能工作在 **Apple** 和 **Fruit** 上,因為 `set()` 的參數也是"**? extends Fruit**",意味著它可以是任何事物,編譯器無法驗證“任何事物”的類型安全性。 但是,`equals()` 方法可以正常工作,因為它接受的參數是 **Object** 而不是 **T** 類型。因此,編譯器只關注傳遞進來和要返回的對象類型。它不會分析代碼,以查看是否執行了任何實際的寫入和讀取操作。 Java 7 引入了 **java.util.Objects** 庫,使創建 `equals()` 和 `hashCode()` 方法變得更加容易,當然還有很多其他功能。`equals()` 方法的標準形式參考 [附錄:理解 equals 和 hashCode 方法](book/Appendix-Understanding-equals-and-hashCode) 一章。 ### 逆變 還可以走另外一條路,即使用超類型通配符。這里,可以聲明通配符是由某個特定類的任何基類來界定的,方法是指定 `<?super MyClass>` ,或者甚至使用類型參數: `<?super T>`(盡管你不能對泛型參數給出一個超類型邊界;即不能聲明 `<T super MyClass>` )。這使得你可以安全地傳遞一個類型對象到泛型類型中。因此,有了超類型通配符,就可以向 **Collection** 寫入了: ```java // generics/SuperTypeWildcards.java import java.util.*; public class SuperTypeWildcards { static void writeTo(List<? super Apple> apples) { apples.add(new Apple()); apples.add(new Jonathan()); // apples.add(new Fruit()); // Error } } ``` 參數 **apples** 是 **Apple** 的某種基類型的 **List**,這樣你就知道向其中添加 **Apple** 或 **Apple** 的子類型是安全的。但是因為 **Apple** 是下界,所以你知道向這樣的 **List** 中添加 **Fruit** 是不安全的,因為這將使這個 **List** 敞開口子,從而可以向其中添加非 **Apple** 類型的對象,而這是違反靜態類型安全的。 下面的示例復習了一下逆變和通配符的的使用: ```java // generics/GenericReading.java import java.util.*; public class GenericReading { static List<Apple> apples = Arrays.asList(new Apple()); static List<Fruit> fruit = Arrays.asList(new Fruit()); static <T> T readExact(List<T> list) { return list.get(0); } // A static method adapts to each call: static void f1() { Apple a = readExact(apples); Fruit f = readExact(fruit); f = readExact(apples); } // A class type is established // when the class is instantiated: static class Reader<T> { T readExact(List<T> list) { return list.get(0); } } static void f2() { Reader<Fruit> fruitReader = new Reader<>(); Fruit f = fruitReader.readExact(fruit); //- Fruit a = fruitReader.readExact(apples); // error: incompatible types: List<Apple> // cannot be converted to List<Fruit> } static class CovariantReader<T> { T readCovariant(List<? extends T> list) { return list.get(0); } } static void f3() { CovariantReader<Fruit> fruitReader = new CovariantReader<>(); Fruit f = fruitReader.readCovariant(fruit); Fruit a = fruitReader.readCovariant(apples); } public static void main(String[] args) { f1(); f2(); f3(); } } ``` `readExact()` 方法使用了精確的類型。如果使用這個沒有任何通配符的精確類型,就可以向 **List** 中寫入和讀取這個精確類型。另外,對于返回值,靜態的泛型方法 `readExact()` 可以有效地“適應”每個方法調用,并能夠從 `List<Apple>` 中返回一個 **Apple** ,從 `List<Fruit>` 中返回一個 **Fruit** ,就像在 `f1()` 中看到的那樣。因此,如果可以擺脫靜態泛型方法,那么在讀取時就不需要協變類型了。 然而對于泛型類來說,當你創建這個類的實例時,就要為這個類確定參數。就像在 `f2()` 中看到的,**fruitReader** 實例可以從 `List<Fruit>` 中讀取一個 **Fruit** ,因為這就是它的確切類型。但是 `List<Apple>` 也應該產生 **Fruit** 對象,而 **fruitReader** 不允許這么做。 為了修正這個問題,`CovariantReader.readCovariant()` 方法將接受 `List<?extends T>` ,因此,從這個列表中讀取一個 **T** 是安全的(你知道在這個列表中的所有對象至少是一個 **T** ,并且可能是從 T 導出的某種對象)。在 `f3()` 中,你可以看到現在可以從 `List<Apple>` 中讀取 **Fruit** 了。 ### 無界通配符 無界通配符 `<?>` 看起來意味著“任何事物”,因此使用無界通配符好像等價于使用原生類型。事實上,編譯器初看起來是支持這種判斷的: ```java // generics/UnboundedWildcards1.java import java.util.*; public class UnboundedWildcards1 { static List list1; static List<?> list2; static List<? extends Object> list3; static void assign1(List list) { list1 = list; list2 = list; //- list3 = list; // warning: [unchecked] unchecked conversion // list3 = list; // ^ // required: List<? extends Object> // found: List } static void assign2(List<?> list) { list1 = list; list2 = list; list3 = list; } static void assign3(List<? extends Object> list) { list1 = list; list2 = list; list3 = list; } public static void main(String[] args) { assign1(new ArrayList()); assign2(new ArrayList()); //- assign3(new ArrayList()); // warning: [unchecked] unchecked method invocation: // method assign3 in class UnboundedWildcards1 // is applied to given types // assign3(new ArrayList()); // ^ // required: List<? extends Object> // found: ArrayList // warning: [unchecked] unchecked conversion // assign3(new ArrayList()); // ^ // required: List<? extends Object> // found: ArrayList // 2 warnings assign1(new ArrayList<>()); assign2(new ArrayList<>()); assign3(new ArrayList<>()); // Both forms are acceptable as List<?>: List<?> wildList = new ArrayList(); wildList = new ArrayList<>(); assign1(wildList); assign2(wildList); assign3(wildList); } } ``` 有很多情況都和你在這里看到的情況類似,即編譯器很少關心使用的是原生類型還是 `<?>` 。在這些情況中,`<?>` 可以被認為是一種裝飾,但是它仍舊是很有價值的,因為,實際上它是在聲明:“我是想用 Java 的泛型來編寫這段代碼,我在這里并不是要用原生類型,但是在當前這種情況下,泛型參數可以持有任何類型。” 第二個示例展示了無界通配符的一個重要應用。當你在處理多個泛型參數時,有時允許一個參數可以是任何類型,同時為其他參數確定某種特定類型的這種能力會顯得很重要: ```java // generics/UnboundedWildcards2.java import java.util.*; public class UnboundedWildcards2 { static Map map1; static Map<?,?> map2; static Map<String,?> map3; static void assign1(Map map) { map1 = map; } static void assign2(Map<?,?> map) { map2 = map; } static void assign3(Map<String,?> map) { map3 = map; } public static void main(String[] args) { assign1(new HashMap()); assign2(new HashMap()); //- assign3(new HashMap()); // warning: [unchecked] unchecked method invocation: // method assign3 in class UnboundedWildcards2 // is applied to given types // assign3(new HashMap()); // ^ // required: Map<String,?> // found: HashMap // warning: [unchecked] unchecked conversion // assign3(new HashMap()); // ^ // required: Map<String,?> // found: HashMap // 2 warnings assign1(new HashMap<>()); assign2(new HashMap<>()); assign3(new HashMap<>()); } } ``` 但是,當你擁有的全都是無界通配符時,就像在 `Map<?,?>` 中看到的那樣,編譯器看起來就無法將其與原生 **Map** 區分開了。另外, **UnboundedWildcards1.java** 展示了編譯器處理 `List<?>` 和 `List<? extends Object>` 是不同的。 令人困惑的是,編譯器并非總是關注像 `List` 和 `List<?>` 之間的這種差異,因此它們看起來就像是相同的事物。事實上,因為泛型參數擦除到它的第一個邊界,因此 `List<?>` 看起來等價于 `List<Object>` ,而 **List** 實際上也是 `List<Object>` ——除非這些語句都不為真。**List** 實際上表示“持有任何 **Object** 類型的原生 **List** ”,而 `List<?>` 表示“具有某種特定類型的非原生 **List** ,只是我們不知道類型是什么。” 編譯器何時才會關注原生類型和涉及無界通配符的類型之間的差異呢?下面的示例使用了前面定義的 `Holder<T>` 類,它包含接受 **Holder** 作為參數的各種方法,但是它們具有不同的形式:作為原生類型,具有具體的類型參數以及具有無界通配符參數: ```java // generics/Wildcards.java // Exploring the meaning of wildcards public class Wildcards { // Raw argument: static void rawArgs(Holder holder, Object arg) { //- holder.set(arg); // warning: [unchecked] unchecked call to set(T) // as a member of the raw type Holder // holder.set(arg); // ^ // where T is a type-variable: // T extends Object declared in class Holder // 1 warning // Can't do this; don't have any 'T': // T t = holder.get(); // OK, but type information is lost: Object obj = holder.get(); } // Like rawArgs(), but errors instead of warnings: static void unboundedArg(Holder<?> holder, Object arg) { //- holder.set(arg); // error: method set in class Holder<T> // cannot be applied to given types; // holder.set(arg); // ^ // required: CAP#1 // found: Object // reason: argument mismatch; // Object cannot be converted to CAP#1 // where T is a type-variable: // T extends Object declared in class Holder // where CAP#1 is a fresh type-variable: // CAP#1 extends Object from capture of ? // 1 error // Can't do this; don't have any 'T': // T t = holder.get(); // OK, but type information is lost: Object obj = holder.get(); } static <T> T exact1(Holder<T> holder) { return holder.get(); } static <T> T exact2(Holder<T> holder, T arg) { holder.set(arg); return holder.get(); } static <T> T wildSubtype(Holder<? extends T> holder, T arg) { //- holder.set(arg); // error: method set in class Holder<T#2> // cannot be applied to given types; // holder.set(arg); // ^ // required: CAP#1 // found: T#1 // reason: argument mismatch; // T#1 cannot be converted to CAP#1 // where T#1,T#2 are type-variables: // T#1 extends Object declared in method // <T#1>wildSubtype(Holder<? extends T#1>,T#1) // T#2 extends Object declared in class Holder // where CAP#1 is a fresh type-variable: // CAP#1 extends T#1 from // capture of ? extends T#1 // 1 error return holder.get(); } static <T> void wildSupertype(Holder<? super T> holder, T arg) { holder.set(arg); //- T t = holder.get(); // error: incompatible types: // CAP#1 cannot be converted to T // T t = holder.get(); // ^ // where T is a type-variable: // T extends Object declared in method // <T>wildSupertype(Holder<? super T>,T) // where CAP#1 is a fresh type-variable: // CAP#1 extends Object super: // T from capture of ? super T // 1 error // OK, but type information is lost: Object obj = holder.get(); } public static void main(String[] args) { Holder raw = new Holder<>(); // Or: raw = new Holder(); Holder<Long> qualified = new Holder<>(); Holder<?> unbounded = new Holder<>(); Holder<? extends Long> bounded = new Holder<>(); Long lng = 1L; rawArgs(raw, lng); rawArgs(qualified, lng); rawArgs(unbounded, lng); rawArgs(bounded, lng); unboundedArg(raw, lng); unboundedArg(qualified, lng); unboundedArg(unbounded, lng); unboundedArg(bounded, lng); //- Object r1 = exact1(raw); // warning: [unchecked] unchecked method invocation: // method exact1 in class Wildcards is applied // to given types // Object r1 = exact1(raw); // ^ // required: Holder<T> // found: Holder // where T is a type-variable: // T extends Object declared in // method <T>exact1(Holder<T>) // warning: [unchecked] unchecked conversion // Object r1 = exact1(raw); // ^ // required: Holder<T> // found: Holder // where T is a type-variable: // T extends Object declared in // method <T>exact1(Holder<T>) // 2 warnings Long r2 = exact1(qualified); Object r3 = exact1(unbounded); // Must return Object Long r4 = exact1(bounded); //- Long r5 = exact2(raw, lng); // warning: [unchecked] unchecked method invocation: // method exact2 in class Wildcards is // applied to given types // Long r5 = exact2(raw, lng); // ^ // required: Holder<T>,T // found: Holder,Long // where T is a type-variable: // T extends Object declared in // method <T>exact2(Holder<T>,T) // warning: [unchecked] unchecked conversion // Long r5 = exact2(raw, lng); // ^ // required: Holder<T> // found: Holder // where T is a type-variable: // T extends Object declared in // method <T>exact2(Holder<T>,T) // 2 warnings Long r6 = exact2(qualified, lng); //- Long r7 = exact2(unbounded, lng); // error: method exact2 in class Wildcards // cannot be applied to given types; // Long r7 = exact2(unbounded, lng); // ^ // required: Holder<T>,T // found: Holder<CAP#1>,Long // reason: inference variable T has // incompatible bounds // equality constraints: CAP#1 // lower bounds: Long // where T is a type-variable: // T extends Object declared in // method <T>exact2(Holder<T>,T) // where CAP#1 is a fresh type-variable: // CAP#1 extends Object from capture of ? // 1 error //- Long r8 = exact2(bounded, lng); // error: method exact2 in class Wildcards // cannot be applied to given types; // Long r8 = exact2(bounded, lng); // ^ // required: Holder<T>,T // found: Holder<CAP#1>,Long // reason: inference variable T // has incompatible bounds // equality constraints: CAP#1 // lower bounds: Long // where T is a type-variable: // T extends Object declared in // method <T>exact2(Holder<T>,T) // where CAP#1 is a fresh type-variable: // CAP#1 extends Long from // capture of ? extends Long // 1 error //- Long r9 = wildSubtype(raw, lng); // warning: [unchecked] unchecked method invocation: // method wildSubtype in class Wildcards // is applied to given types // Long r9 = wildSubtype(raw, lng); // ^ // required: Holder<? extends T>,T // found: Holder,Long // where T is a type-variable: // T extends Object declared in // method <T>wildSubtype(Holder<? extends T>,T) // warning: [unchecked] unchecked conversion // Long r9 = wildSubtype(raw, lng); // ^ // required: Holder<? extends T> // found: Holder // where T is a type-variable: // T extends Object declared in // method <T>wildSubtype(Holder<? extends T>,T) // 2 warnings Long r10 = wildSubtype(qualified, lng); // OK, but can only return Object: Object r11 = wildSubtype(unbounded, lng); Long r12 = wildSubtype(bounded, lng); //- wildSupertype(raw, lng); // warning: [unchecked] unchecked method invocation: // method wildSupertype in class Wildcards // is applied to given types // wildSupertype(raw, lng); // ^ // required: Holder<? super T>,T // found: Holder,Long // where T is a type-variable: // T extends Object declared in // method <T>wildSupertype(Holder<? super T>,T) // warning: [unchecked] unchecked conversion // wildSupertype(raw, lng); // ^ // required: Holder<? super T> // found: Holder // where T is a type-variable: // T extends Object declared in // method <T>wildSupertype(Holder<? super T>,T) // 2 warnings wildSupertype(qualified, lng); //- wildSupertype(unbounded, lng); // error: method wildSupertype in class Wildcards // cannot be applied to given types; // wildSupertype(unbounded, lng); // ^ // required: Holder<? super T>,T // found: Holder<CAP#1>,Long // reason: cannot infer type-variable(s) T // (argument mismatch; Holder<CAP#1> // cannot be converted to Holder<? super T>) // where T is a type-variable: // T extends Object declared in // method <T>wildSupertype(Holder<? super T>,T) // where CAP#1 is a fresh type-variable: // CAP#1 extends Object from capture of ? // 1 error //- wildSupertype(bounded, lng); // error: method wildSupertype in class Wildcards // cannot be applied to given types; // wildSupertype(bounded, lng); // ^ // required: Holder<? super T>,T // found: Holder<CAP#1>,Long // reason: cannot infer type-variable(s) T // (argument mismatch; Holder<CAP#1> // cannot be converted to Holder<? super T>) // where T is a type-variable: // T extends Object declared in // method <T>wildSupertype(Holder<? super T>,T) // where CAP#1 is a fresh type-variable: // CAP#1 extends Long from capture of // ? extends Long // 1 error } } ``` 在 `rawArgs()` 中,編譯器知道 `Holder` 是一個泛型類型,因此即使它在這里被表示成一個原生類型,編譯器仍舊知道向 `set()` 傳遞一個 **Object** 是不安全的。由于它是原生類型,你可以將任何類型的對象傳遞給 `set()` ,而這個對象將被向上轉型為 **Object** 。因此無論何時,只要使用了原生類型,都會放棄編譯期檢查。對 `get()` 的調用說明了相同的問題:沒有任何 **T** 類型的對象,因此結果只能是一個 **Object**。 人們很自然地會開始考慮原生 `Holder` 與 `Holder<?>` 是大致相同的事物。但是 `unboundedArg()` 強調它們是不同的——它揭示了相同的問題,但是它將這些問題作為錯誤而不是警告報告,因為原生 **Holder** 將持有任何類型的組合,而 `Holder<?>` 將持有具有某種具體類型的同構集合,因此不能只是向其中傳遞 **Object** 。 在 `exact1()` 和 `exact2()` 中,你可以看到使用了確切的泛型參數——沒有任何通配符。你將看到,`exact2()`與 `exact1()` 具有不同的限制,因為它有額外的參數。 在 `wildSubtype()` 中,在 **Holder** 類型上的限制被放松為包括持有任何擴展自 **T** 的對象的 **Holder** 。這還是意味著如果 T 是 **Fruit** ,那么 `holder` 可以是 `Holder<Apple>` ,這是合法的。為了防止將 **Orange** 放置到 `Holder<Apple>` 中,對 `set()` 的調用(或者對任何接受這個類型參數為參數的方法的調用)都是不允許的。但是,你仍舊知道任何來自 `Holder<?extends Fruit>` 的對象至少是 **Fruit** ,因此 `get()` (或者任何將產生具有這個類型參數的返回值的方法)都是允許的。 `wildSupertype()` 展示了超類型通配符,這個方法展示了與 `wildSubtype()` 相反的行為:`holder` 可以是持有任何 T 的基類型的容器。因此, `set()` 可以接受 **T** ,因為任何可以工作于基類的對象都可以多態地作用于導出類(這里就是 **T** )。但是,嘗試著調用 `get()` 是沒有用的,因為由 `holder` 持有的類型可以是任何超類型,因此唯一安全的類型就是 **Object** 。 這個示例還展示了對于在 `unbounded()` 中使用無界通配符能夠做什么不能做什么所做出的限制:因為你沒有 **T**,所以你不能將 `set()` 或 `get()` 作用于 **T** 上。 在 `main()` 方法中你看到了某些方法在接受某些類型的參數時沒有報錯和警告。為了遷移兼容性,`rawArgs()` 將接受所有 **Holder** 的不同變體,而不會產生警告。`unboundedArg()` 方法也可以接受相同的所有類型,盡管如前所述,它在方法體內部處理這些類型的方式并不相同。 如果向接受“確切”泛型類型(沒有通配符)的方法傳遞一個原生 **Holder** 引用,就會得到一個警告,因為確切的參數期望得到在原生類型中并不存在的信息。如果向 `exact1()` 傳遞一個無界引用,就不會有任何可以確定返回類型的類型信息。 可以看到,`exact2()` 具有最多的限制,因為它希望精確地得到一個 `Holder<T>` ,以及一個具有類型 **T** 的參數,正由于此,它將產生錯誤或警告,除非提供確切的參數。有時,這樣做很好,但是如果它過于受限,那么就可以使用通配符,這取決于是否想要從泛型參數中返回類型確定的返回值(就像在 `wildSubtype()` 中看到的那樣),或者是否想要向泛型參數傳遞類型確定的參數(就像在 `wildSupertype()` 中看到的那樣)。 因此,使用確切類型來替代通配符類型的好處是,可以用泛型參數來做更多的事,但是使用通配符使得你必須接受范圍更寬的參數化類型作為參數。因此,必須逐個情況地權衡利弊,找到更適合你的需求的方法。 ### 捕獲轉換 有一種特殊情況需要使用 `<?>` 而不是原生類型。如果向一個使用 `<?>` 的方法傳遞原生類型,那么對編譯器來說,可能會推斷出實際的類型參數,使得這個方法可以回轉并調用另一個使用這個確切類型的方法。下面的示例演示了這種技術,它被稱為捕獲轉換,因為未指定的通配符類型被捕獲,并被轉換為確切類型。這里,有關警告的注釋只有在 `@SuppressWarnings` 注解被移除之后才能起作用: ```java // generics/CaptureConversion.java public class CaptureConversion { static <T> void f1(Holder<T> holder) { T t = holder.get(); System.out.println(t.getClass().getSimpleName()); } static void f2(Holder<?> holder) { f1(holder); // Call with captured type } @SuppressWarnings("unchecked") public static void main(String[] args) { Holder raw = new Holder<>(1); f1(raw); // warning: [unchecked] unchecked method invocation: // method f1 in class CaptureConversion // is applied to given types // f1(raw); // ^ // required: Holder<T> // found: Holder // where T is a type-variable: // T extends Object declared in // method <T>f1(Holder<T>) // warning: [unchecked] unchecked conversion // f1(raw); // ^ // required: Holder<T> // found: Holder // where T is a type-variable: // T extends Object declared in // method <T>f1(Holder<T>) // 2 warnings f2(raw); // No warnings Holder rawBasic = new Holder(); rawBasic.set(new Object()); // warning: [unchecked] unchecked call to set(T) // as a member of the raw type Holder // rawBasic.set(new Object()); // ^ // where T is a type-variable: // T extends Object declared in class Holder // 1 warning f2(rawBasic); // No warnings // Upcast to Holder<?>, still figures it out: Holder<?> wildcarded = new Holder<>(1.0); f2(wildcarded); } } /* Output: Integer Integer Object Double */ ``` `f1()` 中的類型參數都是確切的,沒有通配符或邊界。在 `f2()` 中,**Holder** 參數是一個無界通配符,因此它看起來是未知的。但是,在 `f2()` 中調用了 `f1()`,而 `f1()` 需要一個已知參數。這里所發生的是:在調用 `f2()` 的過程中捕獲了參數類型,并在調用 `f1()` 時使用了這種類型。 你可能想知道這項技術是否可以用于寫入,但是這要求在傳遞 `Holder<?>` 時同時傳遞一個具體類型。捕獲轉換只有在這樣的情況下可以工作:即在方法內部,你需要使用確切的類型。注意,不能從 `f2()` 中返回 **T**,因為 **T** 對于 `f2()` 來說是未知的。捕獲轉換十分有趣,但是非常受限。
                  <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>

                              哎呀哎呀视频在线观看