java編程思想 第八章 對象的容納(部分)

java編程思想 第八章 對象的容納(部分)

第八章 對象的容納

在某些程序開發的時候,只有當程序真正的運行時才會知道某一些的的對象時必須的。在沒有運行情況下這是不得而知的。因此為了解決這樣的問題,Java提供了容納對象(或者對象的句柄)的多種方式,其中內建的類型是數組,我們之前已經討論過它,本章準備加深大家對他的認識。此外,Java的工具庫提供了一些“集合類”。利用這些集合類,我們可以容納乃至操縱自己的對象。

8.1 數組

針對容納對象來說,有兩方面的問題將數組數組和其他集合類型區分開來:效率和類型。對於Java來說,為保存和訪問一系列對象(實際是對象的句柄)數組,最有效的方法莫過於數組。數組實際代表一個簡單的線性序列,它使得元素的訪問速度非常快,但我們去要為這種速度付出代價:創建一個數組對象時,它的大小是固定的,而且不可再那個數組對象的“存在時間”內發生改變。可創建特定大小的數組,然後利用光了存儲空間,就在創建一個新數組,將所有的句柄從舊數組移到新數組。這屬於“矢量“(vector)類的行為。然而由於為這種大小的靈活性要付出代價,所以我們認為矢量的效率並沒有數組高。

其他Java集合類型包括:vector(矢量)、Stack(堆棧)以及Hashtable(散列表)。上訴集合類型並沒有什麼特定的類型。換言之,它們將其當做Object類型處理。從某些角度來說,這種處理方法是非常合理的:我們僅需要構建一個集合,然後任何對象都可以進入這個集合。這再一次反應了數組優於常規集合:創建一個數組時,可令其容納一種特定的類型。這意味著可進行編譯期類型檢查,預防自己設置了錯誤的類型,或者錯誤指定了準備提取的類型。當然,在編譯器或者運行期,Java會防止我們講不當的消息發給一個對象。所以我們不必考慮自己的那種做法更危險,只要編譯器能及時指出錯誤,同時運行期間加快速度,目的也就達到了。

8.1.1 數組和第一類對象

無論使用的數組屬於什麼類型,數組標誌符實際都是指向真是對象的一個句柄。那些對象本身是在內存“堆”裡創建的,堆對象既可“隱式”創建(即默認產生),亦可“顯示”創建(即明確指定,用一個new表達式)。堆對象的一部分(實際是我們能訪問的唯一字段或方法)是制度的length成員,它告訴我們那個數組對象最多容納多少元素。對於數組對象,“[]”語法是我們能唯一採用的唯一另一類訪問方法。示例:

package package08;public class ArraySize {public static void main(String[] args) { Weeble[] a; Weeble[] b = new Weeble[5]; Weeble[] c = new Weeble[4]; for(int i = 0;i < c.length;i++){ c[i] = new Weeble(); } Weeble[] d = { new Weeble(),new Weeble(),new Weeble() }; System.out.println("b.length=" + b.length); 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); a = new Weeble[]{ new Weeble(), new Weeble() }; System.out.println("a.length=" +a.length); int[] e; int[] f = new int[5]; int[] g = new int[4]; for(int i = 0;i

上述例子展示了對數組進行初始化的不同方式,以及如何將數組句柄分配給不同的數組對象。它也揭示出對象數組和基本數據類型數組在使用方法上的幾乎是完全一致的。唯一的差別在於對象數組容納的是句柄而基本數據類型容納的是具體數值。

基本數據類型集合

集合類只能容納對象句柄。但對於一個數組,卻既可令其直接容納指向對象的句柄。領用象Integer、Double之類的“封裝器”類,可將基本數據類的值置入一個集合裡。

8.1.2 數組的返回

由於垃圾收集器的存在,Java中寫一個方法用作返回一系列東西,比C/C++要來的簡單。雖然Java返回的任然是數組的指針但是,由於垃圾收集器的存在,無需考慮在java這個數組是否可用——只要需要,它就會自動存在,而且垃圾收集器會在我們完成後自動將其清除。示例:

package package08;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){ 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

8.2 集合

總結之前提到過的知識:為容納一組對象,最適宜的選擇應當是數組。而假如容納的是一系列基本數據,更是必須採用數組。在編寫程序的過程中,通常不能確切地知道最終需要多少個對象。有些時候甚至想用更復雜的方式來保存對象。為解決這個問題,Java提供了四中類型的“集合類”:Vector(矢量)、BitSet(位集)、Stack(堆棧)以及Hashtable(散列表)。與擁有集合功能的其他語言相比,儘管這兒的數量顯得相當少,但仍能用它們解決數量驚人的實際問題。這些集合類具有形形色色的特徵。例如,stack實現了一個LIFO(先入先出)序列,而Hashtable是一種“關聯數組”,允許我們將任何對象關聯起來。除此之外,所有Java集合類都能自動改變自身的大小。所以,在編程的時可以使用數量眾多的對象,同時不必擔心會將集合弄得有多大。

8.2.1 缺點:類型未知

使用Java集合的“缺點”是在將對象置入一個集合時丟失了類型信息。之所以會發生這種情況,是由於當初編寫集合時,那個集合的程序員根本不知道用戶到底把什麼類型置入集合。若指示某個集合只允許特定的類型,會妨礙它成為一個“常規用途”的工具,為用戶帶來麻煩。為解決這個問題,集合實際容納得是類型為object的一些對象的句柄。這種類型當然代表Java中的所有對象,因為他是所有類的根。當然,也要注意這並不包括基本數據類型,因為他們並不是從“任何東西”繼承來的。這是一個很好的方案,只是並不適用下訴場合:

(1)將一個對象句柄置入集合時,由於類型會被拋棄,所以任何類型的對象都可進入我們的集合——即便特別指示它能容納特定類型的對象。舉個例子來說,雖然指示它只能容納貓,但事實上任何人都可以把一條狗扔進來。

(2)由於類型信息不復存在,所以集合能肯定的唯一事情就是自己容納的是一個對象的句柄。正式使用它之前,必須對其進行造型,使其具有正確的類型。

總而言之,就是進入集合相當於將進入的任何帶有類型的對象進行清洗獲得“白身”,使用的時候需要從集合出去,需要重新染色,變成“色身”。

值得欣慰的是,Java不允許人們濫用置入集合的對象。假如將一條狗扔進一個貓的集合,那麼仍會將集合內的所有東西看作貓,所以在使用那條狗時會得到一個“違例”錯誤。在同樣的意義上,假如試圖將一條狗的句柄“造型”到一隻貓。那麼運行期間仍會得到一個“違例”錯誤。示例:

package package08;import java.util.*;class Cat {private int catNumber;Cat(int i){ catNumber = i;}void print(){ System.out.println("Cat #" + catNumber);}}package package08;class Dog {private int dogNumber;Dog(int i){ dogNumber = i;}void print(){ System.out.println("Dog #" +dogNumber);}}package package08;import java.util.Vector;public class CatAndDog {public static void main(String[] args) { Vector cats = new Vector(); for (int i = 0; i < 7; i++) { cats.addElement(new Cat(i)); } for (int i = 0; i < cats.size(); i++) { ((Cat)cats.elementAt(i)).print(); }}}輸出:Cat #0Cat #1Cat #2Cat #3Cat #4Cat #5Cat #6

可以看出,Vector的使用是非常簡單的:先創建一個,再用addElement()置入對象,以後用elementAt()取出那些對象。dog不能以在cat的集合中輸出dog。

1.錯誤有時候並不會顯露出來

在某些情況下,程序似乎正確的工作,不造型回我們與那裡的類型。第一種情況是相當特殊的:String類從編譯器獲得了額外的幫助,使其能夠正常的工作。只要編譯器期待的是一個String對象,但它沒有得到一個,就會自動調用在Object裡定義、並且能夠由任何Java類覆蓋的toString()方法。這個方法能生成滿足要求的String對象,然後在我們需要的時候使用。

因此,為了讓自己類的對象能夠顯示出來,要做的全部事情就是覆蓋toString()方法,如下示例:

package package08;import java.util.*;class Mouse {private int mouseNumber;Mouse(int i){ mouseNumber = i;}public String toString(){ return "This is Mouse #" + mouseNumber;}void print(String msg){ if (msg != null) { System.out.println(msg); } System.out.println("Mouse number" + mouseNumber);}}package package08;class MouseTrap {static void caughtYa(Object m){ Mouse mouse = (Mouse)m; mouse.print("caught one!");}}package package08;import java.util.Vector;public class WorksAnyway {public static void main(String[] args) { Vector mice = new Vector(); for (int i = 0; i < 3; i++) { mice.addElement(new Mouse(i)); } for (int i = 0; i < mice.size(); i++) { System.out.println("Free mouse:"+ mice.elementAt(i)); MouseTrap.caughtYa(mice.elementAt(i)); }}}輸出:Free mouse:This is Mouse #0caught one!Mouse number0Free mouse:This is Mouse #1caught one!Mouse number1Free mouse:This is Mouse #2caught one!Mouse number2

2.生成自動判別類型的Vector

用Vector創建一個新類,使其只接收我們指定的類型,也只生成我們希望的類型。如下所示:

package package08;class Gopher {private int gopherNumber;Gopher(int i){ gopherNumber = i;}void print(String msg){ if (msg != null) { System.out.println(msg); } System.out.println( "Gopher number" + gopherNumber);}}package package08;class GopherTrap {static void caughtYa(Gopher g){ g.print("Caught one!");}}package package08;import java.util.Vector;class GopherVector {private Vector v = new Vector();public void addElement(Gopher m){ v.addElement(m);}public Gopher elementAt(int index){ return(Gopher)v.elementAt(index);}public int size(){ return v.size();}public static void main(String[] args){ GopherVector gophers = new GopherVector(); for (int i = 0; i < 3; i++) { gophers.addElement(new Gopher(i)); } for (int i = 0; i < gophers.size(); i++) { GopherTrap.caughtYa(gophers.elementAt(i)); }}}輸出:Caught one!Gopher number0Caught one!Gopher number1Caught one!Gopher number2

這與之前的一個例子類似,只是新的GopherVector類有一個類型為Vector的private成員,而且方法也和Vector類似。然而,他不會接受和產生普通的Object,只對Gopher對象感興趣。

3.參數化類型

這類問題並不是孤立的——我們許多時候都要在其他類型的基礎上創建新類型。此時,在編譯器件擁有特定的類型信息是非常有幫助的。這便是“參數化類型”的概念。在C++中,他是由語言通過“模板”獲得了直接的支持。至少,java保留了關鍵字generic,期望有一天能夠支持參數化類型。但我們現在無法確定這一天何時來臨。

8.3 枚舉器(反覆器)

java編程思想 第八章 對象的容納(部分)


分享到:


相關文章: