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