## 通配符
你已經在 [集合](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()` 來說是未知的。捕獲轉換十分有趣,但是非常受限。
- 譯者的話
- 前言
- 簡介
- 第一章 對象的概念
- 抽象
- 接口
- 服務提供
- 封裝
- 復用
- 繼承
- "是一個"與"像是一個"的關系
- 多態
- 單繼承結構
- 集合
- 對象創建與生命周期
- 異常處理
- 本章小結
- 第二章 安裝Java和本書用例
- 編輯器
- Shell
- Java安裝
- 校驗安裝
- 安裝和運行代碼示例
- 第三章 萬物皆對象
- 對象操縱
- 對象創建
- 數據存儲
- 基本類型的存儲
- 高精度數值
- 數組的存儲
- 代碼注釋
- 對象清理
- 作用域
- 對象作用域
- 類的創建
- 類型
- 字段
- 基本類型默認值
- 方法使用
- 返回類型
- 參數列表
- 程序編寫
- 命名可見性
- 使用其他組件
- static關鍵字
- 小試牛刀
- 編譯和運行
- 編碼風格
- 本章小結
- 第四章 運算符
- 開始使用
- 優先級
- 賦值
- 方法調用中的別名現象
- 算術運算符
- 一元加減運算符
- 遞增和遞減
- 關系運算符
- 測試對象等價
- 邏輯運算符
- 短路
- 字面值常量
- 下劃線
- 指數計數法
- 位運算符
- 移位運算符
- 三元運算符
- 字符串運算符
- 常見陷阱
- 類型轉換
- 截斷和舍入
- 類型提升
- Java沒有sizeof
- 運算符總結
- 本章小結
- 第五章 控制流
- true和false
- if-else
- 迭代語句
- while
- do-while
- for
- 逗號操作符
- for-in 語法
- return
- break 和 continue
- 臭名昭著的 goto
- switch
- switch 字符串
- 本章小結
- 第六章 初始化和清理
- 利用構造器保證初始化
- 方法重載
- 區分重載方法
- 重載與基本類型
- 返回值的重載
- 無參構造器
- this關鍵字
- 在構造器中調用構造器
- static 的含義
- 垃圾回收器
- finalize()的用途
- 你必須實施清理
- 終結條件
- 垃圾回收器如何工作
- 成員初始化
- 指定初始化
- 構造器初始化
- 初始化的順序
- 靜態數據的初始化
- 顯式的靜態初始化
- 非靜態實例初始化
- 數組初始化
- 動態數組創建
- 可變參數列表
- 枚舉類型
- 本章小結
- 第七章 封裝
- 包的概念
- 代碼組織
- 創建獨一無二的包名
- 沖突
- 定制工具庫
- 使用 import 改變行為
- 使用包的忠告
- 訪問權限修飾符
- 包訪問權限
- public: 接口訪問權限
- 默認包
- private: 你無法訪問
- protected: 繼承訪問權限
- 包訪問權限 Vs Public 構造器
- 接口和實現
- 類訪問權限
- 本章小結
- 第八章 復用
- 組合語法
- 繼承語法
- 初始化基類
- 帶參數的構造函數
- 委托
- 結合組合與繼承
- 保證適當的清理
- 名稱隱藏
- 組合與繼承的選擇
- protected
- 向上轉型
- 再論組合和繼承
- final關鍵字
- final 數據
- 空白 final
- final 參數
- final 方法
- final 和 private
- final 類
- final 忠告
- 類初始化和加載
- 繼承和初始化
- 本章小結
- 第九章 多態
- 向上轉型回顧
- 忘掉對象類型
- 轉機
- 方法調用綁定
- 產生正確的行為
- 可擴展性
- 陷阱:“重寫”私有方法
- 陷阱:屬性與靜態方法
- 構造器和多態
- 構造器調用順序
- 繼承和清理
- 構造器內部多態方法的行為
- 協變返回類型
- 使用繼承設計
- 替代 vs 擴展
- 向下轉型與運行時類型信息
- 本章小結
- 第十章 接口
- 抽象類和方法
- 接口創建
- 默認方法
- 多繼承
- 接口中的靜態方法
- Instrument 作為接口
- 抽象類和接口
- 完全解耦
- 多接口結合
- 使用繼承擴展接口
- 結合接口時的命名沖突
- 接口適配
- 接口字段
- 初始化接口中的字段
- 接口嵌套
- 接口和工廠方法模式
- 本章小結
- 第十一章 內部類
- 創建內部類
- 鏈接外部類
- 使用 .this 和 .new
- 內部類與向上轉型
- 內部類方法和作用域
- 匿名內部類
- 嵌套類
- 接口內部的類
- 從多層嵌套類中訪問外部類的成員
- 為什么需要內部類
- 閉包與回調
- 內部類與控制框架
- 繼承內部類
- 內部類可以被覆蓋么?
- 局部內部類
- 內部類標識符
- 本章小結
- 第十二章 集合
- 泛型和類型安全的集合
- 基本概念
- 添加元素組
- 集合的打印
- 迭代器Iterators
- ListIterator
- 鏈表LinkedList
- 堆棧Stack
- 集合Set
- 映射Map
- 隊列Queue
- 優先級隊列PriorityQueue
- 集合與迭代器
- for-in和迭代器
- 適配器方法慣用法
- 本章小結
- 簡單集合分類
- 第十三章 函數式編程
- 新舊對比
- Lambda表達式
- 遞歸
- 方法引用
- Runnable接口
- 未綁定的方法引用
- 構造函數引用
- 函數式接口
- 多參數函數式接口
- 缺少基本類型的函數
- 高階函數
- 閉包
- 作為閉包的內部類
- 函數組合
- 柯里化和部分求值
- 純函數式編程
- 本章小結
- 第十四章 流式編程
- 流支持
- 流創建
- 隨機數流
- int 類型的范圍
- generate()
- iterate()
- 流的建造者模式
- Arrays
- 正則表達式
- 中間操作
- 跟蹤和調試
- 流元素排序
- 移除元素
- 應用函數到元素
- 在map()中組合流
- Optional類
- 便利函數
- 創建 Optional
- Optional 對象操作
- Optional 流
- 終端操作
- 數組
- 集合
- 組合
- 匹配
- 查找
- 信息
- 數字流信息
- 本章小結
- 第十五章 異常
- 異常概念
- 基本異常
- 異常參數
- 異常捕獲
- try 語句塊
- 異常處理程序
- 終止與恢復
- 自定義異常
- 異常與記錄日志
- 異常聲明
- 捕獲所有異常
- 多重捕獲
- 棧軌跡
- 重新拋出異常
- 精準的重新拋出異常
- 異常鏈
- Java 標準異常
- 特例:RuntimeException
- 使用 finally 進行清理
- finally 用來做什么?
- 在 return 中使用 finally
- 缺憾:異常丟失
- 異常限制
- 構造器
- Try-With-Resources 用法
- 揭示細節
- 異常匹配
- 其他可選方式
- 歷史
- 觀點
- 把異常傳遞給控制臺
- 把“被檢查的異常”轉換為“不檢查的異常”
- 異常指南
- 本章小結
- 后記:Exception Bizarro World
- 第十六章 代碼校驗
- 測試
- 如果沒有測試過,它就是不能工作的
- 單元測試
- JUnit
- 測試覆蓋率的幻覺
- 前置條件
- 斷言(Assertions)
- Java 斷言語法
- Guava斷言
- 使用斷言進行契約式設計
- 檢查指令
- 前置條件
- 后置條件
- 不變性
- 放松 DbC 檢查或非嚴格的 DbC
- DbC + 單元測試
- 使用Guava前置條件
- 測試驅動開發
- 測試驅動 vs. 測試優先
- 日志
- 日志會給出正在運行的程序的各種信息
- 日志等級
- 調試
- 使用 JDB 調試
- 圖形化調試器
- 基準測試
- 微基準測試
- JMH 的引入
- 剖析和優化
- 優化準則
- 風格檢測
- 靜態錯誤分析
- 代碼重審
- 結對編程
- 重構
- 重構基石
- 持續集成
- 本章小結
- 第十七章 文件
- 文件和目錄路徑
- 選取路徑部分片段
- 路徑分析
- Paths的增減修改
- 目錄
- 文件系統
- 路徑監聽
- 文件查找
- 文件讀寫
- 本章小結
- 第十八章 字符串
- 字符串的不可變
- +的重載與StringBuilder
- 意外遞歸
- 字符串操作
- 格式化輸出
- printf()
- System.out.format()
- Formatter類
- 格式化修飾符
- Formatter轉換
- String.format()
- 一個十六進制轉儲(dump)工具
- 正則表達式
- 基礎
- 創建正則表達式
- 量詞
- CharSequence
- Pattern和Matcher
- find()
- 組(Groups)
- start()和end()
- Pattern標記
- split()
- 替換操作
- 正則表達式與 Java I/O
- 掃描輸入
- Scanner分隔符
- 用正則表達式掃描
- StringTokenizer類
- 本章小結
- 第十九章 類型信息
- 為什么需要 RTTI
- Class對象
- 類字面常量
- 泛化的Class引用
- cast()方法
- 類型轉換檢測
- 使用類字面量
- 遞歸計數
- 一個動態instanceof函數
- 注冊工廠
- 類的等價比較
- 反射:運行時類信息
- 類方法提取器
- 動態代理
- Optional類
- 標記接口
- Mock 對象和樁
- 接口和類型
- 本章小結
- 第二十章 泛型
- 簡單泛型
- 泛型接口
- 泛型方法
- 復雜模型構建
- 泛型擦除
- 補償擦除
- 邊界
- 通配符
- 問題
- 自限定的類型
- 動態類型安全
- 泛型異常
- 混型
- 潛在類型機制
- 對缺乏潛在類型機制的補償
- Java8 中的輔助潛在類型
- 總結:類型轉換真的如此之糟嗎?
- 進階閱讀
- 第二十一章 數組
- 數組特性
- 一等對象
- 返回數組
- 多維數組
- 泛型數組
- Arrays的fill方法
- Arrays的setAll方法
- 增量生成
- 隨機生成
- 泛型和基本數組
- 數組元素修改
- 數組并行
- Arrays工具類
- 數組比較
- 數組拷貝
- 流和數組
- 數組排序
- Arrays.sort()的使用
- 并行排序
- binarySearch二分查找
- parallelPrefix并行前綴
- 本章小結
- 第二十二章 枚舉
- 基本 enum 特性
- 將靜態類型導入用于 enum
- 方法添加
- 覆蓋 enum 的方法
- switch 語句中的 enum
- values 方法的神秘之處
- 實現而非繼承
- 隨機選擇
- 使用接口組織枚舉
- 使用 EnumSet 替代 Flags
- 使用 EnumMap
- 常量特定方法
- 使用 enum 的職責鏈
- 使用 enum 的狀態機
- 多路分發
- 使用 enum 分發
- 使用常量相關的方法
- 使用 EnumMap 進行分發
- 使用二維數組
- 本章小結
- 第二十三章 注解
- 基本語法
- 定義注解
- 元注解
- 編寫注解處理器
- 注解元素
- 默認值限制
- 替代方案
- 注解不支持繼承
- 實現處理器
- 使用javac處理注解
- 最簡單的處理器
- 更復雜的處理器
- 基于注解的單元測試
- 在 @Unit 中使用泛型
- 實現 @Unit
- 本章小結
- 第二十四章 并發編程
- 術語問題
- 并發的新定義
- 并發的超能力
- 并發為速度而生
- 四句格言
- 1.不要這樣做
- 2.沒有什么是真的,一切可能都有問題
- 3.它起作用,并不意味著它沒有問題
- 4.你必須仍然理解
- 殘酷的真相
- 本章其余部分
- 并行流
- 創建和運行任務
- 終止耗時任務
- CompletableFuture類
- 基本用法
- 結合 CompletableFuture
- 模擬
- 異常
- 流異常(Stream Exception)
- 檢查性異常
- 死鎖
- 構造方法非線程安全
- 復雜性和代價
- 本章小結
- 缺點
- 共享內存陷阱
- This Albatross is Big
- 其他類庫
- 考慮為并發設計的語言
- 拓展閱讀
- 第二十五章 設計模式
- 概念
- 單例模式
- 模式分類
- 構建應用程序框架
- 面向實現
- 工廠模式
- 動態工廠
- 多態工廠
- 抽象工廠
- 函數對象
- 命令模式
- 策略模式
- 責任鏈模式
- 改變接口
- 適配器模式(Adapter)
- 外觀模式(Fa?ade)
- 包(Package)作為外觀模式的變體
- 解釋器:運行時的彈性
- 回調
- 多次調度
- 模式重構
- 抽象用法
- 多次派遣
- 訪問者模式
- RTTI的優劣
- 本章小結
- 附錄:補充
- 附錄:編程指南
- 附錄:文檔注釋
- 附錄:對象傳遞和返回
- 附錄:流式IO
- 輸入流類型
- 輸出流類型
- 添加屬性和有用的接口
- 通過FilterInputStream 從 InputStream 讀取
- 通過 FilterOutputStream 向 OutputStream 寫入
- Reader和Writer
- 數據的來源和去處
- 更改流的行為
- 未發生改變的類
- RandomAccessFile類
- IO流典型用途
- 緩沖輸入文件
- 從內存輸入
- 格式化內存輸入
- 基本文件的輸出
- 文本文件輸出快捷方式
- 存儲和恢復數據
- 讀寫隨機訪問文件
- 本章小結
- 附錄:標準IO
- 附錄:新IO
- ByteBuffer
- 數據轉換
- 基本類型獲取
- 視圖緩沖區
- 字節存儲次序
- 緩沖區數據操作
- 緩沖區細節
- 內存映射文件
- 性能
- 文件鎖定
- 映射文件的部分鎖定
- 附錄:理解equals和hashCode方法
- 附錄:集合主題
- 附錄:并發底層原理
- 附錄:數據壓縮
- 附錄:對象序列化
- 附錄:靜態語言類型檢查
- 附錄:C++和Java的優良傳統
- 附錄:成為一名程序員