java基础系列:集合基础(1)
數(shù)組和第一類(lèi)對(duì)象
無(wú)論使用的數(shù)組屬于什么類(lèi)型,數(shù)組標(biāo)識(shí)符實(shí)際都是指向真實(shí)對(duì)象的一個(gè)句柄。那些對(duì)象本身是在內(nèi)存
“堆”里創(chuàng)建的。堆對(duì)象既可“隱式”創(chuàng)建(即默認(rèn)產(chǎn)生),亦可“顯式”創(chuàng)建(即明確指定,用一個(gè) new
表達(dá)式)。堆對(duì)象的一部分(實(shí)際是我們能訪(fǎng)問(wèn)的唯一字段或方法)是只讀的length(長(zhǎng)度)成員,它告訴
我們那個(gè)數(shù)組對(duì)象里最多能容納多少元素。對(duì)于數(shù)組對(duì)象,“ []”語(yǔ)法是我們能采用的唯一另類(lèi)訪(fǎng)問(wèn)方法。
對(duì)象數(shù)組和基本數(shù)據(jù)類(lèi)型數(shù)組在使用方法上幾乎是完全一致的。唯一的差別在于對(duì)象數(shù)組容納的是句柄,而基本數(shù)據(jù)類(lèi)型數(shù)組容納的是具體的數(shù)值
public class ArraySize {public static void main(String[] args) {// Arrays of objects:Weeble[] a; // Null handleWeeble[] b = new Weeble[5]; // Null handlesWeeble[] c = new Weeble[4];for (int i = 0; i < c.length; i++)c[i] = new Weeble();Weeble[] d = { new Weeble(), new Weeble(), new Weeble() };// Compile error: variable a not initialized:// !System.out.println("a.length=" + a.length);System.out.println("b.length = " + b.length);// The handles inside the array are// automatically initialized to null:for (int i = 0; i < b.length; i++)System.out.println("b[" + i + "]=" + b[i]);System.out.println("c.length = " + c.length);System.out.println("d.length = " + d.length);a = d;System.out.println("a.length = " + a.length);// Java 1.1 initialization syntax:a = new Weeble[] { new Weeble(), new Weeble() };System.out.println("a.length = " + a.length);// Arrays of primitives:int[] e; // Null handleint[] f = new int[5];int[] g = new int[4];for (int i = 0; i < g.length; i++)g[i] = i * i;int[] h = { 11, 47, 93 };// Compile error: variable e not initialized:// !System.out.println("e.length=" + e.length);System.out.println("f.length = " + f.length);// The primitives inside the array are// automatically initialized to zero:for (int i = 0; i < f.length; i++)System.out.println("f[" + i + "]=" + f[i]);System.out.println("g.length = " + g.length);System.out.println("h.length = " + h.length);e = h;System.out.println("e.length = " + e.length);// Java 1.1 initialization syntax:e = new int[] { 1, 2 };System.out.println("e.length = " + e.length);} }輸出如下:
b.length = 5
b[0]=null
b[1]=null
b[2]=null
b[3]=null
b[4]=null
c.length = 4
d.length = 3
a.length = 3
a.length = 2
f.length = 5
f[0]=0
f[1]=0
f[2]=0
f[3]=0
f[4]=0
g.length = 4
h.length = 3
e.length = 3
e.length = 2
其中,數(shù)組 a 只是初始化成一個(gè) null 句柄。此時(shí),編譯器會(huì)禁止我們對(duì)這個(gè)句柄作任何實(shí)際操作,除非已正
確地初始化了它。數(shù)組 b 被初始化成指向由 Weeble 句柄構(gòu)成的一個(gè)數(shù)組,但那個(gè)數(shù)組里實(shí)際并未放置任何
Weeble 對(duì)象。然而,我們?nèi)匀豢梢圆樵?xún)那個(gè)數(shù)組的大小,因?yàn)?b 指向的是一個(gè)合法對(duì)象。
換言之,我們只知道數(shù)組對(duì)象的大小或容量,不知其實(shí)際容納了多少個(gè)元素。
盡管如此,由于數(shù)組對(duì)象在創(chuàng)建之初會(huì)自動(dòng)初始化成 null,所以可檢查它是否為 null,判斷一個(gè)特定的數(shù)組“空位”是否容納一個(gè)對(duì)象。類(lèi)似地,由基本數(shù)據(jù)類(lèi)型構(gòu)成的數(shù)組會(huì)自動(dòng)初始化成零(針對(duì)數(shù)值類(lèi)型)、 null(字符類(lèi)型)或者false(布爾類(lèi)型)
數(shù)組 c 顯示出我們首先創(chuàng)建一個(gè)數(shù)組對(duì)象,再將 Weeble 對(duì)象賦給那個(gè)數(shù)組的所有“空位”。數(shù)組 d 揭示出
“集合初始化”語(yǔ)法,從而創(chuàng)建數(shù)組對(duì)象(用 new 命令明確進(jìn)行,類(lèi)似于數(shù)組 c),然后用 Weeble 對(duì)象進(jìn)行
初始化,全部工作在一條語(yǔ)句里完成。
下面這個(gè)表達(dá)式:
向我們展示了如何取得同一個(gè)數(shù)組對(duì)象連接的句柄,然后將其賦給另一個(gè)數(shù)組對(duì)象,向我們展示了如何取得同一個(gè)數(shù)組對(duì)象連接的句柄,然后將其賦給另一個(gè)數(shù)組對(duì)象
1.基本數(shù)據(jù)類(lèi)型集合
集合類(lèi)只能容納對(duì)象句柄。但對(duì)一個(gè)數(shù)組,卻既可令其直接容納基本類(lèi)型的數(shù)據(jù),亦可容納指向?qū)ο蟮木?
柄。利用象 Integer、 Double 之類(lèi)的“ 封裝器”類(lèi),可將基本數(shù)據(jù)類(lèi)型的值置入一個(gè)集合里。
無(wú)論將基本類(lèi)型的數(shù)據(jù)置入數(shù)組,還是將其封裝進(jìn)入位于集合的一個(gè)類(lèi)內(nèi),都涉及到執(zhí)行效率的問(wèn)題。顯
然,若能創(chuàng)建和訪(fǎng)問(wèn)一個(gè)基本數(shù)據(jù)類(lèi)型數(shù)組,那么比起訪(fǎng)問(wèn)一個(gè)封裝數(shù)據(jù)的集合,前者的效率會(huì)高出許多。
數(shù)組的返回
假定我們現(xiàn)在想寫(xiě)一個(gè)方法,同時(shí)不希望它僅僅返回一樣?xùn)|西,而是想返回一系列東西。此時(shí),象C 和 C++這樣的語(yǔ)言會(huì)使問(wèn)題復(fù)雜化,因?yàn)槲覀儾荒芊祷匾粋€(gè)數(shù)組,只能返回指向數(shù)組的一個(gè)指針。這樣就非常麻煩,因?yàn)楹茈y控制數(shù)組的“存在時(shí)間”,它很容易造成內(nèi)存“漏洞”的出現(xiàn)。
Java 采用的是類(lèi)似的方法,但我們能“返回一個(gè)數(shù)組”。當(dāng)然,此時(shí)返回的實(shí)際仍是指向數(shù)組的指針。但在Java 里,我們永遠(yuǎn)不必?fù)?dān)心那個(gè)數(shù)組的是否可用—— 只要需要,它就會(huì)自動(dòng)存在。而且垃圾收集器會(huì)在我們完成后自動(dòng)將其清除
public class IceCream {static String[] flav = { "Chocolate", "Strawberry", "Vanilla Fudge Swirl","Mint Chip", "Mocha Almond Fudge", "Rum Raisin", "Praline Cream","Mud Pie" };static String[] flavorSet(int n) {// Force it to be positive & within bounds:n = Math.abs(n) % (flav.length + 1);String[] results = new String[n];int[] picks = new int[n];for(int i = 0; i < picks.length; i++)picks[i] = -1;for(int i = 0; i < picks.length; i++) {retry:while(true) {int t =(int)(Math.random() * flav.length);for(int j = 0; j < i; j++)213if(picks[j] == t) continue retry;picks[i] = t;results[i] = flav[t];break;}}return results;}public static void main(String[] args) {for (int i = 0; i < 20; i++) {System.out.println("flavorSet(" + i + ") = ");String[] fl = flavorSet(flav.length);for (int j = 0; j < fl.length; j++)System.out.println("\t" + fl[j]);}} }flavorSet()方法創(chuàng)建了一個(gè)名為 results 的 String 數(shù)組。該數(shù)組的大小為 n—— 具體數(shù)值取決于我們傳遞給方法的自變量。隨后,它從數(shù)組 flav 里隨機(jī)挑選一些“香料”( Flavor),并將它們置入 results 里,并最終返回 results。返回?cái)?shù)組與返回其他任何對(duì)象沒(méi)什么區(qū)別—— 最終返回的都是一個(gè)句柄。
另一方面,注意當(dāng) flavorSet()隨機(jī)挑選香料的時(shí)候,它需要保證以前出現(xiàn)過(guò)的一次隨機(jī)選擇不會(huì)再次出現(xiàn)。為達(dá)到這個(gè)目的,它使用了一個(gè)無(wú)限 while 循環(huán),不斷地作出隨機(jī)選擇,直到發(fā)現(xiàn)未在 picks 數(shù)組里出現(xiàn)過(guò)的一個(gè)元素為止(當(dāng)然,也可以進(jìn)行字串比較,檢查隨機(jī)選擇是否在 results 數(shù)組里出現(xiàn)過(guò),但字串比較的效率比較低)。若成功,就添加這個(gè)元素,并中斷循環(huán)( break),再查找下一個(gè)( i 值會(huì)遞增)。但假若 t 是一個(gè)已在 picks 里出現(xiàn)過(guò)的數(shù)組,就用標(biāo)簽式的 continue 往回跳兩級(jí),強(qiáng)制選擇一個(gè)新 t。 用一個(gè)調(diào)試程序可以很清楚地看到這個(gè)過(guò)程。
集合
為容納一組對(duì)象,最適宜的選擇應(yīng)當(dāng)是數(shù)組。而且假如容納的是一系列基本數(shù)據(jù)類(lèi)型,更是必須采用數(shù)組。
缺點(diǎn):類(lèi)型未知
使用 Java 集合的“缺點(diǎn)”是在將對(duì)象置入一個(gè)集合時(shí)丟失了類(lèi)型信息。之所以會(huì)發(fā)生這種情況,是由于當(dāng)初編寫(xiě)集合時(shí),那個(gè)集合的程序員根本不知道用戶(hù)到底想把什么類(lèi)型置入集合。若指示某個(gè)集合只允許特定的類(lèi)型,會(huì)妨礙它成為一個(gè)“常規(guī)用途”的工具,為用戶(hù)帶來(lái)麻煩。為解決這個(gè)問(wèn)題,集合實(shí)際容納的是類(lèi)型為 Object 的一些對(duì)象的句柄。
當(dāng)然,也要注意集合并不包括基本數(shù)據(jù)類(lèi)型,因?yàn)樗鼈儾⒉皇菑摹叭魏螙|西”繼承來(lái)的。
Java 不允許人們?yōu)E用置入集合的對(duì)象。假如將一條狗扔進(jìn)一個(gè)貓的集合,那么仍會(huì)將集合內(nèi)的所有東西都看作貓,所以在使用那條狗時(shí)會(huì)得到一個(gè)“違例”錯(cuò)誤。在同樣的意義上,假若試圖將一條狗的句柄“造型”到一只貓,那么運(yùn)行期間仍會(huì)得到一個(gè)“違例”錯(cuò)誤
- 錯(cuò)誤有時(shí)并不顯露出來(lái)
在某些情況下,程序似乎正確地工作,不造型回我們?cè)瓉?lái)的類(lèi)型。第一種情況是相當(dāng)特殊的: String 類(lèi)從編譯器獲得了額外的幫助,使其能夠正常工作。只要編譯器期待的是一個(gè)String 對(duì)象,但它沒(méi)有得到一個(gè),就會(huì)自動(dòng)調(diào)用在 Object 里定義、并且能夠由任何 Java 類(lèi)覆蓋的 toString()方法。這個(gè)方法能生成滿(mǎn)足要求的String 對(duì)象,然后在我們需要的時(shí)候使用。因此,為了讓自己類(lèi)的對(duì)象能顯示出來(lái),要做的全部事情就是覆蓋toString()方法。
可在 Mouse 里看到對(duì) toString()的重定義代碼。在 main()的第二個(gè) for 循環(huán)中,可發(fā)現(xiàn)下述語(yǔ)句:
System.out.println("Free mouse: " + mice.elementAt(i));在“ +”后,編譯器預(yù)期看到的是一個(gè) String 對(duì)象。 elementAt()生成了一個(gè) Object,所以為獲得希望的String,編譯器會(huì)默認(rèn)調(diào)用 toString()。但不幸的是,只有針對(duì) String 才能得到象這樣的結(jié)果;其他任何類(lèi)型都不會(huì)進(jìn)行這樣的轉(zhuǎn)換。
隱藏造型的第二種方法已在 Mousetrap 里得到了應(yīng)用。 caughtYa()方法接收的不是一個(gè) Mouse,而是一個(gè)Object。隨后再將其造型為一個(gè) Mouse。當(dāng)然,這樣做是非常冒失的,因?yàn)橥ㄟ^(guò)接收一個(gè) Object,任何東西都可以傳遞給方法。然而,假若造型不正確—— 如果我們傳遞了錯(cuò)誤的類(lèi)型—— 就會(huì)在運(yùn)行期間得到一個(gè)違例錯(cuò)誤。這當(dāng)然沒(méi)有在編譯期進(jìn)行檢查好,但仍然能防止問(wèn)題的發(fā)生。注意在使用這個(gè)方法時(shí)毋需進(jìn)行造型:
MouseTrap.caughtYa(mice.elementAt(i));
- 生成能自動(dòng)判別類(lèi)型的 Vector
一個(gè)更“健壯”的方案是用 Vector 創(chuàng)建一個(gè)新類(lèi),使其只接收我們指定的
類(lèi)型,也只生成我們希望的類(lèi)型。
新的 GopherVector 類(lèi)有一個(gè)類(lèi)型為 Vector 的 private 成員(從 Vector 繼承有些麻煩,理由稍后便知),而且方法也和 Vector 類(lèi)似。然而,它不會(huì)接收和產(chǎn)生普通 Object,只對(duì) Gopher 對(duì)象
感興趣。
由于 GopherVector 只接收一個(gè) Gopher(地鼠),所以假如我們使用:
gophers.addElement(new Pigeon());
就會(huì)在編譯期間獲得一條出錯(cuò)消息。采用這種方式,盡管從編碼的角度看顯得更令人沉悶,但可以立即判斷出是否使用了正確的類(lèi)型。注意在使用 elementAt()時(shí)不必進(jìn)行造型—— 它肯定是一個(gè) Gopher
枚舉器
容納各種各樣的對(duì)象正是集合的首要任務(wù)。在 Vector 中, addElement()便是我們插入對(duì)象采用的方法,而 elementAt()是
提取對(duì)象的唯一方法。 Vector 非常靈活,我們可在任何時(shí)候選擇任何東西,并可使用不同的索引選擇多個(gè)元素。
若從更高的角度看這個(gè)問(wèn)題,就會(huì)發(fā)現(xiàn)它的一個(gè)缺陷:需要事先知道集合的準(zhǔn)確類(lèi)型,否則無(wú)法使用。乍看來(lái),這一點(diǎn)似乎沒(méi)什么關(guān)系。但假若最開(kāi)始決定使用Vector,后來(lái)在程序中又決定(考慮執(zhí)行效率的原因)改變成一個(gè) List(屬于 Java1.2 集合庫(kù)的一部分),這時(shí)又該如何做呢?
我們通常認(rèn)為反復(fù)器是一種“輕量級(jí)”對(duì)象;也就是說(shuō),創(chuàng)建它只需付出極少的代價(jià)。但也正是由于這個(gè)原因,我們常發(fā)現(xiàn)反復(fù)器存在一些似乎很奇怪的限制。例如,有些反復(fù)器只能朝一個(gè)方向移動(dòng)。
Java 的 Enumeration(枚舉,注釋②)便是具有這些限制的一個(gè)反復(fù)器的例子。除下面這些外,不可再用它
做其他任何事情:
(1) 用一個(gè)名為 elements()的方法要求集合為我們提供一個(gè) Enumeration。我們首次調(diào)用它的 nextElement()
時(shí),這個(gè) Enumeration 會(huì)返回序列中的第一個(gè)元素。
(2) 用 nextElement() 獲得下一個(gè)對(duì)象。
(3) 用 hasMoreElements()檢查序列中是否還有更多的對(duì)象
仔細(xì)研究一下打印方法:
static void printAll(Enumeration e) { while(e.hasMoreElements()) System.out.println( e.nextElement().toString()); }注意其中沒(méi)有與序列類(lèi)型有關(guān)的信息。我們擁有的全部東西便是Enumeration。為了解有關(guān)序列的情況,一個(gè) Enumeration 便足夠了:可取得下一個(gè)對(duì)象,亦可知道是否已抵達(dá)了末尾。取得一系列對(duì)象,然后在其中遍歷,從而執(zhí)行一個(gè)特定的操作—— 這是一個(gè)頗有價(jià)值的編程概念
總結(jié)
以上是生活随笔為你收集整理的java基础系列:集合基础(1)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Windows的cmd ping不通vm
- 下一篇: java基础系列:集合基础(2)