@@ -67,6 +67,7 @@ public class ObjectFoo {
67
67
68
68
由於 Java 中所有定義的類別,都以 Object 為最上層的父類別,所以用它來實現泛型(Generics)功能是一個不錯的考量,在 J2SE 1.4 或之前版本上,大部份的開發人員會這麼作,您只要撰寫如範例 12.3 的類別,然後可以如下的使用它:
69
69
70
+ ``` java
70
71
ObjectFoo foo1 = new ObjectFoo ();
71
72
ObjectFoo foo2 = new ObjectFoo ();
72
73
@@ -77,12 +78,15 @@ public class ObjectFoo {
77
78
foo2. setFoo(new Integer (10 ));
78
79
// 記得轉換操作型態
79
80
Integer i = (Integer ) foo2. getFoo();
81
+ ```
80
82
81
83
看來還不錯,但是設定至 foo1 或 foo2 的 Integer 或 Boolean 實例會失去其型態資訊,從 getFoo() 傳回的是 Object 型態的實例,您必須轉換它的操作型態,問題出在這邊,粗心的程式設計人員往往會忘了要作這個動作,或者是轉換型態時用錯了型態 (像是該用Boolean卻用了Integer),例如:
82
84
85
+ ``` java
83
86
ObjectFoo foo1 = new ObjectFoo ();
84
87
foo1. setFoo(new Boolean (true ));
85
88
String s = (String ) foo1. getFoo();
89
+ ```
86
90
87
91
由於語法上並沒有錯誤,所以編譯器檢查不出上面的程式有錯誤,真正的錯誤要在執行時期才會發生,這時惱人的 ClassCastException 就會出來搞怪,在使用 Object 設計泛型程式時,程式人員要再細心一些,例如在 J2SE 1.4 或舊版本上,所有存入 List、Map、Set 容器中的實例都會失去其型態資訊,要從這些容器中取回物件並加以操作的話,就得記住取回的物件是什麼型態。
88
92
@@ -133,9 +137,11 @@ public class GenericFooDemo {
133
137
134
138
與單純使用 Object 宣告型態所不同的地方在於,使用泛型所定義的類別在宣告及配置物件時,您可以使用角括號一併指定泛型類別型態持有者 T 真正的型態,而型態或介面轉換就不再需要了,getFoo() 所設定的引數或傳回的型態,就是您在宣告及配置物件時在 ` <> ` 之間所指定的型態,您所定義出來的泛型類別在使用時多了一層安全性,可以省去惱人的 ClassCastException 發生,編譯器可以幫您作第一層防線,例如下面的程式會被檢查出錯誤:
135
139
140
+ ``` java
136
141
GenericFoo<Boolean > foo1 = new GenericFoo<Boolean > ();
137
142
foo1. setFoo(new Boolean (true ));
138
143
Integer i = foo1. getFoo(); // 傳回的是Boolean型態
144
+ ```
139
145
140
146
foo1 使用 getFoo() 方法傳回的是 Boolean 型態的實例,若您要將這個實例指定給 Integer 型態的變數,顯然在語法上不合,編譯器這時檢查出錯誤:
141
147
@@ -146,8 +152,10 @@ foo1 使用 getFoo() 方法傳回的是 Boolean 型態的實例,若您要將
146
152
147
153
如果使用泛型類別,但宣告及配置物件時不一併指定型態呢?那麼預設會使用 Object 型態,不過您就要自己轉換物件的介面型態了,例如 GenericFoo 可以這麼宣告與使用:
148
154
155
+ ``` java
149
156
GenericFoo foo3 = new GenericFoo ();
150
157
foo3. setFoo(new Boolean (false ));
158
+ ```
151
159
152
160
但編譯時編譯器會提出警訊,告訴您這可能是不安全的操作:
153
161
@@ -156,8 +164,10 @@ foo1 使用 getFoo() 方法傳回的是 Boolean 型態的實例,若您要將
156
164
157
165
回過頭來看看下面的宣告:
158
166
167
+ ``` java
159
168
GenericFoo<Boolean > foo1 = new GenericFoo<Boolean > ();
160
169
GenericFoo<Integer > foo2 = new GenericFoo<Integer > ();
170
+ ```
161
171
162
172
` GenericFoo<Boolean> ` 宣告的 foo1 與 ` GenericFoo<Integer> ` 宣告的 foo2 是相同的類型嗎?答案是否定的!基本上 foo1 與 foo2 是兩個不同的類型,foo1 是 ` GenericFoo<Boolean> ` 類型,而 foo2 是 ` GenericFoo<Integer> ` 類型,所以您不可以將 foo1 所參考的實例指定給 foo2,或是將 foo2 所參考的實例指定給 foo1,要不然編譯器會回報以下錯誤:
163
173
@@ -198,8 +208,10 @@ public class GenericFoo2<T1, T2> {
198
208
199
209
您可以如下使用 GenericFoo2 類別,分別以 Integer 與 Boolean 設定 T1 與 T2 的真正型態:
200
210
211
+ ``` java
201
212
GenericFoo<Integer , Boolean > foo =
202
213
new GenericFoo<Integer , Boolean > ();
214
+ ```
203
215
204
216
泛型可以用於宣告陣列型態,範例 12.7 是個簡單示範。
205
217
@@ -220,24 +232,30 @@ public class GenericFoo3<T> {
220
232
221
233
您可以像下面的方式來使用範例 12.7 所定義的類別。
222
234
223
- `String[] strs = {"caterpillar", "momor", "bush"};
235
+ ``` java
236
+ String [] strs = {" caterpillar" , " momor" , " bush" };
224
237
GenericFoo3<String > foo = new GenericFoo3<String > ();
225
238
foo. setFooArray(strs);
226
239
strs = foo. getFooArray();
240
+ ```
227
241
228
242
注意您可以使用泛型機制來宣告一個陣列,例如下面這樣是可行的:
229
243
244
+ ``` java
230
245
public class GenericFoo <T> {
231
246
private T [] fooArray;
232
247
// ...
233
248
}
249
+ ```
234
250
235
251
但是您不可以使用泛型來建立陣列的實例,例如以下是不可行的:
236
252
253
+ ``` java
237
254
public class GenericFoo <T> {
238
255
private T [] fooArray = new T [10 ]; // 不可以使用泛型建立陣列實例
239
256
// ...
240
257
}
258
+ ```
241
259
242
260
如果您已經定義了一個泛型類別,想要用這個類別在另一個泛型類別中宣告成員的話要如何作?舉個實例,假設您已經定義了範例 12.4 的類別,現在想要設計一個新的類別,當中包括了範例12.4的類別實例作為其成員,您可以如範例 12.8 的方式設計。
243
261
@@ -258,10 +276,12 @@ public class WrapperFoo<T> {
258
276
259
277
這麼一來,您就可以保留型態持有者T的功能,一個使用的例子如下:
260
278
279
+ ``` java
261
280
GenericFoo<Integer > foo = new GenericFoo<Integer > ();
262
281
foo. setFoo(new Integer (10 ));
263
282
WrapperFoo<Integer > wrapper = new WrapperFoo<Integer > ();
264
283
wrapper. setFoo(foo);
284
+ ```
265
285
266
286
## 12.2 泛型進階語法
267
287
@@ -292,32 +312,42 @@ public class ListGenericFoo<T extends List> {
292
312
293
313
ListGenericFoo 在宣告類型持有者時,一併指定這個持有者實例化的對象,必須是實作 java.util.List 介面(interface)的類別,在限定持有者時,無論是要限定的對象是介面或類別,都是使用 "extends" 關鍵字,範例中您使用 "extends" 限定型態持有者實例化的對象,必須是實作 List 介面的類別,像 java.util.LinkedList 與 java.util.ArrayList 就實作了 List 介面(第 13 章就會介紹),例如下面的程式片段是合法的使用方式:
294
314
315
+ ``` java
295
316
ListGenericFoo<LinkedList > foo1 =
296
317
new ListGenericFoo<LinkedList > ();
297
318
ListGenericFoo<ArrayList > foo2 =
298
319
new ListGenericFoo<ArrayList > ();
299
320
321
+ ```
300
322
但如果不是實作 List 的類別,編譯時就會發生錯誤,例如下面的程式片段通不過編譯:
301
323
324
+ ``` java
302
325
ListGenericFoo<HashMap > foo3 =
303
326
new ListGenericFoo<HashMap > ();
327
+ ```
304
328
305
329
因為 java.util.HashMap 並沒有實作 List 介面(事實上 HashMap 實作了 Map 介面),編譯器會在編譯時期就檢查出這個錯誤:
306
330
331
+ ``` java
307
332
type parameter java.util. HashMap is not within its bound
308
333
ListGenericFoo<HashMap > foo3 = new ListGenericFoo<HashMap > ();
334
+ ```
309
335
310
336
HashMap 並沒有實作 List 介面,所以無法作為實例化型態持有者的對象,事實上,當您沒有使用 "extends" 關鍵字限定型態持有者時,預設是 Object 下的所有子類別都可以實例化型態持有者,也就是說在您定義泛型類別時如果只寫以下的話:
311
337
338
+ ``` java
312
339
public class GenericFoo <T> {
313
340
// ....
314
341
}
342
+ ```
315
343
316
344
其實就相當於以下的定義方式:
317
345
346
+ ``` java
318
347
public class GenericFoo <T extends Object > {
319
348
// ....
320
349
}
350
+ ```
321
351
322
352
由於 Java 中所有的實例都繼承自 Object 類別,所以定義時若只寫 ` <T> ` 就表示,所有類型的物件都可以實例化您所定義的泛型類別。
323
353
@@ -344,27 +374,35 @@ HashMap 並沒有實作 List 介面,所以無法作為實例化型態持有者
344
374
345
375
仍然以範例 12.4 所定義的 GenericFoo 來進行說明,假設您使用 GenericFoo 類別來如下宣告名稱:
346
376
377
+ ``` java
347
378
GenericFoo<Integer > foo1 = null ;
348
379
GenericFoo<Boolean > foo2 = null ;
380
+ ```
349
381
350
382
那麼名稱 foo1 就只能參考 ` GenericFoo<Integer> ` 類型的實例,而名稱 foo2 只能參考 ` GenericFoo<Boolean> ` 類型的實例,也就是說下面的方式是可行的:
351
383
384
+ ``` java
352
385
foo1 = new GenericFoo<Integer > ();
353
386
foo2 = new GenericFoo<Boolean > ();
387
+ ```
354
388
355
389
現在您有這麼一個需求,您希望有一個參考名稱 foo 可以如下接受所指定的實例:
356
390
391
+ ``` java
357
392
foo = new GenericFoo<ArrayList > ();
358
393
foo = new GenericFoo<LinkedList > ();
394
+ ```
359
395
360
396
簡單的說,您想要有一個 foo 名稱可以參考的對象,其型態持有者實例化的對象是實作 List 介面的類別或其子類別,要宣告這麼一個參考名稱,您可以使用 '?'「通配字元」(Wildcard),'?' 代表未知型態,並使用 "extends" 關鍵字來作限定,例如:
361
397
398
+ ``` java
362
399
GenericFoo<? extends List > foo = null ;
363
400
foo = new GenericFoo<ArrayList > ();
364
401
.....
365
402
foo = new GenericFoo<LinkedList > ();
366
403
....
367
-
404
+ ```
405
+
368
406
` <? extends List> ` 表示型態未知,只知會是實作 List 介面的類別,所以如果型態持有者實例化的對象不是實作 List 介面的類別,則編譯器會回報錯誤,例如以下這行無法通過編譯:
369
407
370
408
GenericFoo<? extends List> foo = new GenericFoo<HashMap>();
@@ -378,19 +416,24 @@ HashMap 並沒有實作 List 介面,所以無法作為實例化型態持有者
378
416
379
417
使用 '?' 來作限定有時是很有用的,例如若您想要自訂一個 showFoo() 方法,方法的內容實作是針對 String 或其子類的實例而制定的,例如:
380
418
419
+ ``` java
381
420
public void showFoo(GenericFoo foo) {
382
421
// 針對String或其子類而制定的內容
383
422
}
423
+ ```
384
424
385
425
如果只作以上的宣告,那麼像 ` GenericFoo<Integer> ` 、` GenericFoo<Boolean> ` 等型態都可以傳入至方法中,如果您不希望任何的型態都可以傳 入showFoo() 方法中,您可以使用以下的方式來限定:
386
426
427
+ ``` java
387
428
public void showFoo(GenericFoo<? extends String > foo) {
388
429
// 針對String或其子類而制定的內容,例如下面這行
389
430
System . out. println(foo. getFoo());
390
431
}
432
+ ```
391
433
392
434
這麼一來,如果有粗心的程式設計人員傳入了您不想要的型態,例如 ` GenericFoo<Boolean> ` 型態的實例,則編譯器都會告訴它這是不可行的,在宣告名稱時如果指定了 ` <?> ` 而不使用 "extends",則預設是允許 Object 及其下的子類,也就是所有的 Java 物件了,那為什麼不直接使用 ` GenericFoo ` 宣告就好了,何必要用 ` GenericFoo<?> ` 來宣告?使用通配字元有點要注意的是,透過使用通配字元宣告的名稱所參考的物件,您沒辦法再對它加入新的資訊,您只能取得它當中的資訊或是移除當中的資訊,例如:
393
435
436
+ ``` java
394
437
GenericFoo<String > foo = new GenericFoo<String > ();
395
438
foo. setFoo(" caterpillar" );
396
439
@@ -404,12 +447,15 @@ HashMap 並沒有實作 List 介面,所以無法作為實例化型態持有者
404
447
// 不可透過immutableFoo來設定新的資訊給foo所參考的實例
405
448
// 所以下面這行無法通過編譯
406
449
// immutableFoo.setFoo("良葛格");
450
+ ```
407
451
408
452
所以使用 ` <?> ` 或是 ` <? extends SomeClass> ` 的宣告方式,意味著您只能透過該名稱來取得所參考實例的資訊,或者是移除某些資訊,但不能增加它的資訊,理由很簡單,因為您不知道 ` <?> ` 或是 ` <? extends SomeClass> ` 宣告的參考名稱,實際上參考的物件,當中確實儲存的是什麼類型的資訊,基於泛型的設計理念,當然也就沒有理由能加入新的資訊了,因為若能加入,被加入的物件同樣也會有失去型態資訊的問題。
409
453
410
454
除了可以向下限制,您也可以向上限制,只要使用 "super" 關鍵字,例如:
411
455
456
+ ``` java
412
457
GenericFoo<? super StringBuilder > foo = null ;
458
+ ```
413
459
414
460
如此, foo 就只接受 StringBuilder 及其上層的父類型態,也就是只能接受 ` GenericFoo<StringBuilder> ` 與 ` GenericFoo<Object> ` 的實例。
415
461
0 commit comments