日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

JAVA泛型编程笔记

發(fā)布時(shí)間:2024/4/14 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAVA泛型编程笔记 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

?? 1介紹

???? Java泛型編程是JDK1.5版本后引入的。泛型讓編程人員能夠使用類型抽象,通常用于集合里面。

???? 下面是一個(gè)不用泛型例子:

????

1 List myIntList=new LinkedList(); //1 2 myIntList.add(newInteger(0)); //2 3 Integer x=(Integer)myIntList.iterator().next(); //3

???

????? 注意第3行代碼,但這是讓人很不爽的一點(diǎn),因?yàn)槌绦騿T肯定知道自己存儲(chǔ)在List里面的對(duì)象類型是Integer,但是在返回列表中元素時(shí),還是必須強(qiáng)制轉(zhuǎn)換類型,這是為什么呢?原因在于,編譯器只能保證迭代器的next()方法返回的是Object類型的對(duì)象,為保證Integer變量的類型安全,所以必須強(qiáng)制轉(zhuǎn)換。

????? 這種轉(zhuǎn)換不僅顯得混亂,更可能導(dǎo)致類型轉(zhuǎn)換異常ClassCastException,運(yùn)行時(shí)異常往往讓人難以檢測(cè)到。保證列表中的元素為一個(gè)特定的數(shù)據(jù)類型,這樣就可以取消類型轉(zhuǎn)換,減少發(fā)生錯(cuò)誤的機(jī)會(huì),?這也是泛型設(shè)計(jì)的初衷。下面是一個(gè)使用了泛型的例子:

1 List<Integer> myIntList=newLinkedList<Integer>(); //1’ 2 myIntList.add(newInteger(0)); //2’ 3 Integerx=myIntList.iterator().next(); //3’

???在第1行代碼中指定List中存儲(chǔ)的對(duì)象類型為Integer,這樣在獲取列表中的對(duì)象時(shí),不必強(qiáng)制轉(zhuǎn)換類型了。

?

???

 2定義簡(jiǎn)單的泛型

???? 下面是一個(gè)引用自java.util包中的接口List和Iterator的定義,其中用到了泛型技術(shù)。

1 public interface List<E> { 2 <span style="white-space: pre;"> </span>void add(E x); 3 <span style="white-space: pre;"> </span>Iterator<E> iterator(); 4 } 5 public interface Iterator<E> { 6 <span style="white-space: pre;"> </span>E next(); 7 <span style="white-space: pre;"> </span>boolean hasNext(); 8 }

????? 這跟原生類型沒有什么區(qū)別,只是在接口后面加入了一個(gè)尖括號(hào),尖括號(hào)里面是一個(gè)類型參數(shù)(定義時(shí)就是一個(gè)格式化的類型參數(shù),在調(diào)用時(shí)會(huì)使用一個(gè)具體的類型來(lái)替換該類型)。

???????也許可以這樣認(rèn)為,List<Integer>表示List中的類型參數(shù)E會(huì)被替換成Integer。

1 public interface IntegerList { 2 <span style="white-space: pre;"> </span>void add(Integer x) 3 <span style="white-space: pre;"> </span>Iterator<Integer> iterator(); 4 }

????? 類型擦除指的是通過(guò)類型參數(shù)合并,將泛型類型實(shí)例關(guān)聯(lián)到同一份字節(jié)碼上。編譯器只為泛型類型生成一份字節(jié)碼,并將其實(shí)例關(guān)聯(lián)到這份字節(jié)碼上,因此泛型類型中的靜態(tài)變量是所有實(shí)例共享的。此外,需要注意的是,一個(gè)static方法,無(wú)法訪問泛型類的類型參數(shù),因?yàn)轭愡€沒有實(shí)例化,所以,若static方法需要使用泛型能力,必須使其成為泛型方法。類型擦除的關(guān)鍵在于從泛型類型中清除類型參數(shù)的相關(guān)信息,并且再必要的時(shí)候添加類型檢查和類型轉(zhuǎn)換的方法在使用泛型時(shí),任何具體的類型都被擦除,唯一知道的是你在使用一個(gè)對(duì)象。比如:List<String>List<Integer>在運(yùn)行事實(shí)上是相同的類型。他們都被擦除成他們的原生類型,即List因?yàn)榫幾g的時(shí)候會(huì)有類型擦除,所以不能通過(guò)同一個(gè)泛型類的實(shí)例來(lái)區(qū)分方法,如下面的例子編譯時(shí)會(huì)出錯(cuò),因?yàn)轭愋筒脸?#xff0c;兩個(gè)方法都是List類型的參數(shù),因此并不能根據(jù)泛型類的類型來(lái)區(qū)分方法。

1 /*會(huì)導(dǎo)致編譯時(shí)錯(cuò)誤*/ 2 public class Erasure{ 3 public void test(List<String> ls){ 4 System.out.println("Sting"); 5 } 6 public void test(List<Integer> li){ 7 System.out.println("Integer"); 8 } 9 }

??????那么這就有個(gè)問題了,既然在編譯的時(shí)候會(huì)在方法和類中擦除實(shí)際類型的信息,那么在返回對(duì)象時(shí)又是如何知道其具體類型的呢?如List<String>編譯后會(huì)擦除掉String信息,那么在運(yùn)行時(shí)通過(guò)迭代器返回List中的對(duì)象時(shí),又是如何知道List中存儲(chǔ)的是String類型對(duì)象呢?

???????擦除在方法體中移除了類型信息,所以在運(yùn)行時(shí)的問題就是邊界即對(duì)象進(jìn)入和離開方法的地點(diǎn),這正是編譯器在編譯期執(zhí)行類型檢查并插入轉(zhuǎn)型代碼的地點(diǎn)。泛型中的所有動(dòng)作都發(fā)生在邊界處:對(duì)傳遞進(jìn)來(lái)的值進(jìn)行額外的編譯期檢查,并插入對(duì)傳遞出去的值的轉(zhuǎn)型。

?

???

??? 3.泛型和子類型

????? 為了徹底理解泛型,這里看個(gè)例子:(Apple為Fruit的子類)

List<Apple> apples = new ArrayList<Apple>(); //1 List<Fruit> fruits = apples; //2

????? 第1行代碼顯然是對(duì)的,但是第2行是否對(duì)呢?我們知道Fruit fruit = new Apple(),這樣肯定是對(duì)的,即蘋果肯定是水果,但是第2行在編譯的時(shí)候會(huì)出錯(cuò)。這會(huì)讓人比較納悶的是一個(gè)蘋果是水果,為什么一箱蘋果就不是一箱水果了呢?可以這樣考慮,我們假定第2行代碼沒有問題,那么我們可以使用語(yǔ)句fruits.add(new Strawberry())(Strawberry為Fruit的子類)在fruits中加入草莓了,但是這樣的話,一個(gè)List中裝入了各種不同類型的子類水果,這顯然是不可以的,因?yàn)槲覀冊(cè)谌〕鯨ist中的水果對(duì)象時(shí),就分不清楚到底該轉(zhuǎn)型為蘋果還是草莓了。

????? 通常來(lái)說(shuō),如果Foo是Bar的子類型,G是一種帶泛型的類型,則G<Foo>不是G<Bar>的子類型。這也許是泛型學(xué)習(xí)里面最讓人容易混淆的一點(diǎn)。

?

??? 4.通配符

???? 4.1通配符?

???? 先看一個(gè)打印集合中所有元素的代碼。

1 //不使用泛型 2 void printCollection(Collection c) { 3 <span style="white-space: pre;"> </span>Iterator i=c.iterator(); 4 <span style="white-space: pre;"> </span>for (k=0;k < c.size();k++) { 5 <span style="white-space: pre;"> </span>System.out.println(i.next()); 6 <span style="white-space: pre;"> </span>} 7 } 1 //使用泛型 2 void printCollection(Collection<Object> c) { 3 for (Object e:c) { 4 System.out.println(e); 5 } 6 }

????? 很容易發(fā)現(xiàn),使用泛型的版本只能接受元素類型為Object類型的集合如ArrayList<Object>();如果是ArrayList<String>,則會(huì)編譯時(shí)出錯(cuò)。因?yàn)槲覀兦懊嬲f(shuō)過(guò),Collection<Object>并不是所有集合的超類。而老版本可以打印任何類型的集合,那么如何改造新版本以便它能接受所有類型的集合呢?這個(gè)問題可以通過(guò)使用通配符來(lái)解決。修改后的代碼如下所示:

//使用通配符?,表示可以接收任何元素類型的集合作為參數(shù) void printCollection(Collection<?> c) { <span style="white-space: pre;"> </span>for (Object e:c) { <span style="white-space: pre;"> </span>System.out.println(e); <span style="white-space: pre;"> </span>} }

????? 這里使用了通配符?指定可以使用任何類型的集合作為參數(shù)。讀取的元素使用了Object類型來(lái)表示,這是安全的,因?yàn)樗械念惗际荗bject的子類。這里就又出現(xiàn)了另外一個(gè)問題,如下代碼所示,如果試圖往使用通配符?的集合中加入對(duì)象,就會(huì)在編譯時(shí)出現(xiàn)錯(cuò)誤。需要注意的是,這里不管加入什么類型的對(duì)象都會(huì)出錯(cuò)。這是因?yàn)橥ㄅ浞?#xff1f;表示該集合存儲(chǔ)的元素類型未知,可以是任何類型。往集合中加入元素需要是一個(gè)未知元素類型的子類型,正因?yàn)樵摷洗鎯?chǔ)的元素類型未知,所以我們沒法向該集合中添加任何元素。唯一的例外是null,因?yàn)閚ull是所有類型的子類型,所以盡管元素類型不知道,但是null一定是它的子類型。

?

  • Collection<?>?c=new?ArrayList<String>();??
  • c.add(newObject());?//compile?time?error,不管加入什么對(duì)象都出錯(cuò),除了null外。??
  • c.add(null);?//OK?
  • ???? 另一方面,我們可以從List<?> lists中獲取對(duì)象,雖然不知道List中存儲(chǔ)的是什么類型,但是可以肯定的是存儲(chǔ)的類型一定是Object的子類型,所以可以用Object類型來(lái)獲取值。如for(Object obj: lists),這是合法的。

    ?? 4.2邊界通配符

    ?? 1)?extends通配符

    假定有一個(gè)畫圖的應(yīng)用,可以畫各種形狀的圖形,如矩形和圓形等。為了在程序里面表示,定義如下的類層次:

  • public?abstract?class?Shape?{??
  • <span?style="white-space:?pre;">????</span>public?abstract?void?draw(Canvas?c);??
  • }??
  • ??
  • public?class?Circle?extends?Shape?{??
  • <span?style="white-space:?pre;">????</span>private?int?x,y,radius;??
  • <span?style="white-space:?pre;">????</span>public?void?draw(Canvas?c)?{?...?}??
  • }??
  • ??
  • public?class?Rectangle?extends?Shape??
  • <span?style="white-space:?pre;">????</span>private?int?x,y,width,height;??
  • <span?style="white-space:?pre;">????</span>public?void?draw(Canvasc)?{?...?}??
  • }??
  • ???? 為了畫出集合中所有的形狀,我們可以定義一個(gè)函數(shù),該函數(shù)接受帶有泛型的集合類對(duì)象作為參數(shù)。但是不幸的是,我們只能接收元素類型為Shape的List對(duì)象,而不能接收類型為L(zhǎng)ist<Cycle>的對(duì)象,這在前面已經(jīng)說(shuō)過(guò)。為了解決這個(gè)問題,所以有了邊界通配符的概念。這里可以采用public void drawAll(List<? extends Shape>shapes)來(lái)滿足條件,這樣就可以接收元素類型為Shape子類型的列表作為參數(shù)了。

  • //原始版本??
  • public?void?drawAll(List<Shape>?shapes)?{??
  • <span?style="white-space:?pre;">????</span>for?(Shapes:shapes)?{??
  • <span?style="white-space:?pre;">????????</span>s.draw(this);??
  • <span?style="white-space:?pre;">????</span>}??
  • }??
  • ?

  • //使用邊界通配符的版本??
  • public?void?drawAll(List<?exends?Shape>?shapes)?{??
  • <span?style="white-space:?pre;">????</span>for?(Shapes:shapes)?{??
  • <span?style="white-space:?pre;">????????</span>s.draw(this);??
  • <span?style="white-space:?pre;">????</span>}??
  • }??
  • ?

    ?????? 這里就又有個(gè)問題要注意了,如果我們希望在List<?exends Shape> shapes中加入一個(gè)矩形對(duì)象,如下所示:

    shapes.add(0, new Rectangle()); //compile-time error

    那么這時(shí)會(huì)出現(xiàn)一個(gè)編譯時(shí)錯(cuò)誤,原因在于:我們只知道shapes中的元素時(shí)Shape類型的子類型,具體是什么子類型我們并不清楚,所以我們不能往shapes中加入任何類型的對(duì)象。不過(guò)我們?cè)谌〕銎渲袑?duì)象時(shí),可以使用Shape類型來(lái)取值,因?yàn)殡m然我們不知道列表中的元素類型具體是什么類型,但是我們肯定的是它一定是Shape類的子類型。

    ?

    ?????? 2)?super通配符

    ???????這里還有一種邊界通配符為?super。比如下面的代碼:

  • List<Shape>?shapes?=?new?ArrayList<Shape>();??
  • List<??super?Cicle>?cicleSupers?=?shapes;??
  • cicleSupers.add(new?Cicle());?//OK,?subclass?of?Cicle?also?OK??
  • cicleSupers.add(new?Shape());?//ERROR??
  • 這表示cicleSupers列表存儲(chǔ)的元素為Cicle的超類,因此我們可以往其中加入Cicle對(duì)象或者Cicle的子類對(duì)象,但是不能加入Shape對(duì)象。這里的原因在于列表cicleSupers存儲(chǔ)的元素類型為Cicle的超類,但是具體是Cicle的什么超類并不清楚。但是我們可以確定的是只要是Cicle或者Circle的子類,則一定是與該元素類別兼容。

    ?

    3)邊界通配符總結(jié)

    <!--[if !supportLists]-->l?????????<!--[endif]-->如果你想從一個(gè)數(shù)據(jù)類型里獲取數(shù)據(jù),使用?? extends?通配符

    <!--[if !supportLists]-->l?????????<!--[endif]-->如果你想把對(duì)象寫入一個(gè)數(shù)據(jù)結(jié)構(gòu)里,使用?? super?通配符

    <!--[if !supportLists]-->l?????????<!--[endif]-->如果你既想存,又想取,那就別用通配符。

    ?

    5.泛型方法

    考慮實(shí)現(xiàn)一個(gè)方法,該方法拷貝一個(gè)數(shù)組中的所有對(duì)象到集合中。下面是初始的版本:

  • static?void?fromArrayToCollection(Object[]a,?Collection<?>?c)?{??
  • <span?style="white-space:?pre;">????</span>for?(Object?o:a)?{??
  • <span?style="white-space:?pre;">????????</span>c.add(o);?//compile?time?error??
  • <span?style="white-space:?pre;">????</span>}??
  • }??
  • 可以看到顯然會(huì)出現(xiàn)編譯錯(cuò)誤,原因在之前有講過(guò),因?yàn)榧蟘中的類型未知,所以不能往其中加入任何的對(duì)象(當(dāng)然,null除外)。解決該問題的一種比較好的辦法是使用泛型方法,如下所示:

  • static?<T>?void?fromArrayToCollection(T[]?a,?Collection<T>c){??
  • <span?style="white-space:?pre;">????</span>for(T?o?:?a)?{??
  • <span?style="white-space:?pre;">????????</span>c.add(o);//?correct??
  • <span?style="white-space:?pre;">????</span>}??
  • }??
  • 注意泛型方法的格式,類型參數(shù)<T>需要放在函數(shù)返回值之前。然后在參數(shù)和返回值中就可以使用泛型參數(shù)了。具體一些調(diào)用方法的實(shí)例如下:

  • Object[]?oa?=?new?Object[100];??
  • Collection<Object>co?=?new?ArrayList<Object>();??
  • fromArrayToCollection(oa,?co);//?T?inferred?to?be?Object??
  • String[]?sa?=?new?String[100];??
  • Collection<String>cs?=?new?ArrayList<String>();??
  • fromArrayToCollection(sa,?cs);//?T?inferred?to?be?String??
  • fromArrayToCollection(sa,?co);//?T?inferred?to?be?Object??
  • Integer[]?ia?=?new?Integer[100];??
  • Float[]?fa?=?new?Float[100];??
  • Number[]?na?=?new?Number[100];??
  • Collection<Number>cn?=?new?ArrayList<Number>();??
  • fromArrayToCollection(ia,?cn);//?T?inferred?to?be?Number??
  • fromArrayToCollection(fa,?cn);//?T?inferred?to?be?Number??
  • fromArrayToCollection(na,?cn);//?T?inferred?to?be?Number??
  • fromArrayToCollection(na,?co);//?T?inferred?to?be?Object??
  • fromArrayToCollection(na,?cs);//?compile-time?error?
  • 注意到我們調(diào)用方法時(shí)并不需要傳遞類型參數(shù),系統(tǒng)會(huì)自動(dòng)判斷類型參數(shù)并調(diào)用合適的方法。當(dāng)然在某些情況下需要指定傳遞類型參數(shù),比如當(dāng)存在與泛型方法相同的方法的時(shí)候(方法參數(shù)類型不一致),如下面的一個(gè)例子:

  • public??<T>?void?go(T?t)?{??
  • ????System.out.println("generic?function");??
  • }??
  • public?void?go(String?str)?{??
  • ????System.out.println("normal?function");??
  • }??
  • public?static?void?main(String[]?args)?{??
  • ????????FuncGenric?fg?=?new?FuncGenric();??
  • ????????fg.go("haha");//打印normal?function??
  • ????????fg.<String>go("haha");//打印generic?function??
  • ????????fg.go(new?Object());//打印generic?function??
  • ????????fg.<Object>go(new?Object());//打印generic?function??
  • }??
  • 如例子中所示,當(dāng)不指定類型參數(shù)時(shí),調(diào)用的是普通的方法,如果指定了類型參數(shù),則調(diào)用泛型方法。可以這樣理解,因?yàn)榉盒头椒ň幾g后類型擦除,如果不指定類型參數(shù),則泛型方法此時(shí)相當(dāng)于是public void go(Object t)。而普通的方法接收參數(shù)為String類型,因此以String類型的實(shí)參調(diào)用函數(shù),肯定會(huì)調(diào)用形參為String的普通方法了。如果是以O(shè)bject類型的實(shí)參調(diào)用函數(shù),則會(huì)調(diào)用泛型方法。

    6.其他需要注意的小點(diǎn)

    1)方法重載

    在JAVA里面方法重載是不能通過(guò)返回值類型來(lái)區(qū)分的,比如代碼一中一個(gè)類中定義兩個(gè)如下的方法是不容許的。但是當(dāng)參數(shù)為泛型類型時(shí),卻是可以的。如下面代碼二中所示,雖然形參經(jīng)過(guò)類型擦除后都為L(zhǎng)ist類型,但是返回類型不同,這是可以的。

    ?

  • /*代碼一:編譯時(shí)錯(cuò)誤*/???
  • public?class?Erasure{??
  • ????????????public?void?test(int?i){??
  • ????????????????System.out.println("Sting");??
  • ????????????}??
  • ????????????public?int?test(int?i){??
  • ????????????????System.out.println("Integer");??
  • ????????????}??
  • ??}??
  • /*代碼二:正確?*/??
  • ?public?class?Erasure{??
  • ????????????public?void?test(List<String>?ls){??
  • ????????????????System.out.println("Sting");??
  • ????????????}??
  • ????????????public?int?test(List<Integer>?li){??
  • ????????????????System.out.println("Integer");??
  • ????????????}??
  • ??}??
  • 2)泛型類型是被所有調(diào)用共享的

    ???????所有泛型類的實(shí)例都共享同一個(gè)運(yùn)行時(shí)類,類型參數(shù)信息會(huì)在編譯時(shí)被擦除。因此考慮如下代碼,雖然ArrayList<String>和ArrayList<Integer>類型參數(shù)不同,但是他們都共享ArrayList類,所以結(jié)果會(huì)是true。

  • List<String>l1?=?new?ArrayList<String>();??
  • List<Integer>l2?=?new?ArrayList<Integer>();??
  • System.out.println(l1.getClass()?==?l2.getClass());?//True??
  • 3)instanceof

    不能對(duì)確切的泛型類型使用instanceOf操作。如下面的操作是非法的,編譯時(shí)會(huì)出錯(cuò)。

  • Collection?cs?=?new?ArrayList<String>();??
  • if?(cs?instanceof?Collection<String>){…}//?compile?error.如果改成instanceof?Collection<?>則不會(huì)出錯(cuò)。??
  • 4)泛型數(shù)組問題

    不能創(chuàng)建一個(gè)確切泛型類型的數(shù)組。如下面代碼會(huì)出錯(cuò)。

    List<String>[] lsa = new ArrayList<String>[10];?//compile error.

    ???????因?yàn)槿绻梢赃@樣,那么考慮如下代碼,會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤。

  • List<String>[]?lsa?=?new?ArrayList<String>[10];?//?實(shí)際上并不允許這樣創(chuàng)建數(shù)組??
  • Object?o?=?lsa;??
  • Object[]?oa?=?(Object[])?o;??
  • List<Integer>li?=?new?ArrayList<Integer>();??
  • li.add(new?Integer(3));??
  • oa[1]?=?li;//?unsound,?but?passes?run?time?store?check??
  • String?s?=?lsa[1].get(0);?//run-time?error?-?ClassCastException?
  • 因此只能創(chuàng)建帶通配符的泛型數(shù)組,如下面例子所示,這回可以通過(guò)編譯,但是在倒數(shù)第二行代碼中必須顯式的轉(zhuǎn)型才行,即便如此,最后還是會(huì)拋出類型轉(zhuǎn)換異常,因?yàn)榇鎯?chǔ)在lsa中的是List<Integer>類型的對(duì)象,而不是List<String>類型。最后一行代碼是正確的,類型匹配,不會(huì)拋出異常。

  • List<?>[]?lsa?=?new?List<?>[10];?//?ok,?array?of?unbounded?wildcard?type??
  • Object?o?=?lsa;??
  • Object[]?oa?=?(Object[])?o;??
  • List<Integer>li?=?new?ArrayList<Integer>();??
  • li.add(new?Integer(3));??
  • oa[1]?=?li;?//correct??
  • String?s?=?(String)?lsa[1].get(0);//?run?time?error,?but?cast?is?explicit??
  • Integer?it?=?(Integer)lsa[1].get(0);?//?OK?
  • ?

    轉(zhuǎn)載于:https://www.cnblogs.com/UniqueColor/p/5712358.html

    總結(jié)

    以上是生活随笔為你收集整理的JAVA泛型编程笔记的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。