日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

java通配符

 精髓_感知力 2016-03-08

來源:http://blog.csdn.net/flfna/article/details/6576394

通配符

在本文的前面的部分里已經(jīng)說過了泛型類型的子類型的不相關(guān)性。但有些時候,我們希望能夠像使用普通類型那樣使用泛型類型:

◆ 向上造型一個泛型對象的引用

◆ 向下造型一個泛型對象的引用

向上造型一個泛型對象的引用

例如,假設(shè)我們有很多箱子,每個箱子里都裝有不同的水果,我們需要找到一種方法能夠通用的處理任何一箱水果。更通俗的說法,A是B的子類型,我們需要找到一種方法能夠?qū)<A>類型的實(shí)例賦給一個C<B>類型的聲明。

為了完成這種操作,我們需要使用帶有通配符的擴(kuò)展聲明,就像下面的例子里那樣:

  1. List<Apple> apples = new ArrayList<Apple>();
  2. List<? extends Fruit> fruits = apples;

“? extends”是泛型類型的子類型相關(guān)性成為現(xiàn)實(shí):Apple是Fruit的子類型,List<Apple> 是 List<? extends Fruit> 的子類型。

向下造型一個泛型對象的引用

現(xiàn)在我來介紹另外一種通配符:? super。如果類型B是類型A的超類型(父類型),那么C<B> 是 C<? super A> 的子類型:

  1. List<Fruit> fruits = new ArrayList<Fruit>();
  2. List<? super Apple> = fruits;

為什么使用通配符標(biāo)記能行得通?

原理現(xiàn)在已經(jīng)很明白:我們?nèi)绾卫眠@種新的語法結(jié)構(gòu)?

extends

讓我們重新看看這第二部分使用的一個例子,其中談到了Java數(shù)組的子類型相關(guān)性:

  1. Apple[] apples = new Apple[ 1 ];
  2. Fruit[] fruits = apples;
  3. fruits[ 0 ] = new Strawberry();

就像我們看到的,當(dāng)你往一個聲明為Fruit數(shù)組的Apple對象數(shù)組里加入Strawberry對象后,代碼可以編譯,但在運(yùn)行時拋出異常。

現(xiàn)在我們可以使用通配符把相關(guān)的代碼轉(zhuǎn)換成泛型:因?yàn)锳pple是Fruit的一個子類,我們使用? extends 通配符,這樣就能將一個List<Apple>對象的定義賦到一個List<? extends Fruit>的聲明上:

  1. List<Apple> apples = new ArrayList<Apple>();
  2. List<? extends Fruit> fruits = apples;
  3. fruits.add( new Strawberry());

這次,代碼就編譯不過去了!Java編譯器會阻止你往一個Fruit list里加入strawberry。在編譯時我們就能檢測到錯誤,在運(yùn)行時就不需要進(jìn)行檢查來確保往列表里加入不兼容的類型了。即使你往list里加入Fruit對象也不行:

  1. fruits.add( new Fruit());

你沒有辦法做到這些。事實(shí)上你不能夠往一個使用了? extends的數(shù)據(jù)結(jié)構(gòu)里寫入任何的值。

原因非常的簡單,你可以這樣想:這個? extends T 通配符告訴編譯器我們在處理一個類型T的子類型,但我們不知道這個子類型究竟是什么。因?yàn)闆]法確定,為了保證類型安全,我們就不允許往里面加入任何這種類型的數(shù)據(jù)。另一方面,因?yàn)槲覀冎溃徽撍鞘裁搭愋?,它總是類型T的子類型,當(dāng)我們在讀取數(shù)據(jù)時,能確保得到的數(shù)據(jù)是一個T類型的實(shí)例:

  1. Fruit get = fruits.get( 0 );

super

使用 ? super 通配符一般是什么情況?讓我們先看看這個:

  1. List<Fruit> fruits = new ArrayList<Fruit>();
  2. List<? super Apple> = fruits;

我們看到fruits指向的是一個裝有Apple的某種超類(supertype)的List。同樣的,我們不知道究竟是什么超類,但我們知道 Apple和任何Apple的子類都跟它的類型兼容。既然這個未知的類型即是Apple,也是GreenApple的超類,我們就可以寫入:

  1. fruits.add( new Apple());
  2. fruits.add( new GreenApple());

如果我們想往里面加入Apple的超類,編譯器就會警告你:

  1. fruits.add( new Fruit());
  2. fruits.add( new Object());

因?yàn)槲覀儾恢浪窃鯓拥某?,所有這樣的實(shí)例就不允許加入。

從這種形式的類型里獲取數(shù)據(jù)又是怎么樣的呢?結(jié)果表明,你只能取出Object實(shí)例:因?yàn)槲覀儾恢莱惥烤故鞘裁?,編譯器唯一能保證的只是它是個Object,因?yàn)镺bject是任何Java類型的超類。

存取原則和PECS法則

總結(jié) ? extends 和 the ? super 通配符的特征,我們可以得出以下結(jié)論:

◆ 如果你想從一個數(shù)據(jù)類型里獲取數(shù)據(jù),使用 ? extends 通配符

◆ 如果你想把對象寫入一個數(shù)據(jù)結(jié)構(gòu)里,使用 ? super 通配符

◆ 如果你既想存,又想取,那就別用通配符。

這就是Maurice Naftalin在他的《Java Generics and Collections》這本書中所說的存取原則,以及Joshua Bloch在他的《Effective Java》這本書中所說的PECS法則。

Bloch提醒說,這PECS是指”Producer Extends, Consumer Super”,這個更容易記憶和運(yùn)用。

------------

上面的接最底下的:

The Java Tutorial

Java Generics and Collections, by Maurice Naftalin and Philip Wadler

Effective Java中文版(第2版), by Joshua Bloch.

盡管有這么多豐富的資料,有時我感覺,有很多的程序員仍然不太明白Java泛型的功用和意義。這就是為什么我想使用一種最簡單的形式來總結(jié)一下程序員需要知道的關(guān)于Java泛型的最基本的知識。

Java泛型由來的動機(jī)

理解Java泛型最簡單的方法是把它看成一種便捷語法,能節(jié)省你某些Java類型轉(zhuǎn)換(casting)上的操作:

  1. List<Apple> box = ...;
  2. Apple apple = box.get( 0 );

上面的代碼自身已表達(dá)的很清楚:box是一個裝有Apple對象的List。get方法返回一個Apple對象實(shí)例,這個過程不需要進(jìn)行類型轉(zhuǎn)換。沒有泛型,上面的代碼需要寫成這樣:

  1. List box = ...;
  2. Apple apple = (Apple) box.get( 0 );

很明顯,泛型的主要好處就是讓編譯器保留參數(shù)的類型信息,執(zhí)行類型檢查,執(zhí)行類型轉(zhuǎn)換操作:編譯器保證了這些類型轉(zhuǎn)換的絕對無誤。

相對于依賴程序員來記住對象類型、執(zhí)行類型轉(zhuǎn)換——這會導(dǎo)致程序運(yùn)行時的失敗,很難調(diào)試和解決,而編譯器能夠幫助程序員在編譯時強(qiáng)制進(jìn)行大量的類型檢查,發(fā)現(xiàn)其中的錯誤。

泛型的構(gòu)成

由泛型的構(gòu)成引出了一個類型變量的概念。根據(jù)Java語言規(guī)范,類型變量是一種沒有限制的標(biāo)志符,產(chǎn)生于以下幾種情況:

◆ 泛型類聲明

◆ 泛型接口聲明

◆ 泛型方法聲明

◆ 泛型構(gòu)造器(constructor)聲明

泛型類和接口

如果一個類或接口上有一個或多個類型變量,那它就是泛型。類型變量由尖括號界定,放在類或接口名的后面:

  1. public interface List<T> extends Collection<T> {
  2. ...
  3. }

簡單的說,類型變量扮演的角色就如同一個參數(shù),它提供給編譯器用來類型檢查的信息。

Java類庫里的很多類,例如整個Collection框架都做了泛型化的修改。例如,我們在上面的第一段代碼里用到的List接口就是一個泛型類。在那段代碼里,box是一個List對象,它是一個帶有一個Apple類型變量的List接口的類實(shí)現(xiàn)的實(shí)例。編譯器使用這個類型變量參數(shù)在get方法被調(diào)用、返回一個Apple對象時自動對其進(jìn)行類型轉(zhuǎn)換。

實(shí)際上,這新出現(xiàn)的泛型標(biāo)記,或者說這個List接口里的get方法是這樣的:

  1. T get( int index);

get方法實(shí)際返回的是一個類型為T的對象,T是在List<T>聲明中的類型變量。

泛型方法和構(gòu)造器(Constructor)

非常的相似,如果方法和構(gòu)造器上聲明了一個或多個類型變量,它們也可以泛型化。

  1. public static <t> T getFirst(List<T> list)

這個方法將會接受一個List<T>類型的參數(shù),返回一個T類型的對象。

例子

你既可以使用Java類庫里提供的泛型類,也可以使用自己的泛型類。

類型安全的寫入數(shù)據(jù)…

下面的這段代碼是個例子,我們創(chuàng)建了一個List<String>實(shí)例,然后裝入一些數(shù)據(jù):

  1. List<String> str = new ArrayList<String>();
  2. str.add( "Hello " );
  3. str.add( "World." );

如果我們試圖在List<String>裝入另外一種對象,編譯器就會提示錯誤:

  1. str.add( 1 ); // 不能編譯

類型安全的讀取數(shù)據(jù)…

當(dāng)我們在使用List<String>對象時,它總能保證我們得到的是一個String對象:

  1. String myString = str.get( 0 );

遍歷

類庫中的很多類,諸如Iterator<T>,功能都有所增強(qiáng),被泛型化。List<T>接口里的iterator()方法現(xiàn)在返回的是Iterator<T>,由它的T next()方法返回的對象不需要再進(jìn)行類型轉(zhuǎn)換,你直接得到正確的類型。

  1. for (Iterator<String> iter = str.iterator(); iter.hasNext();) {
  2. String s = iter.next();
  3. System.out.print(s);
  4. }

使用foreach

“for each”語法同樣受益于泛型。前面的代碼可以寫出這樣:

  1. for (String s: str) {
  2. System.out.print(s);
  3. }

這樣既容易閱讀也容易維護(hù)。

自動封裝(Autoboxing)和自動拆封(Autounboxing)

在使用Java泛型時,autoboxing/autounboxing這兩個特征會被自動的用到,就像下面的這段代碼:

  1. List<Integer> ints = new ArrayList<Integer>();
  2. ints.add( 0 );
  3. ints.add( 1 );
  4. int sum = 0 ;
  5. for ( int i : ints) {
  6. sum += i;
  7. }

然而,你要明白的一點(diǎn)是,封裝和解封會帶來性能上的損失,所有,通用要謹(jǐn)慎的使用。

子類型

在Java中,跟其它具有面向?qū)ο箢愋偷恼Z言一樣,類型的層級可以被設(shè)計成這樣:

Java泛型簡明教程

在Java中,類型T的子類型既可以是類型T的一個擴(kuò)展,也可以是類型T的一個直接或非直接實(shí)現(xiàn)(如果T是一個接口的話)。因?yàn)椤俺蔀槟愁愋偷淖宇愋汀笔且粋€具有傳遞性質(zhì)的關(guān)系,如果類型A是B的一個子類型,B是C的子類型,那么A也是C的子類型。在上面的圖中:

◆ FujiApple(富士蘋果)是Apple的子類型

◆ Apple是Fruit(水果)的子類型

◆ FujiApple(富士蘋果)是Fruit(水果)的子類型

所有Java類型都是Object類型的子類型。

B類型的任何一個子類型A都可以被賦給一個類型B的聲明:

  1. Apple a = ...;
  2. Fruit f = a;

泛型類型的子類型

如果一個Apple對象的實(shí)例可以被賦給一個Fruit對象的聲明,就像上面看到的,那么,List<Apple> 和 a List<Fruit>之間又是個什么關(guān)系呢?更通用些,如果類型A是類型B的子類型,那C<A> 和 C<B>之間是什么關(guān)系?

答案會出乎你的意料:沒有任何關(guān)系。用更通俗的話,泛型類型跟其是否子類型沒有任何關(guān)系。

這意味著下面的這段代碼是無效的:

  1. List<Apple> apples = ...;
  2. List<Fruit> fruits = apples;

下面的同樣也不允許:

  1. List < Apple > apples;
  2. List < Fruit > fruits = ...;
  3. apples = fruits ;

為什么?一個蘋果是一個水果,為什么一箱蘋果不能是一箱水果?

在某些事情上,這種說法可以成立,但在類型(類)封裝的狀態(tài)和操作上不成立。如果把一箱蘋果當(dāng)成一箱水果會發(fā)生什么情況?

  1. List<Apple> apples = ...;
  2. List<Fruit> fruits = apples;
  3. fruits.add( new Strawberry());

如果可以這樣的話,我們就可以在list里裝入各種不同的水果子類型,這是絕對不允許的。

另外一種方式會讓你有更直觀的理解:一箱水果不是一箱蘋果,因?yàn)樗锌赡苁且幌淞硗庖环N水果,比如草莓(子類型)。

這是一個需要注意的問題嗎?

應(yīng)該不是個大問題。而程序員對此感到意外的最大原因是數(shù)組和泛型類型上用法的不一致。對于泛型類型,它們和類型的子類型之間是沒什么關(guān)系的。而對于數(shù)組,它們和子類型是相關(guān)的:如果類型A是類型B的子類型,那么A[]是B[]的子類型:

  1. Apple[] apples = ...;
  2. Fruit[] fruits = apples;

可是稍等一下!如果我們把前面的那個議論中暴露出的問題放在這里,我們?nèi)匀荒軌蛟谝粋€apple類型的數(shù)組中加入strawberrie(草莓)對象:

  1. Apple[] apples = new Apple[ 1 ];
  2. Fruit[] fruits = apples;
  3. fruits[ 0 ] = new Strawberry();

這樣寫真的可以編譯,但是在運(yùn)行時拋出ArrayStoreException異常。因?yàn)閿?shù)組的這特點(diǎn),在存儲數(shù)據(jù)的操作上,Java運(yùn)行時需要檢查類型的兼容性。這種檢查,很顯然,會帶來一定的性能問題,你需要明白這一點(diǎn)。

重申一下,泛型使用起來更安全,能“糾正”Java數(shù)組中這種類型上的缺陷。

現(xiàn)在估計你會感到很奇怪,為什么在數(shù)組上會有這種類型和子類型的關(guān)系,我來給你一個《Java Generics and Collections》這本書上給出的答案:如果它們不相關(guān),你就沒有辦法把一個未知類型的對象數(shù)組傳入一個方法里(不經(jīng)過每次都封裝成 Object[]),就像下面的:

  1. void sort(Object[] o);

泛型出現(xiàn)后,數(shù)組的這個個性已經(jīng)不再有使用上的必要了(下面一部分我們會談到這個),實(shí)際上是應(yīng)該避免使用。


    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多