# 4.5 數組初始化
在C中初始化數組極易出錯,而且相當麻煩。C++通過“集合初始化”使其更安全(注釋⑥)。Java則沒有象C++那樣的“集合”概念,因為Java中的所有東西都是對象。但它確實有自己的數組,通過數組初始化來提供支持。
數組代表一系列對象或者基本數據類型,所有相同的類型都封裝到一起——采用一個統一的標識符名稱。數組的定義和使用是通過方括號索引運算符進行的(`[]`)。為定義一個數組,只需在類型名后簡單地跟隨一對空方括號即可:
```
int[] al;
```
也可以將方括號置于標識符后面,獲得完全一致的結果:
```
int al[];
```
這種格式與C和C++程序員習慣的格式是一致的。然而,最“通順”的也許還是前一種語法,因為它指出類型是“一個`int`數組”。本書將沿用那種格式。
編譯器不允許我們告訴它一個數組有多大。這樣便使我們回到了“引用”的問題上。此時,我們擁有的一切就是指向數組的一個引用,而且尚未給數組分配任何空間。為了給數組創建相應的存儲空間,必須編寫一個初始化表達式。對于數組,初始化工作可在代碼的任何地方出現,但也可以使用一種特殊的初始化表達式,它必須在數組創建的地方出現。這種特殊的初始化是一系列由花括號封閉起來的值。存儲空間的分配(等價于使用`new`)將由編譯器在這種情況下進行。例如:
```
int[] a1 = { 1, 2, 3, 4, 5 };
```
那么為什么還要定義一個沒有數組的數組引用呢?
```
int[] a2;
```
事實上在Java中,可將一個數組分配給另一個,所以能使用下述語句:
```
a2 = a1;
```
我們真正準備做的是復制一個引用,就象下面演示的那樣:
```
//: Arrays.java
// Arrays of primitives.
public class Arrays {
public static void main(String[] args) {
int[] a1 = { 1, 2, 3, 4, 5 };
int[] a2;
a2 = a1;
for(int i = 0; i < a2.length; i++)
a2[i]++;
for(int i = 0; i < a1.length; i++)
prt("a1[" + i + "] = " + a1[i]);
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
```
大家看到`a1`獲得了一個初始值,而`a2`沒有;`a2`將在以后賦值——這種情況下是賦給另一個數組。
這里也出現了一些新東西:所有數組都有一個本質成員(無論它們是對象數組還是基本類型數組),可對其進行查詢——但不是改變,從而獲知數組內包含了多少個元素。這個成員就是`length`。與C和C++類似,由于Java數組從元素0開始計數,所以能索引的最大元素編號是`length-1`。如超出邊界,C和C++會“默默”地接受,并允許我們胡亂使用自己的內存,這正是許多程序錯誤的根源。然而,Java可保留我們這受這一問題的損害,方法是一旦超過邊界,就生成一個運行期錯誤(即一個“異常”,這是第9章的主題)。當然,由于需要檢查每個數組的訪問,所以會消耗一定的時間和多余的代碼量,而且沒有辦法把它關閉。這意味著數組訪問可能成為程序效率低下的重要原因——如果它們在關鍵的場合進行。但考慮到因特網訪問的安全,以及程序員的編程效率,Java設計人員還是應該把它看作是值得的。
程序編寫期間,如果不知道在自己的數組里需要多少元素,那么又該怎么辦呢?此時,只需簡單地用`new`在數組里創建元素。在這里,即使準備創建的是一個基本數據類型的數組,`new`也能正常地工作(`new`不會創建非數組的基本類型):
```
//: ArrayNew.java
// Creating arrays with new.
import java.util.*;
public class ArrayNew {
static Random rand = new Random();
static int pRand(int mod) {
return Math.abs(rand.nextInt()) % mod + 1;
}
public static void main(String[] args) {
int[] a;
a = new int[pRand(20)];
prt("length of a = " + a.length);
for(int i = 0; i < a.length; i++)
prt("a[" + i + "] = " + a[i]);
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
```
由于數組的大小是隨機決定的(使用早先定義的`pRand()`方法),所以非常明顯,數組的創建實際是在運行期間進行的。除此以外,從這個程序的輸出中,大家可看到基本數據類型的數組元素會自動初始化成“空”值(對于數值,空值就是零;對于`char`,它是`null`;而對于`boolean`,它卻是`false`)。
當然,數組可能已在相同的語句中定義和初始化了,如下所示:
```
int[] a = new int[pRand(20)];
```
若操作的是一個非基本類型對象的數組,那么無論如何都要使用`new`。在這里,我們會再一次遇到引用問題,因為我們創建的是一個引用數組。請大家觀察包裝器類型`Integer`,它是一個類,而非基本數據類型:
```
//: ArrayClassObj.java
// Creating an array of non-primitive objects.
import java.util.*;
public class ArrayClassObj {
static Random rand = new Random();
static int pRand(int mod) {
return Math.abs(rand.nextInt()) % mod + 1;
}
public static void main(String[] args) {
Integer[] a = new Integer[pRand(20)];
prt("length of a = " + a.length);
for(int i = 0; i < a.length; i++) {
a[i] = new Integer(pRand(500));
prt("a[" + i + "] = " + a[i]);
}
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
```
在這兒,甚至在`new`調用后才開始創建數組:
```
Integer[] a = new Integer[pRand(20)];
```
它只是一個引用數組,而且除非通過創建一個新的`Integer`對象,從而初始化了對象引用,否則初始化進程不會結束:
```
a[i] = new Integer(pRand(500));
```
但若忘記創建對象,就會在運行期試圖讀取空數組位置時獲得一個“異常”錯誤。
下面讓我們看看打印語句中`String`對象的構成情況。大家可看到指向`Integer`對象的引用會自動轉換,從而產生一個`String`,它代表著位于對象內部的值。
亦可用花括號封閉列表來初始化對象數組。可采用兩種形式,第一種是Java 1.0允許的唯一形式。第二種(等價)形式自Java 1.1才開始提供支持:
```
//: ArrayInit.java
// Array initialization
public class ArrayInit {
public static void main(String[] args) {
Integer[] a = {
new Integer(1),
new Integer(2),
new Integer(3),
};
// Java 1.1 only:
Integer[] b = new Integer[] {
new Integer(1),
new Integer(2),
new Integer(3),
};
}
} ///:~
```
這種做法大多數時候都很有用,但限制也是最大的,因為數組的大小是在編譯期間決定的。初始化列表的最后一個逗號是可選的(這一特性使長列表的維護變得更加容易)。
數組初始化的第二種形式(Java 1.1開始支持)提供了一種更簡便的語法,可創建和調用方法,獲得與C的“變量參數列表”(C通常把它簡稱為“變參表”)一致的效果。這些效果包括未知的參數數量以及未知的類型(如果這樣選擇的話)。由于所有類最終都是從通用的根類`Object`中繼承的,所以能創建一個方法,令其獲取一個`Object`數組,并象下面這樣調用它:
```
//: VarArgs.java
// Using the Java 1.1 array syntax to create
// variable argument lists
class A { int i; }
public class VarArgs {
static void f(Object[] x) {
for(int i = 0; i < x.length; i++)
System.out.println(x[i]);
}
public static void main(String[] args) {
f(new Object[] {
new Integer(47), new VarArgs(),
new Float(3.14), new Double(11.11) });
f(new Object[] {"one", "two", "three" });
f(new Object[] {new A(), new A(), new A()});
}
} ///:~
```
此時,我們對這些未知的對象并不能采取太多的操作,而且這個程序利用自動`String`轉換對每個`Object`做一些有用的事情。在第11章(運行期類型識別或RTTI),大家還會學習如何調查這類對象的準確類型,使自己能對它們做一些有趣的事情。
## 4.5.1 多維數組
在Java里可以方便地創建多維數組:
```
//: MultiDimArray.java
// Creating multidimensional arrays.
import java.util.*;
public class MultiDimArray {
static Random rand = new Random();
static int pRand(int mod) {
return Math.abs(rand.nextInt()) % mod + 1;
}
public static void main(String[] args) {
int[][] a1 = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};
for(int i = 0; i < a1.length; i++)
for(int j = 0; j < a1[i].length; j++)
prt("a1[" + i + "][" + j +
"] = " + a1[i][j]);
// 3-D array with fixed length:
int[][][] a2 = new int[2][2][4];
for(int i = 0; i < a2.length; i++)
for(int j = 0; j < a2[i].length; j++)
for(int k = 0; k < a2[i][j].length;
k++)
prt("a2[" + i + "][" +
j + "][" + k +
"] = " + a2[i][j][k]);
// 3-D array with varied-length vectors:
int[][][] a3 = new int[pRand(7)][][];
for(int i = 0; i < a3.length; i++) {
a3[i] = new int[pRand(5)][];
for(int j = 0; j < a3[i].length; j++)
a3[i][j] = new int[pRand(5)];
}
for(int i = 0; i < a3.length; i++)
for(int j = 0; j < a3[i].length; j++)
for(int k = 0; k < a3[i][j].length;
k++)
prt("a3[" + i + "][" +
j + "][" + k +
"] = " + a3[i][j][k]);
// Array of non-primitive objects:
Integer[][] a4 = {
{ new Integer(1), new Integer(2)},
{ new Integer(3), new Integer(4)},
{ new Integer(5), new Integer(6)},
};
for(int i = 0; i < a4.length; i++)
for(int j = 0; j < a4[i].length; j++)
prt("a4[" + i + "][" + j +
"] = " + a4[i][j]);
Integer[][] a5;
a5 = new Integer[3][];
for(int i = 0; i < a5.length; i++) {
a5[i] = new Integer[3];
for(int j = 0; j < a5[i].length; j++)
a5[i][j] = new Integer(i*j);
}
for(int i = 0; i < a5.length; i++)
for(int j = 0; j < a5[i].length; j++)
prt("a5[" + i + "][" + j +
"] = " + a5[i][j]);
}
static void prt(String s) {
System.out.println(s);
}
} ///:~
```
用于打印的代碼里使用了`length`,所以它不必依賴固定的數組大小。
第一個例子展示了基本數據類型的一個多維數組。我們可用花括號定出數組內每個向量的邊界:
```
int[][] a1 = {
{ 1, 2, 3, },
{ 4, 5, 6, },
};
```
每個方括號對都將我們移至數組的下一級。
第二個例子展示了用`new`分配的一個三維數組。在這里,整個數組都是立即分配的:
```
int[][][] a2 = new int[2][2][4];
```
但第三個例子卻向大家揭示出構成矩陣的每個向量都可以有任意的長度:
```
int[][][] a3 = new int[pRand(7)][][];
for(int i = 0; i < a3.length; i++) {
a3[i] = new int[pRand(5)][];
for(int j = 0; j < a3[i].length; j++)
a3[i][j] = new int[pRand(5)];
}
```
對于第一個`new`創建的數組,它的第一個元素的長度是隨機的,其他元素的長度則沒有定義。`for`循環內的第二個`new`則會填寫元素,但保持第三個索引的未定狀態——直到碰到第三個`new`。
根據輸出結果,大家可以看到:假若沒有明確指定初始化值,數組值就會自動初始化成零。
可用類似的表式處理非基本類型對象的數組。這從第四個例子可以看出,它向我們演示了用花括號收集多個`new`表達式的能力:
```
Integer[][] a4 = {
{ new Integer(1), new Integer(2)},
{ new Integer(3), new Integer(4)},
{ new Integer(5), new Integer(6)},
};
```
第五個例子展示了如何逐漸構建非基本類型的對象數組:
```
Integer[][] a5;
a5 = new Integer[3][];
for(int i = 0; i < a5.length; i++) {
a5[i] = new Integer[3];
for(int j = 0; j < a5[i].length; j++)
a5[i][j] = new Integer(i*j);
}
```
`i*j`只是在`Integer`里置了一個有趣的值。
- 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 推薦讀物