# 8.4 集合的類型
標準Java 1.0和1.1庫配套提供了非常少的一系列集合類。但對于自己的大多數編程要求,它們基本上都能勝任。正如大家到本章末尾會看到的,Java 1.2提供的是一套重新設計過的大型集合庫。
## 8.4.1 `Vector`
`Vector`的用法很簡單,這已在前面的例子中得到了證明。盡管我們大多數時候只需用`addElement()`插入對象,用`elementAt()`一次提取一個對象,并用`elements()`獲得對序列的一個“枚舉”。但仍有其他一系列方法是非常有用的。同我們對于Java庫慣常的做法一樣,在這里并不使用或講述所有這些方法。但請務必閱讀相應的電子文檔,對它們的工作有一個大概的認識。
(1) 崩潰Java
Java標準集合里包含了`toString()`方法,所以它們能生成自己的`String`表達方式,包括它們容納的對象。例如在`Vector`中,`toString()`會在`Vector`的各個元素中步進和遍歷,并為每個元素調用`toString()`。假定我們現在想打印出自己類的地址。看起來似乎簡單地引用`this`即可(特別是C++程序員有這樣做的傾向):
```
//: CrashJava.java
// One way to crash Java
import java.util.*;
public class CrashJava {
public String toString() {
return "CrashJava address: " + this + "\n";
}
public static void main(String[] args) {
Vector v = new Vector();
for(int i = 0; i < 10; i++)
v.addElement(new CrashJava());
System.out.println(v);
}
} ///:~
```
若只是簡單地創建一個`CrashJava`對象,并將其打印出來,就會得到無窮無盡的一系列異常錯誤。然而,假如將`CrashJava`對象置入一個`Vector`,并象這里演示的那樣打印`Vector`,就不會出現什么錯誤提示,甚至連一個異常都不會出現。此時Java只是簡單地崩潰(但至少它沒有崩潰我的操作系統)。這已在Java 1.1中測試通過。
此時發生的是字符串的自動類型轉換。當我們使用下述語句時:
```
"CrashJava address: " + this
```
編譯器就在一個字符串后面發現了一個`+`以及好象并非字符串的其他東西,所以它會試圖將`this`轉換成一個字符串。轉換時調用的是`toString()`,后者會產生一個遞歸調用。若在一個`Vector`內出現這種事情,看起來棧就會溢出,同時異常控制機制根本沒有機會作出響應。
若確實想在這種情況下打印出對象的地址,解決方案就是調用`Object`的`toString`方法。此時就不必加入`this`,只需使用`super.toString()`。當然,采取這種做法也有一個前提:我們必須從`Object`直接繼承,或者沒有一個父類覆蓋了`toString`方法。
## 8.4.2 `BitSet`
`BitSet`實際是由“二進制位”構成的一個`Vector`。如果希望高效率地保存大量“開-關”信息,就應使用`BitSet`。它只有從尺寸的角度看才有意義;如果希望的高效率的訪問,那么它的速度會比使用一些固有類型的數組慢一些。
此外,`BitSet`的最小長度是一個長整數(`Long`)的長度:64位。這意味著假如我們準備保存比這更小的數據,如8位數據,那么`BitSet`就顯得浪費了。所以最好創建自己的類,用它容納自己的標志位。
在一個普通的`Vector`中,隨我們加入越來越多的元素,集合也會自我膨脹。在某種程度上,`BitSet`也不例外。也就是說,它有時會自行擴展,有時則不然。而且Java的1.0版本似乎在這方面做得最糟,它的`BitSet`表現十分差強人意(Java1.1已改正了這個問題)。下面這個例子展示了`BitSet`是如何運作的,同時演示了1.0版本的錯誤:
```
//: Bits.java
// Demonstration of BitSet
import java.util.*;
public class Bits {
public static void main(String[] args) {
Random rand = new Random();
// Take the LSB of nextInt():
byte bt = (byte)rand.nextInt();
BitSet bb = new BitSet();
for(int i = 7; i >=0; i--)
if(((1 << i) & bt) != 0)
bb.set(i);
else
bb.clear(i);
System.out.println("byte value: " + bt);
printBitSet(bb);
short st = (short)rand.nextInt();
BitSet bs = new BitSet();
for(int i = 15; i >=0; i--)
if(((1 << i) & st) != 0)
bs.set(i);
else
bs.clear(i);
System.out.println("short value: " + st);
printBitSet(bs);
int it = rand.nextInt();
BitSet bi = new BitSet();
for(int i = 31; i >=0; i--)
if(((1 << i) & it) != 0)
bi.set(i);
else
bi.clear(i);
System.out.println("int value: " + it);
printBitSet(bi);
// Test bitsets >= 64 bits:
BitSet b127 = new BitSet();
b127.set(127);
System.out.println("set bit 127: " + b127);
BitSet b255 = new BitSet(65);
b255.set(255);
System.out.println("set bit 255: " + b255);
BitSet b1023 = new BitSet(512);
// Without the following, an exception is thrown
// in the Java 1.0 implementation of BitSet:
// b1023.set(1023);
b1023.set(1024);
System.out.println("set bit 1023: " + b1023);
}
static void printBitSet(BitSet b) {
System.out.println("bits: " + b);
String bbits = new String();
for(int j = 0; j < b.size() ; j++)
bbits += (b.get(j) ? "1" : "0");
System.out.println("bit pattern: " + bbits);
}
} ///:~
```
隨機數字生成器用于創建一個隨機的`byte`、`short`和`int`。每一個都會轉換成`BitSet`內相應的位模型。此時一切都很正常,因為`BitSet`是64位的,所以它們都不會造成最終尺寸的增大。但在Java 1.0中,一旦`BitSet`大于64位,就會出現一些令人迷惑不解的行為。假如我們設置一個只比`BitSet`當前分配存儲空間大出1的一個位,它能夠正常地擴展。但一旦試圖在更高的位置設置位,同時不先接觸邊界,就會得到一個惱人的異常。這正是由于`BitSet`在Java 1.0里不能正確擴展造成的。本例創建了一個512位的`BitSet`。構造器分配的存儲空間是位數的兩倍。所以假如設置位1024或更高的位,同時沒有先設置位1023,就會在Java 1.0里得到一個異常。但幸運的是,這個問題已在Java 1.1得到了改正。所以如果是為Java 1.0寫代碼,請盡量避免使用`BitSet`。
## 8.4.3 `Stack`
`Stack`有時也可以稱為“后入先出”(LIFO)集合。換言之,我們在棧里最后“壓入”的東西將是以后第一個“彈出”的。和其他所有Java集合一樣,我們壓入和彈出的都是“對象”,所以必須對自己彈出的東西進行“轉換”。
一種很少見的做法是拒絕使用`Vector`作為一個`Stack`的基本構成元素,而是從`Vector`里“繼承”一個`Stack`。這樣一來,它就擁有了一個`Vector`的所有特征及行為,另外加上一些額外的`Stack`行為。很難判斷出設計者到底是明確想這樣做,還是屬于一種固有的設計。
下面是一個簡單的棧示例,它能讀入數組的每一行,同時將其作為字符串壓入棧。
```
//: Stacks.java
// Demonstration of Stack Class
import java.util.*;
public class Stacks {
static String[] months = {
"January", "February", "March", "April",
"May", "June", "July", "August", "September",
"October", "November", "December" };
public static void main(String[] args) {
Stack stk = new Stack();
for(int i = 0; i < months.length; i++)
stk.push(months[i] + " ");
System.out.println("stk = " + stk);
// Treating a stack as a Vector:
stk.addElement("The last line");
System.out.println(
"element 5 = " + stk.elementAt(5));
System.out.println("popping elements:");
while(!stk.empty())
System.out.println(stk.pop());
}
} ///:~
```
`months`數組的每一行都通過`push()`繼承進入棧,稍后用`pop()`從棧的頂部將其取出。要聲明的一點是,`Vector`操作亦可針對Stack對象進行。這可能是由繼承的特質決定的——`Stack`“屬于”一種`Vector`。因此,能對`Vector`進行的操作亦可針對`Stack`進行,例如`elementAt()`方法。
## 8.4.4 `Hashtable`
`Vector`允許我們用一個數字從一系列對象中作出選擇,所以它實際是將數字同對象關聯起來了。但假如我們想根據其他標準選擇一系列對象呢?棧就是這樣的一個例子:它的選擇標準是“最后壓入棧的東西”。這種“從一系列對象中選擇”的概念亦可叫作一個“映射”、“字典”或者“關聯數組”。從概念上講,它看起來象一個`Vector`,但卻不是通過數字來查找對象,而是用另一個對象來查找它們!這通常都屬于一個程序中的重要進程。
在Java中,這個概念具體反映到抽象類`Dictionary`身上。該類的接口是非常直觀的`size()`告訴我們其中包含了多少元素;`isEmpty()`判斷是否包含了元素(是則為`true`);`put(Object key, Object value)`添加一個值(我們希望的東西),并將其同一個鍵關聯起來(想用于搜索它的東西);`get(Object key)`獲得與某個鍵對應的值;而`remove(Object Key)`用于從列表中刪除“鍵-值”對。還可以使用枚舉技術:`keys()`產生對鍵的一個枚舉(`Enumeration`);而`elements()`產生對所有值的一個枚舉。這便是一個`Dictionary`(字典)的全部。
`Dictionary`的實現過程并不麻煩。下面列出一種簡單的方法,它使用了兩個`Vector`,一個用于容納鍵,另一個用來容納值:
```
//: AssocArray.java
// Simple version of a Dictionary
import java.util.*;
public class AssocArray extends Dictionary {
private Vector keys = new Vector();
private Vector values = new Vector();
public int size() { return keys.size(); }
public boolean isEmpty() {
return keys.isEmpty();
}
public Object put(Object key, Object value) {
keys.addElement(key);
values.addElement(value);
return key;
}
public Object get(Object key) {
int index = keys.indexOf(key);
// indexOf() Returns -1 if key not found:
if(index == -1) return null;
return values.elementAt(index);
}
public Object remove(Object key) {
int index = keys.indexOf(key);
if(index == -1) return null;
keys.removeElementAt(index);
Object returnval = values.elementAt(index);
values.removeElementAt(index);
return returnval;
}
public Enumeration keys() {
return keys.elements();
}
public Enumeration elements() {
return values.elements();
}
// Test it:
public static void main(String[] args) {
AssocArray aa = new AssocArray();
for(char c = 'a'; c <= 'z'; c++)
aa.put(String.valueOf(c),
String.valueOf(c)
.toUpperCase());
char[] ca = { 'a', 'e', 'i', 'o', 'u' };
for(int i = 0; i < ca.length; i++)
System.out.println("Uppercase: " +
aa.get(String.valueOf(ca[i])));
}
} ///:~
```
在對`AssocArray`的定義中,我們注意到的第一個問題是它“擴展”了字典。這意味著`AssocArray`屬于`Dictionary`的一種類型,所以可對其發出與`Dictionary`一樣的請求。如果想生成自己的`Dictionary`,而且就在這里進行,那么要做的全部事情只是填充位于`Dictionar`y內的所有方法(而且必須覆蓋所有方法,因為它們——除構造器外——都是抽象的)。
`Vector key`和`value`通過一個標準索引編號鏈接起來。也就是說,如果用`roof`的一個鍵以及`blue`的一個值調用`put()`——假定我們準備將一個房子的各部分與它們的油漆顏色關聯起來,而且`AssocArray`里已有100個元素,那么`roof`就會有101個鍵元素,而`blue`有101個值元素。而且要注意一下`get()`,假如我們作為鍵傳遞`roof`,它就會產生與`keys.index.Of()`的索引編號,然后用那個索引編號生成相關的值向量內的值。
`main()`中進行的測試是非常簡單的;它只是將小寫字符轉換成大寫字符,這顯然可用更有效的方式進行。但它向我們揭示出了`AssocArray`的強大功能。
標準Java庫只包含`Dictionary`的一個變種,名為`Hashtable`(散列表,注釋③)。Java的散列表具有與`AssocArray`相同的接口(因為兩者都是從`Dictionary`繼承來的)。但有一個方面卻反映出了差別:執行效率。若仔細想想必須為一個`get()`做的事情,就會發現在一個`Vector`里搜索鍵的速度要慢得多。但此時用散列表卻可以加快不少速度。不必用冗長的線性搜索技術來查找一個鍵,而是用一個特殊的值,名為“散列碼”。散列碼可以獲取對象中的信息,然后將其轉換成那個對象“相對唯一”的整數(`int`)。所有對象都有一個散列碼,而`hashCode()`是根類`Object`的一個方法。`Hashtable`獲取對象的`hashCode()`,然后用它快速查找鍵。這樣可使性能得到大幅度提升(④)。散列表的具體工作原理已超出了本書的范圍(⑤)——大家只需要知道散列表是一種快速的“字典”(`Dictionary`)即可,而字典是一種非常有用的工具。
③:如計劃使用RMI(在第15章詳述),應注意將遠程對象置入散列表時會遇到一個問題(參閱《Core Java》,作者Conrell和Horstmann,Prentice-Hall 1997年出版)
④:如這種速度的提升仍然不能滿足你對性能的要求,甚至可以編寫自己的散列表例程,從而進一步加快表格的檢索過程。這樣做可避免在與`Object`之間進行轉換的時間延誤,也可以避開由Java類庫散列表例程內建的同步過程。
⑤:我的知道的最佳參考讀物是《Practical Algorithms for Programmers》,作者為Andrew Binstock和John Rex,Addison-Wesley 1995年出版。
作為應用散列表的一個例子,可考慮用一個程序來檢驗Java的`Math.random()`方法的隨機性到底如何。在理想情況下,它應該產生一系列完美的隨機分布數字。但為了驗證這一點,我們需要生成數量眾多的隨機數字,然后計算落在不同范圍內的數字多少。散列表可以極大簡化這一工作,因為它能將對象同對象關聯起來(此時是將`Math.random()`生成的值同那些值出現的次數關聯起來)。如下所示:
```
//: Statistics.java
// Simple demonstration of Hashtable
import java.util.*;
class Counter {
int i = 1;
public String toString() {
return Integer.toString(i);
}
}
class Statistics {
public static void main(String[] args) {
Hashtable ht = new Hashtable();
for(int i = 0; i < 10000; i++) {
// Produce a number between 0 and 20:
Integer r =
new Integer((int)(Math.random() * 20));
if(ht.containsKey(r))
((Counter)ht.get(r)).i++;
else
ht.put(r, new Counter());
}
System.out.println(ht);
}
} ///:~
```
在`main()`中,每次產生一個隨機數字,它都會封裝到一個`Integer`對象里,使引用能夠隨同散列表一起使用(不可對一個集合使用基本數據類型,只能使用對象引用)。`containKey()`方法檢查這個鍵是否已經在集合里(也就是說,那個數字以前發現過嗎?)若已在集合里,則`get()`方法獲得那個鍵關聯的值,此時是一個`Counter`(計數器)對象。計數器內的值`i`隨后會增加1,表明這個特定的隨機數字又出現了一次。
假如鍵以前尚未發現過,那么方法`put()`仍然會在散列表內置入一個新的“鍵-值”對。在創建之初,`Counter`會自己的變量`i`自動初始化為1,它標志著該隨機數字的第一次出現。
為顯示散列表,只需把它簡單地打印出來即可。`Hashtable toString()`方法能遍歷所有鍵-值對,并為每一對都調用`toString()`。`Integer toString()`是事先定義好的,可看到計數器使用的`toString`。一次運行的結果(添加了一些換行)如下:
```
{19=526, 18=533, 17=460, 16=513, 15=521, 14=495,
13=512, 12=483, 11=488, 10=487, 9=514, 8=523,
7=497, 6=487, 5=480, 4=489, 3=509, 2=503, 1=475,
0=505}
```
大家或許會對`Counter`類是否必要感到疑惑,它看起來似乎根本沒有封裝類`Integer`的功能。為什么不用`int`或`Integer`呢?事實上,由于所有集合能容納的僅有對象引用,所以根本不可以使用整數。學過集合后,封裝類的概念對大家來說就可能更容易理解了,因為不可以將任何基本數據類型置入集合里。然而,我們對Java包裝器能做的唯一事情就是將其初始化成一個特定的值,然后讀取那個值。也就是說,一旦包裝器對象已經創建,就沒有辦法改變一個值。這使得`Integer`包裝器對解決我們的問題毫無意義,所以不得不創建一個新類,用它來滿足自己的要求。
(1) 創建“關鍵”類
在前面的例子里,我們用一個標準庫的類(`Integer`)作為`Hashtable`的一個鍵使用。作為一個鍵,它能很好地工作,因為它已經具備正確運行的所有條件。但在使用散列表的時候,一旦我們創建自己的類作為鍵使用,就會遇到一個很常見的問題。例如,假設一套天氣預報系統將`Groundhog`(土拔鼠)對象匹配成`Prediction`(預報)。這看起來非常直觀:我們創建兩個類,然后將`Groundhog`作為鍵使用,而將`Prediction`作為值使用。如下所示:
```
//: SpringDetector.java
// Looks plausible, but doesn't work right.
import java.util.*;
class Groundhog {
int ghNumber;
Groundhog(int n) { ghNumber = n; }
}
class Prediction {
boolean shadow = Math.random() > 0.5;
public String toString() {
if(shadow)
return "Six more weeks of Winter!";
else
return "Early Spring!";
}
}
public class SpringDetector {
public static void main(String[] args) {
Hashtable ht = new Hashtable();
for(int i = 0; i < 10; i++)
ht.put(new Groundhog(i), new Prediction());
System.out.println("ht = " + ht + "\n");
System.out.println(
"Looking up prediction for groundhog #3:");
Groundhog gh = new Groundhog(3);
if(ht.containsKey(gh))
System.out.println((Prediction)ht.get(gh));
}
} ///:~
```
每個`Groundhog`都具有一個標識號碼,所以赤了在散列表中查找一個`Prediction`,只需指示它“告訴我與`Groundhog`號碼3相關的`Prediction`”。`Prediction`類包含了一個布爾值,用`Math.random()`進行初始化,以及一個`toString()`為我們解釋結果。在`main()`中,用`Groundhog`以及與它們相關的`Prediction`填充一個散列表。散列表被打印出來,以便我們看到它們確實已被填充。隨后,用標識號碼為3的一個`Groundhog`查找與`Groundhog #3`對應的預報。
看起來似乎非常簡單,但實際是不可行的。問題在于`Groundhog`是從通用的`Object`根類繼承的(若當初未指定基類,則所有類最終都是從`Object`繼承的)。事實上是用`Object`的`hashCode()`方法生成每個對象的散列碼,而且默認情況下只使用它的對象的地址。所以,`Groundhog(3)`的第一個實例并不會產生與`Groundhog(3)`第二個實例相等的散列碼,而我們用第二個實例進行檢索。
大家或許認為此時要做的全部事情就是正確地覆蓋`hashCode()`。但這樣做依然行不能,除非再做另一件事情:覆蓋也屬于`Object`一部分的`equals()`。當散列表試圖判斷我們的鍵是否等于表內的某個鍵時,就會用到這個方法。同樣地,默認的`Object.equals()`只是簡單地比較對象地址,所以一個`Groundhog(3)`并不等于另一個`Groundhog(3)`。
因此,為了在散列表中將自己的類作為鍵使用,必須同時覆蓋`hashCode()`和`equals()`,就象下面展示的那樣:
```
//: SpringDetector2.java
// If you create a class that's used as a key in
// a Hashtable, you must override hashCode()
// and equals().
import java.util.*;
class Groundhog2 {
int ghNumber;
Groundhog2(int n) { ghNumber = n; }
public int hashCode() { return ghNumber; }
public boolean equals(Object o) {
return (o instanceof Groundhog2)
&& (ghNumber == ((Groundhog2)o).ghNumber);
}
}
public class SpringDetector2 {
public static void main(String[] args) {
Hashtable ht = new Hashtable();
for(int i = 0; i < 10; i++)
ht.put(new Groundhog2(i),new Prediction());
System.out.println("ht = " + ht + "\n");
System.out.println(
"Looking up prediction for groundhog #3:");
Groundhog2 gh = new Groundhog2(3);
if(ht.containsKey(gh))
System.out.println((Prediction)ht.get(gh));
}
} ///:~
```
注意這段代碼使用了來自前一個例子的`Prediction`,所以`SpringDetector.java`必須首先編譯,否則就會在試圖編譯`SpringDetector2.java`時得到一個編譯期錯誤。
`Groundhog2.hashCode()`將土拔鼠號碼作為一個標識符返回(在這個例子中,程序員需要保證沒有兩個土拔鼠用同樣的ID號碼并存)。為了返回一個獨一無二的標識符,并不需要`hashCode()`,`equals()`方法必須能夠嚴格判斷兩個對象是否相等。
`equals()`方法要進行兩種檢查:檢查對象是否為`null`;若不為`null`,則繼續檢查是否為`Groundhog2`的一個實例(要用到`instanceof`關鍵字,第11章會詳加論述)。即使為了繼續執行`equals()`,它也應該是一個`Groundhog2`。正如大家看到的那樣,這種比較建立在實際`ghNumber`的基礎上。這一次一旦我們運行程序,就會看到它終于產生了正確的輸出(許多Java庫的類都覆蓋了`hashcode()`和`equals()`方法,以便與自己提供的內容適應)。
(2) 屬性:`Hashtable`的一種類型
在本書的第一個例子中,我們使用了一個名為`Properties`(屬性)的`Hashtable`類型。在那個例子中,下述程序行:
```
Properties p = System.getProperties();
p.list(System.out);
```
調用了一個名為`getProperties()`的`static`方法,用于獲得一個特殊的`Properties`對象,對系統的某些特征進行描述。`list()`屬于`Properties`的一個方法,可將內容發給我們選擇的任何流式輸出。也有一個`save()`方法,可用它將屬性列表寫入一個文件,以便日后用`load()`方法讀取。
盡管`Properties`類是從`Hashtable`繼承的,但它也包含了一個散列表,用于容納“默認”屬性的列表。所以假如沒有在主列表里找到一個屬性,就會自動搜索默認屬性。
`Properties`類亦可在我們的程序中使用(第17章的`ClassScanner.java`便是一例)。在Java庫的用戶文檔中,往往可以找到更多、更詳細的說明。
## 8.4.5 再論枚舉器
我們現在可以開始演示`Enumeration`(枚舉)的真正威力:將穿越一個序列的操作與那個序列的基礎結構分隔開。在下面的例子里,`PrintData`類用一個`Enumeration`在一個序列中移動,并為每個對象都調用`toString()`方法。此時創建了兩個不同類型的集合:一個`Vector`和一個`Hashtable`。并且在它們里面分別填充`Mouse`和`Hamster`對象(本章早些時候已定義了這些類;注意必須先編譯`HamsterMaze.java`和`WorksAnyway.java`,否則下面的程序不能編譯)。由于`Enumeration`隱藏了基層集合的結構,所以`PrintData`不知道或者不關心`Enumeration`來自于什么類型的集合:
```
//: Enumerators2.java
// Revisiting Enumerations
import java.util.*;
class PrintData {
static void print(Enumeration e) {
while(e.hasMoreElements())
System.out.println(
e.nextElement().toString());
}
}
class Enumerators2 {
public static void main(String[] args) {
Vector v = new Vector();
for(int i = 0; i < 5; i++)
v.addElement(new Mouse(i));
Hashtable h = new Hashtable();
for(int i = 0; i < 5; i++)
h.put(new Integer(i), new Hamster(i));
System.out.println("Vector");
PrintData.print(v.elements());
System.out.println("Hashtable");
PrintData.print(h.elements());
}
} ///:~
```
注意`PrintData.print()`利用了這些集合中的對象屬于`Object`類這一事實,所以它調用了`toString()`。但在解決自己的實際問題時,經常都要保證自己的`Enumeration`穿越某種特定類型的集合。例如,可能要求集合中的所有元素都是一個Shape(幾何形狀),并含有`draw()`方法。若出現這種情況,必須從`Enumeration.nextElement()`返回的`Object`進行向下轉換,以便產生一個`Shape`。
- Java 編程思想
- 寫在前面的話
- 引言
- 第1章 對象入門
- 1.1 抽象的進步
- 1.2 對象的接口
- 1.3 實現方案的隱藏
- 1.4 方案的重復使用
- 1.5 繼承:重新使用接口
- 1.6 多態對象的互換使用
- 1.7 對象的創建和存在時間
- 1.8 異常控制:解決錯誤
- 1.9 多線程
- 1.10 永久性
- 1.11 Java和因特網
- 1.12 分析和設計
- 1.13 Java還是C++
- 第2章 一切都是對象
- 2.1 用引用操縱對象
- 2.2 所有對象都必須創建
- 2.3 絕對不要清除對象
- 2.4 新建數據類型:類
- 2.5 方法、參數和返回值
- 2.6 構建Java程序
- 2.7 我們的第一個Java程序
- 2.8 注釋和嵌入文檔
- 2.9 編碼樣式
- 2.10 總結
- 2.11 練習
- 第3章 控制程序流程
- 3.1 使用Java運算符
- 3.2 執行控制
- 3.3 總結
- 3.4 練習
- 第4章 初始化和清除
- 4.1 用構造器自動初始化
- 4.2 方法重載
- 4.3 清除:收尾和垃圾收集
- 4.4 成員初始化
- 4.5 數組初始化
- 4.6 總結
- 4.7 練習
- 第5章 隱藏實現過程
- 5.1 包:庫單元
- 5.2 Java訪問指示符
- 5.3 接口與實現
- 5.4 類訪問
- 5.5 總結
- 5.6 練習
- 第6章 類復用
- 6.1 組合的語法
- 6.2 繼承的語法
- 6.3 組合與繼承的結合
- 6.4 到底選擇組合還是繼承
- 6.5 protected
- 6.6 累積開發
- 6.7 向上轉換
- 6.8 final關鍵字
- 6.9 初始化和類裝載
- 6.10 總結
- 6.11 練習
- 第7章 多態性
- 7.1 向上轉換
- 7.2 深入理解
- 7.3 覆蓋與重載
- 7.4 抽象類和方法
- 7.5 接口
- 7.6 內部類
- 7.7 構造器和多態性
- 7.8 通過繼承進行設計
- 7.9 總結
- 7.10 練習
- 第8章 對象的容納
- 8.1 數組
- 8.2 集合
- 8.3 枚舉器(迭代器)
- 8.4 集合的類型
- 8.5 排序
- 8.6 通用集合庫
- 8.7 新集合
- 8.8 總結
- 8.9 練習
- 第9章 異常差錯控制
- 9.1 基本異常
- 9.2 異常的捕獲
- 9.3 標準Java異常
- 9.4 創建自己的異常
- 9.5 異常的限制
- 9.6 用finally清除
- 9.7 構造器
- 9.8 異常匹配
- 9.9 總結
- 9.10 練習
- 第10章 Java IO系統
- 10.1 輸入和輸出
- 10.2 增添屬性和有用的接口
- 10.3 本身的缺陷:RandomAccessFile
- 10.4 File類
- 10.5 IO流的典型應用
- 10.6 StreamTokenizer
- 10.7 Java 1.1的IO流
- 10.8 壓縮
- 10.9 對象序列化
- 10.10 總結
- 10.11 練習
- 第11章 運行期類型識別
- 11.1 對RTTI的需要
- 11.2 RTTI語法
- 11.3 反射:運行期類信息
- 11.4 總結
- 11.5 練習
- 第12章 傳遞和返回對象
- 12.1 傳遞引用
- 12.2 制作本地副本
- 12.3 克隆的控制
- 12.4 只讀類
- 12.5 總結
- 12.6 練習
- 第13章 創建窗口和程序片
- 13.1 為何要用AWT?
- 13.2 基本程序片
- 13.3 制作按鈕
- 13.4 捕獲事件
- 13.5 文本字段
- 13.6 文本區域
- 13.7 標簽
- 13.8 復選框
- 13.9 單選鈕
- 13.10 下拉列表
- 13.11 列表框
- 13.12 布局的控制
- 13.13 action的替代品
- 13.14 程序片的局限
- 13.15 視窗化應用
- 13.16 新型AWT
- 13.17 Java 1.1用戶接口API
- 13.18 可視編程和Beans
- 13.19 Swing入門
- 13.20 總結
- 13.21 練習
- 第14章 多線程
- 14.1 反應靈敏的用戶界面
- 14.2 共享有限的資源
- 14.3 堵塞
- 14.4 優先級
- 14.5 回顧runnable
- 14.6 總結
- 14.7 練習
- 第15章 網絡編程
- 15.1 機器的標識
- 15.2 套接字
- 15.3 服務多個客戶
- 15.4 數據報
- 15.5 一個Web應用
- 15.6 Java與CGI的溝通
- 15.7 用JDBC連接數據庫
- 15.8 遠程方法
- 15.9 總結
- 15.10 練習
- 第16章 設計模式
- 16.1 模式的概念
- 16.2 觀察器模式
- 16.3 模擬垃圾回收站
- 16.4 改進設計
- 16.5 抽象的應用
- 16.6 多重分發
- 16.7 訪問器模式
- 16.8 RTTI真的有害嗎
- 16.9 總結
- 16.10 練習
- 第17章 項目
- 17.1 文字處理
- 17.2 方法查找工具
- 17.3 復雜性理論
- 17.4 總結
- 17.5 練習
- 附錄A 使用非JAVA代碼
- 附錄B 對比C++和Java
- 附錄C Java編程規則
- 附錄D 性能
- 附錄E 關于垃圾收集的一些話
- 附錄F 推薦讀物