理解 Java 程式
| 導航 入門 主題: |
本文介紹了一個可以從控制檯執行的小型 Java 程式。它計算平面上的兩點之間的距離。您現在不必瞭解程式的結構和含義;我們很快就會講到。此外,由於該程式旨在作為簡單介紹,因此它有一些改進空間,我們將在本模組的後面展示一些這些改進。但是,讓我們不要操之過急!
此類名為 Distance,因此使用您最喜歡的編輯器或 Java IDE,首先建立一個名為 Distance.java 的檔案,然後複製下面的原始碼,將其貼上到檔案中並儲存檔案。
程式碼清單 2.1:Distance.java
public class Distance {
private java.awt.Point point0, point1;
public Distance(int x0, int y0, int x1, int y1) {
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
public void printDistance() {
System.out.println("Distance between " + point0 + " and " + point1
+ " is " + point0.distance(point1));
}
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
}
|
此時,您可能希望檢視原始碼,看看您能理解多少。雖然可能不是最通順的程式語言,但瞭解其他過程語言(如 C)或其他面嚮物件語言(如 C++ 或 C#)的人能夠理解大多數(如果不是全部)示例程式。
儲存檔案後,編譯程式
編譯命令
$ javac Distance.java |
(如果 javac 命令失敗,請檢視 安裝說明。)
要執行程式,請為它提供平面上的兩點的 x 和 y 座標,座標之間用空格隔開。對於此版本的 Distance,僅支援整數點。命令序列為 java Distance <x0> <y0> <x1> <y1>,用於計算點 (x0, y0) 和 (x1, y1) 之間的距離。
如果您收到 java.lang.NumberFormatException 異常,則某些引數不是數字。如果您收到 java.lang.ArrayIndexOutOfBoundsException 異常,則您沒有提供足夠的數字。 |
以下兩個示例
點 (0, 3) 和 (4, 0) 之間距離的輸出
$ java Distance 0 3 4 0 Distance between java.awt.Point[x=0,y=3] and java.awt.Point[x=4,y=0] is 5.0 |
點 (-4, 5) 和 (11, 19) 之間距離的輸出
$ java Distance -4 5 11 19 Distance between java.awt.Point[x=-4,y=5] and java.awt.Point[x=11,y=19] is 20.518284528683193 |
我們稍後將解釋這種奇怪的輸出,並展示如何改進它。
正如承諾的那樣,我們現在將提供對該 Java 程式的詳細描述。我們將討論程式的語法和結構以及該結構的含義。
程式碼清單
public class Distance {
private java.awt.Point point0, point1;
public Distance(int x0, int y0, int x1, int y1) {
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
public void printDistance() {
System.out.println("Distance between " + point0 + " and " + point1
+ " is " + point0.distance(point1));
}
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
}
|
- 圖 2.1:基本 Java 語法。
- 有關 Java 語法元素的進一步說明,另請參閱 語法。
Java 類的 語法 是用於編碼該類的字元、符號及其結構。Java 程式由一系列標記組成。標記有不同型別。例如,有詞語標記,例如 class 和 public,它們代表 關鍵字 (紫色 上面)——在 Java 中具有保留意義的特殊詞語。其他詞語,如 Distance、point0、x1 和 printDistance 不是關鍵字,而是 識別符號(灰色)。識別符號在 Java 中有多種用途,但主要用作名稱。Java 還具有用於表示數字的標記,例如 1 和 3;這些被稱為 字面量 (橙色)。字串字面量 (藍色),例如 "Distance between ",由嵌入雙引號中的零個或多個字元組成,而 運算子 (紅色),例如 + 和 = 用於表示基本計算,例如加法或字串連線或賦值。還有左大括號和右大括號({ 和 }),它們包含 程式碼塊。類的主體就是一個這樣的程式碼塊。有些標記是標點符號,例如句號 . 和逗號 , 以及分號 ;。使用 空白,例如空格、製表符和換行符,來分隔標記。例如,關鍵字和識別符號之間需要空白:publicstatic 是一個包含十二個字元的單個識別符號,而不是兩個 Java 關鍵字。
public class Distance {
private java.awt.Point point0, point1;
public Distance(int x0, int y0, int x1, int y1) {
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
public void printDistance() {
System.out.println("Distance between " + point0 + " and " + point1
+ " is " + point0.distance(point1));
}
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
}
- 圖 2.2: 宣告和定義。
如上 所示,一系列的標記被用來構建 Java 類中的下一個構建塊:宣告和定義。類宣告提供了一個類的名稱和可見性。在我們的例子中,public class Distance 是類宣告。它由(在本例中)兩個關鍵字組成, 和 public,後面跟著識別符號 classDistance。
這意味著我們正在定義一個名為 Distance 的類。其他類,或者在我們的例子中,命令列,可以透過這個名稱來引用這個類。public 關鍵字是一個 訪問修飾符,它宣告這個類及其成員可以被其他類訪問。class 關鍵字,顯然,標識這個宣告是一個類。Java 還允許宣告 介面 和 註解。
類聲明後面跟著一個塊(用花括號包圍),它提供了類的定義 (在 圖 2.2 中用藍色表示)。定義是類的實現 - 類的成員的宣告和定義。這個類包含正好六個成員,我們將在後面解釋。
- 兩個名為
point0和point1的欄位宣告 (用綠色表示) - 一個建構函式宣告 (用橙色表示)
- 三個方法宣告 (用紅色表示)
宣告
程式碼部分 2.1: 宣告。
private java.awt.Point point0, point1;
|
...聲明瞭兩個 例項欄位。例項欄位表示在每次構造類的例項時分配的命名值。當一個 Java 程式建立一個 Distance 例項時,該例項將包含 point0 和 point1 的空間。當另一個 Distance 物件被建立時,它將包含它自己的 point0 和 point1 值的空間。第一個 Distance 物件中 point0 的值可以獨立於第二個 Distance 物件中 point0 的值變化。
此宣告由以下部分組成
private訪問修飾符,
這意味著這些例項欄位對其他類不可見。- 例項欄位的型別。在本例中,型別為
java.awt.Point。
這是java.awt包中的Point類。 - 例項欄位的名稱,以逗號分隔的列表。
這兩個欄位也可以用兩個獨立的但更詳細的宣告來宣告,
程式碼部分 2.2: 詳細宣告。
private java.awt.Point point0;
private java.awt.Point point1;
|
由於這些欄位的型別是引用型別(即一個 引用 或可以儲存 引用 到物件值的欄位),Java 會在建立 Distance 例項時隱式地將 point0 和 point1 的值初始化為 null。null 值意味著一個引用值不引用任何物件。特殊的 Java 字面量 null 用於在程式中表示 null 值。雖然你可以在宣告中顯式地賦值 null 值,就像在
程式碼部分 2.3: 宣告和賦值。
private java.awt.Point point0 = null;
private java.awt.Point point1 = null;
|
中一樣,但這不是必需的,大多數程式設計師會省略這些預設賦值。
一個 建構函式 是類中的一種特殊方法,用於構造類的例項。建構函式可以執行物件的初始化,超出了 Java VM 自動執行的初始化。例如,Java 會自動將 point0 和 point1 欄位初始化為 null。
程式碼部分 2.4: 類的建構函式
public Distance(int x0, int y0, int x1, int y1) {
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
|
上面的建構函式包含五個部分
- 可選的 訪問修飾符。
在本例中,建構函式被宣告為public - 建構函式名稱,必須與類名完全匹配:在本例中為
Distance。 - 建構函式引數。
引數列表是必需的。即使建構函式沒有任何引數,你也必須指定空列表()。引數列表宣告每個方法引數的型別和名稱。 - 可選的
throws子句,它宣告建構函式可能丟擲的 異常。這個建構函式沒有宣告任何異常。 - 建構函式主體,它是一個 Java 塊(用
{}括起來)。這個建構函式的主體包含兩個語句。
這個建構函式接受四個引數,命名為 x0, y0, x1 和 y1。每個引數都需要一個引數型別宣告,在本例中,所有四個引數都是 。引數列表中的引數用逗號分隔。int
這個建構函式中的兩個賦值使用了 Java 的 new 運算子 來分配兩個 java.awt.Point 物件。第一個分配一個表示第一個點 (x0, y0) 的物件,並將其分配給 point0 例項變數(替換例項變數初始化的 null 值)。第二個語句分配一個第二個帶有 (x1, y1) 的 java.awt.Point 例項,並將其分配給 point1 例項變數。
這是 Distance 類的建構函式。Distance 隱式地從 java.lang.Object 擴充套件而來。如果建構函式沒有顯式編碼,Java 會將對超類建構函式的呼叫插入到建構函式的第一條可執行語句中。上面的建構函式主體等同於以下主體,其中包含顯式的超類建構函式呼叫
程式碼部分 2.5: 超類建構函式。
{
super();
point0 = new java.awt.Point(x0, y0);
point1 = new java.awt.Point(x1, y1);
}
|
雖然這個類可以用其他方式實現,例如簡單地儲存兩個點的座標並計算距離為 ,但這個類卻使用了現有的 java.awt.Point 類。這種選擇與這個類的抽象定義相符:列印平面上的兩點之間的距離。我們利用了 Java 平臺中已有的行為,而不是再次實現它。我們將在後面看到如何使程式更加靈活,而不會增加太多複雜性,因為我們在這裡選擇使用物件抽象。然而,關鍵在於這個類使用了資訊隱藏。也就是說,如何 儲存類的狀態或如何 計算距離是隱藏的。我們可以更改此實現,而不會改變客戶端使用和呼叫該類的方式。
方法 是第三種也是最重要的類成員型別。這個類包含三個 方法,其中定義了 Distance 類的行為:printDistance()、main() 和 intValue()
printDistance() 方法將兩點之間的距離列印到標準輸出(通常是控制檯)。
程式碼部分 2.6: printDistance() 方法。
public void printDistance() {
System.out.println("Distance between " + point0
+ " and " + point1
+ " is " + point0.distance(point1));
}
|
此例項方法在隱式Distance物件的上下文中執行。例項欄位引用,point0和point1,指的是該隱式物件的例項欄位。您也可以使用特殊變數this來顯式引用當前物件。在例項方法中,Java 將名稱this繫結到正在執行該方法的物件,this的型別是當前類的型別。printDistance方法的程式碼也可以寫成
程式碼部分 2.7:當前類的顯式例項。
System.out.println("Distance between " + this.point0
+ " and " + this.point1
+ " is " + this.point0.distance(this.point1));
|
以使例項欄位引用更加明確。
此方法在一個語句中既計算距離又列印距離。距離使用point0.distance(point1)計算;distance()是java.awt.Point類(point0和point1是其例項)的例項方法。該方法對point0進行操作(在執行方法期間將this繫結到point0所引用的物件),並接受另一個點作為引數。實際上,它比這稍微複雜一些,但我們將在後面解釋。distance()方法的結果是一個雙精度浮點數。
此方法使用語法
程式碼部分 2.8:字串連線。
"Distance between " + this.point0
+ " and " + this.point1
+ " is " + this.point0.distance(this.point1)
|
構建一個字串傳遞給System.out.println()。此表示式是一系列字串連線方法,這些方法連線字串或原始型別(如雙精度數)或物件的字串表示形式,並返回一個長字串。例如,此表示式對於點 (0,3) 和 (4,0) 的結果是字串
輸出
"Distance between java.awt.Point[x=0,y=3] and java.awt.Point[x=4,y=0] is 5.0" |
該方法隨後將它列印到System.out。
為了列印,我們呼叫println()。這是一個來自java.io.PrintStream的例項方法,它是類java.lang.System中靜態欄位out的型別。Java 虛擬機器在啟動程式時將System.out繫結到標準輸出流。
main()方法
[edit | edit source]main()方法是 Java 在您從命令列啟動 Java 程式時呼叫的主要入口點。命令
輸出
java Distance 0 3 4 0 |
指示 Java 找到 Distance 類,將四個命令列引數放入 String 值陣列中,然後將這些引數傳遞給該類的public static main(String[])方法。我們將很快介紹陣列。任何您想從命令列或桌面快捷方式呼叫的 Java 類都必須具有此簽名或以下簽名的主方法:public static main(String...)。
程式碼部分 2.9:main()方法。
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
|
main()方法呼叫最終方法intValue()四次。intValue()接受一個字串引數,並返回字串中表示的整數值。例如,intValue("3")將返回整數 3。
進行測試優先程式設計或執行迴歸測試的人員會在每個 Java 類中編寫一個 main() 方法,以及在每個 Python 模組中編寫一個 main() 函式,以執行自動測試。當某人直接執行檔案時,main() 方法會執行並執行該檔案的自動測試。當某人執行另一個 Java 檔案,該檔案又匯入許多其他 Java 類時,只會執行一個 main() 方法——直接執行檔案的 main() 方法。
intValue()方法
[edit | edit source]intValue()方法將它的工作委託給Integer.parseInt()方法。main 方法可以直接呼叫Integer.parseInt();intValue()方法只是使main()方法更易讀。
程式碼部分 2.10:intValue()方法。
private static int intValue(String data) {
return Integer.parseInt(data);
}
|
此方法是private,因為它與欄位point0和point1一樣,是類內部實現的一部分,不是Distance類的外部程式設計介面的一部分。
靜態方法與例項方法
[edit | edit source]main()和intValue()方法都是靜態方法。static關鍵字告訴編譯器建立一個與該類關聯的單個記憶體空間。每個例項化的單個物件都有自己的私有狀態變數和方法,但使用相同的方法,這些方法是編譯器在第一個類物件被例項化或建立時建立的單個類物件的共同方法。這意味著該方法在靜態或非物件上下文中執行——當靜態方法從各個物件執行時,沒有隱式的單獨例項可用,並且特殊變數this不可用。因此,靜態方法不能直接訪問例項方法或例項欄位(如printDistance())或point0)。main()方法只能透過例項引用(如dist)呼叫例項方法printDistance()方法。
資料型別
[edit | edit source]大多數宣告都有一個數據型別。Java 具有幾種資料型別類別:引用型別、基本型別、陣列型別以及一種特殊型別 void。
基本型別
[edit | edit source]基本型別用於表示布林值、字元和數值。此程式顯式地只使用了一種基本型別,,它表示 32 位有符號整數值。該程式還隱式地使用int,它是doublejava.awt.Point的distance()方法的返回值型別。double值是 64 位 IEEE 浮點數。main()方法使用整數值 0、1、2 和 3 來訪問命令列引數的元素。Distance()建構函式的四個引數也具有型別int。此外,intValue()方法的返回型別為int。這意味著對該方法的呼叫,例如intValue(args[0]),是一個int型別的表示式。這有助於解釋為什麼 main 方法不能呼叫
程式碼部分 2.11:錯誤型別。
new Distance(args[0], args[1], args[2], args[3]) // This is an error
|
由於args陣列元素的型別是 String,而我們的建構函式的引數必須是int,因此這種呼叫會導致錯誤,因為 Java 不會自動將 String 型別的值轉換為int值。
Java 的基本型別是、boolean、byte、char、short、int、long和float。它們都是 Java 語言的關鍵字。double
引用型別
[edit | edit source]除了基本型別之外,Java 還支援引用型別。引用型別是一種由 Java 類或介面定義的 Java 資料型別。引用型別之所以得名,是因為這些值引用物件或包含對物件的引用。這個想法類似於其他語言(如 C)中的指標。
Java 使用引用型別java.lang.String來表示字元資料序列或String,它通常被稱為String。字串字面量,如"Distance between ",是型別為 String 的常量。
此程式使用三種不同的引用型別
- java.lang.String(或簡稱為 String)
- Distance
- java.awt.Point
- 有關更多資訊,請參見章節:Java 程式設計/類、物件和型別。
Java 支援陣列,它們是聚合型別,具有固定元素型別(可以是任何 Java 型別)和整數大小。該程式只使用一個數組,String[] args。這表明 args 具有陣列型別,並且元素型別為 String。Java VM 會構造並初始化傳遞給 main 方法的陣列。有關如何建立陣列和訪問其大小的更多詳細資訊,請參見陣列。
陣列的元素使用整數索引訪問。陣列的第一個元素始終是元素 0。該程式使用索引 0、1、2 和 3 顯式訪問 args 陣列的前四個元素。該程式沒有執行任何輸入驗證,例如驗證使用者是否向程式傳遞了至少四個引數。我們將在稍後修復這個問題。
void 不是 Java 中的型別;它表示型別的缺失。不返回值的方法宣告為void 方法。
此類定義了兩個 void 方法
程式碼部分 2.12:Void 方法
public static void main(String[] args) { ... }
public void printDistance() { ... }
|
Java 中的空白用於分隔 Java 原始檔中的標記。在某些地方需要空白,例如在訪問修飾符、型別名稱和識別符號之間,並且在其他地方用於提高可讀性。
在 Java 需要空白的地方,可以使用一個或多個空白字元。在 Java 中空白是可選的地方,可以使用零個或多個空白字元。
Java 空白包括
- 空格字元
' '(0x20), - 製表符(十六進位制 0x09),
- 換頁符(十六進位制 0x0c),
- 換行符(十六進位制 0x0a)或回車符(十六進位制 0x0d)字元。
行分隔符是特殊的空白字元,因為它們還會終止行註釋,而普通空白則不會。
其他 Unicode 空格字元,包括垂直製表符,在 Java 中不允許作為空白。
檢視 static 方法 intValue
程式碼部分 2.13:方法宣告
private static int intValue(String data) {
return Integer.parseInt(data);
}
|
在 private 和 static 之間、static 和 int 之間、int 和 intValue 之間以及 String 和 data 之間需要空白。
如果程式碼這樣寫
程式碼部分 2.14:摺疊的程式碼
privatestaticint intValue(String data) {
return Integer.parseInt(data);
}
|
...它的意思完全不同:它聲明瞭一個返回型別為 privatestaticint 的方法。這種型別不太可能存在,而且方法不再是靜態的,因此上述程式碼將導致語義錯誤。
Java 忽略語句前面的所有空白。因此,這兩個程式碼片段對於編譯器來說是相同的
程式碼部分 2.15:縮排的程式碼
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
|
程式碼部分 2.16:未縮排的程式碼
public static void main(String[] args) {
Distance dist = new Distance(
intValue(args[0]), intValue(args[1]),
intValue(args[2]), intValue(args[3]));
dist.printDistance();
}
private static int intValue(String data) {
return Integer.parseInt(data);
}
|
但是,第一個程式碼的樣式(帶空白)更受歡迎,因為可讀性更高。即使以較高的閱讀速度,也可以更容易地區分方法體和方法頭。