Changes between Version 1 and Version 2 of Generic
- Timestamp:
- Jul 10, 2008, 4:28:16 PM (17 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
Generic
v1 v2 3 3 由於Java中所有定義的類別,都以Object為最上層的父類別,所以在 J2SE 5.0 之前,Java程式設計人員可以使用Object來解決上面這樣的需求,為了讓定義出來的類別可以更加通用(Generic),傳入的值或傳回的物件都是以Object為主,當您要取出這些物件來使用時,必須記得將介面轉換為原來的類型,這樣才可以操作物件上的方法。 4 4 5 然而使用Object來撰寫泛型類別(Generic Class)留下了一個問題,因為您必須要轉換介面,粗心的程式設計人員往往會忘了要作這個動作,或者是轉換介面時用錯了型態(像是該用Boolean卻用了Integer),要命的是,語法上是可以的,所以編譯器檢查不出錯誤,真正的錯誤要在執行時期才會發生,這時惱人的 ClassCastException就會出來搞怪,在使用Object設計泛型程式時,程式人員要再細心一些、小心一些。5 然而使用Object來撰寫泛型類別(Generic Class)留下了一個問題,因為您必須要轉換介面,粗心的程式設計人員往往會忘了要作這個動作,或者是轉換介面時用錯了型態(像是該用Boolean卻用了Integer),要命的是,語法上是可以的,所以編譯器檢查不出錯誤,真正的錯誤要在執行時期才會發生,這時惱人的!ClassCastException就會出來搞怪,在使用Object設計泛型程式時,程式人員要再細心一些、小心一些。 6 6 7 7 在J2SE 5.0之後,提出了針對泛型(Generics)設計的解決方案,要定義一個簡單的泛型類別是簡單的,直接來看個例子: 8 8 9 * GenericFoo.java9 * !GenericFoo.java 10 10 {{{ 11 11 #!java … … 25 25 <T> 用來宣告一個型態持有者(Holder)T,之後您可以用 T 作為型態代表來宣告變數(參考)名稱,然後您可以像下面的程式來使用這個類別: 26 26 {{{ 27 #!java 27 28 GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>(); 28 29 GenericFoo<Integer> foo2 = new GenericFoo<Integer>(); … … 37 38 回過頭來看看下面的宣告: 38 39 {{{ 40 #!java 39 41 GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>(); 40 42 GenericFoo<Integer> foo2 = new GenericFoo<Integer>(); 41 43 }}} 42 44 43 GenericFoo< Boolean>宣告的foo1與GenericFoo< Integer>宣告的foo2是相同的類型嗎?答案是否定的,基本上它們分屬於兩個不同類別的類型,即「相當於」下面兩個類型(只是個比喻): 44 {{{ 45 !GenericFoo< Boolean>宣告的foo1與!GenericFoo< Integer>宣告的foo2是相同的類型嗎?答案是否定的,基本上它們分屬於兩個不同類別的類型,即「相當於」下面兩個類型(只是個比喻): 46 {{{ 47 #!java 45 48 public class GenericFooBoolean { 46 49 private Boolean foo; … … 57 60 以及: 58 61 {{{ 62 #!java 59 63 public class GenericFooInteger { 60 64 private Integer foo; … … 76 80 您可以在定義泛型類別時,宣告多個類型持有者,例如: 77 81 78 * GenericFoo.java 79 80 {{{ 82 * !GenericFoo.java 83 84 {{{ 85 #!java 81 86 public class GenericFoo<T1, T2> { 82 87 private T1 foo1; … … 100 105 } 101 106 }}} 102 您可以如下使用 GenericFoo類別,分別以Integer與Boolean取代T1與T2:[[BR]]103 104 105 GenericFoo<Integer, Boolean> foo = newGenericFoo<Integer, Boolean>();[[BR]]107 您可以如下使用!GenericFoo類別,分別以Integer與Boolean取代T1與T2:[[BR]] 108 109 110 !GenericFoo<Integer, Boolean> foo = new !GenericFoo<Integer, Boolean>();[[BR]] 106 111 107 112 108 113 如果是陣列的話,可以像這樣: 109 114 110 * GenericFoo.java 111 112 {{{ 115 * !GenericFoo.java 116 117 {{{ 118 #!java 113 119 public class GenericFoo<T> { 114 120 private T[] fooArray; … … 126 132 您可以像下面的方式來使用它: 127 133 {{{ 134 #!java 128 135 String[] strs = {"caterpillar", "momor", "bush"}; 129 136 … … 132 139 strs = foo.getFooArray(); 133 140 }}} 134 來改寫一下 Object 類別 中的 SimpleCollection: 135 136 * SimpleCollection.java 137 138 {{{ 141 來改寫一下 Object 類別 中的 !SimpleCollection: 142 143 * !SimpleCollection.java 144 145 {{{ 146 #!java 139 147 public class SimpleCollection<T> { 140 148 private T[] objArr; … … 168 176 * Test.java 169 177 {{{ 178 #!java 170 179 public class Test { 171 180 public static void main(String[] args) { … … 184 193 }}} 185 194 186 另一個SimpleCollection的寫法也可以如下,作用是一樣的: 187 188 * SimpleCollection.java 189 {{{ 195 另一個!SimpleCollection的寫法也可以如下,作用是一樣的: 196 197 * !SimpleCollection.java 198 {{{ 199 #!java 190 200 public class SimpleCollection<T> { 191 201 private Object[] objArr; … … 219 229 * GenericFoo.java 220 230 {{{ 231 #!java 221 232 public class GenericFoo<T> { 222 233 private T foo; … … 232 243 }}} 233 244 234 您想要寫一個包裝類別(Wrapper),這個類別必須也具有GenericFoo的泛型功能,您可以這麼寫: 235 236 * WrapperFoo.java 237 {{{ 245 您想要寫一個包裝類別(Wrapper),這個類別必須也具有!GenericFoo的泛型功能,您可以這麼寫: 246 247 * !WrapperFoo.java 248 {{{ 249 #!java 238 250 public class WrapperFoo<T> { 239 251 private GenericFoo<T> foo; … … 252 264 這麼一來,您就可以保留型態持有者 T 的功能,一個使用的例子如下: 253 265 {{{ 266 #!java 254 267 GenericFoo<Integer> foo = new GenericFoo<Integer>(); 255 268 foo.setFoo(new Integer(10)); … … 264 277 您可以在定義型態持有者時,一併使用"extends"指定這個型態持有者必須是擴充某個類型,舉個實例來說: 265 278 266 * ListGenericFoo.java 267 268 {{{ 279 * !ListGenericFoo.java 280 281 {{{ 282 #!java 269 283 import java.util.List; 270 284 … … 282 296 }}} 283 297 284 ListGenericFoo在宣告類型持有者時,一併指定這個持有者必須擴充自List介面(interface),在限定持有者時,無論是要限定的對象是介面或類別,都是使用"extends"關鍵字。[[BR]] 285 [[BR]] 286 287 288 您使用"extends"限定型態持有者必須是實作List的類別或其子類別,例如LinkedList與ArrayList,下面的程式是合法的:[[BR]] 289 {{{ 298 !ListGenericFoo在宣告類型持有者時,一併指定這個持有者必須擴充自List介面(interface),在限定持有者時,無論是要限定的對象是介面或類別,都是使用"extends"關鍵字。[[BR]] 299 [[BR]] 300 301 302 您使用"extends"限定型態持有者必須是實作List的類別或其子類別,例如!LinkedList與!ArrayList,下面的程式是合法的:[[BR]] 303 {{{ 304 #!java 290 305 ListGenericFoo<LinkedList> foo1 = 291 306 new ListGenericFoo<LinkedList>(); … … 297 312 [[BR]] 298 313 299 ''' ListGenericFoo<HashMap> foo3 = new ListGenericFoo<HashMap>();'''[[BR]]314 '''!ListGenericFoo<!!HashMap> foo3 = new !ListGenericFoo<!!HashMap>();'''[[BR]] 300 315 [[BR]] 301 316 302 317 編譯器會回報以下錯誤訊息:[[BR]] 303 type parameter java.util. HashMap is not within its bound[[BR]]304 ListGenericFoo<HashMap> foo3 = new ListGenericFoo<HashMap>();[[BR]][[BR]]305 306 HashMap並沒有實作List介面,所以無法用來實例化型態持有者,事實上,當您沒有使用extends關鍵字限定型態持有者時,預設則是Object下的所有子類別都可以實例化型態持有者,即只寫<T>時就相當於<T extends Object>。[[BR]]318 type parameter java.util.!HashMap is not within its bound[[BR]] 319 !ListGenericFoo<!HashMap> foo3 = new !ListGenericFoo<!HashMap>();[[BR]][[BR]] 320 321 !HashMap並沒有實作List介面,所以無法用來實例化型態持有者,事實上,當您沒有使用extends關鍵字限定型態持有者時,預設則是Object下的所有子類別都可以實例化型態持有者,即只寫<T>時就相當於<T extends Object>。[[BR]] 307 322 308 323 = 型態通配字元 = … … 312 327 * GenericFoo.java 313 328 {{{ 329 #!java 314 330 public class GenericFoo<T> { 315 331 private T foo; … … 339 355 現在您有這麼一個需求,您希望有一個參考名稱foo可以接受所有下面的實例(List、Map或List介面以及其實介面的相關類別,在J2SE 5.0中已經針對泛型功能作了改寫,在這邊仍請將之當作介面就好,這是為了簡化說明的考量):[[BR]][[BR]] 340 356 341 '''foo = new GenericFoo< ArrayList>();342 foo = new GenericFoo< LinkedList>();'''[[BR]][[BR]]357 '''foo = new GenericFoo<!ArrayList>(); 358 foo = new GenericFoo<!LinkedList>();'''[[BR]][[BR]] 343 359 344 360 簡單的說,實例化型態持有者時,它必須是實作List的類別或其子類別,要宣告這麼一個參考名稱,您可以使用 '?' 通配字元,並使用"extends"關鍵字限定型態持有者的型態,例如[[BR]] … … 346 362 347 363 '''GenericFoo<? extends List> foo = null; 348 foo = new GenericFoo< ArrayList>();364 foo = new GenericFoo<!ArrayList>(); 349 365 ..... 350 foo = new GenericFoo< LinkedList>();366 foo = new GenericFoo<!LinkedList>(); 351 367 ....'''[[BR]] 352 368 [[BR]] … … 357 373 [[BR]] 358 374 359 '''GenericFoo<? extends List> foo = new GenericFoo< HashMap>();'''[[BR]]375 '''GenericFoo<? extends List> foo = new GenericFoo<!HashMap>();'''[[BR]] 360 376 361 377 上面這段程式編譯器會回報以下的錯誤: 362 378 {{{ 379 #!java 363 380 incompatible types 364 381 found : GenericFoo<java.util.HashMap> … … 384 401 這麼一來,如果有粗心的程式設計人員傳入了您不想要的型態,例如GenericFoo<Boolean>型態的實例,則編譯器都會告訴它這是不可行的,在宣告名稱時如果指定了<?>而不使用"extends",則預設是允許Object及其下的子類,也就是所有的Java物件了,那為什麼不直接使用GenericFoo宣告就好了,何必要用GenericFoo<?>來宣告?使用通配字元有點要注意的是,透過使用通配字元宣告的名稱所參考的物件,您沒辦法再對它加入新的資訊,您只能取得它的資訊或是移除它的資訊,例如: 385 402 {{{ 403 #!java 386 404 GenericFoo<String> foo = new GenericFoo<String>(); 387 405 foo.setFoo("caterpillar"); … … 398 416 // immutableFoo.setFoo("良葛格"); 399 417 }}} 400 所以使用<?>或是<? extends SomeClass>的宣告方式,意味著您只能透過該名稱來取得所參考實例的資訊,或者是移除某些資訊,但不能增加它的資訊,因為只知道當中放置的是SomeClass的子類,但不確定是什麼類的實例,編譯器不讓您加入物件,理由是,如果可以加入物件的話,那麼您就得記得取回的物件實例是什麼形態,然後轉換為原來的型態方可進行操作,這就失去了使用泛型的意義。[[BR]]418 所以使用<?>或是<? extends !SomeClass>的宣告方式,意味著您只能透過該名稱來取得所參考實例的資訊,或者是移除某些資訊,但不能增加它的資訊,因為只知道當中放置的是!SomeClass的子類,但不確定是什麼類的實例,編譯器不讓您加入物件,理由是,如果可以加入物件的話,那麼您就得記得取回的物件實例是什麼形態,然後轉換為原來的型態方可進行操作,這就失去了使用泛型的意義。[[BR]] 401 419 [[BR]] 402 420 … … 409 427 [[BR]] 410 428 411 '''GenericFoo<? super StringBuilder> foo;'''[[BR]]412 413 414 如此,foo就只接受 StringBuilder 及其上層的父類型態之物件。[[BR]]429 '''GenericFoo<? super !StringBuilder> foo;'''[[BR]] 430 431 432 如此,foo就只接受 !StringBuilder 及其上層的父類型態之物件。[[BR]] 415 433 416 434 = 擴充泛型類別、實作泛型介面 = … … 419 437 * GenericFoo.java 420 438 {{{ 439 #!java 421 440 public class GenericFoo<T1, T2> { 422 441 private T1 foo1; … … 444 463 再來寫一個子類別擴充上面的父類別: 445 464 {{{ 465 #!java 446 466 * SubGenericFoo.java 447 467 … … 465 485 * IFoo.java 466 486 {{{ 487 #!java 467 488 public interface IFoo<T1, T2> { 468 489 public void setFoo1(T1 foo1); … … 477 498 * GenericFoo.java 478 499 {{{ 500 #!java 479 501 public class GenericFoo<T1, T2> implements IFoo<T1, T2> { 480 502 private T1 foo1;