變數(Variables)
正如我們上一節中所學,物件將它的狀態儲存在"欄位(fields)"中
int cadence = 0;
int speed = 0;
int gear = 1;
複製程式碼
"何為物件?"中向您介紹了“欄位(fields)”,但是您可能會存在一些疑問,例如:命名"欄位(fields)"的規則和約定是什麼?除了int,還有一些什麼資料型別?欄位宣告時必須初始化嗎?如果沒有顯示初始化,它們具有預設的值嗎?本節課我們將探索這些問題的答案,但是在這之前,下面是您必須首先注意的一些技術區別。在Java程式語言中,術語“欄位(fields)”和“變數(Variables)”都被採用,對於新開發人員來說,這是一個常見的困惑源,它們通常指代的似乎是同一個事物。
Java程式語言定義了以下幾種變數:
-
例項變數(非靜態欄位):技術上來說,物件將各自的狀態儲存在“非靜態欄位”中,也就是說,宣告欄位時沒有指定static關鍵字。非靜態欄位也稱為例項變數,因為他們的值在每個類的例項中(或者說:在每個物件中)都是不一樣的。一輛自行車的當前速度與另一輛自行車的當前速度無關。
-
類變數(靜態欄位):使用靜態修飾符(static)宣告的任何欄位稱為類變數;這告訴編譯器,不管這個類例項化了多少次,該變數都僅存在一個副本。對於特定型別自行車齒輪數的欄位可以宣告為static,這將賦予所有例項相同的齒輪數。程式碼段:static int numGears = 6;將建立一個靜態欄位,此外,可以新增關鍵字final來表示齒輪的數量永遠不會改變。
-
區域性變數:與物件將狀態儲存在欄位中相似,方法經常將其暫時的狀態存在區域性變數中。宣告區域性變數的語法類似於宣告欄位(int count = 0;)。沒有特殊的關鍵字指定其為區域性變數;這完全取決於變數宣告的位置——方法的開括號和閉括號之間;因此,區域性變數只對宣告它們的方法可見;其他類無法訪問它們。
-
引數: 在自行車類和“Hello world!”應用中您已經看到過引數的示例。回想一下main方法:public static void main(String[] args),這裡,args是這個方法的引數。重要的是要記住引數總是被分類為“變數”而不是“欄位”。這也適用於其他接受引數的構造方法(如建構函式和異常處理器),您將在本教程的後面學習這些
話雖如此,如果我們談論的是“一般的欄位”(不包括區域性變數和引數),我們可以簡單地說“欄位”。如果討論適用於“以上所有”,我們可以簡單地說“變數”。如果上下文需要區別,我們將使用特定的術語(靜態欄位、區域性變數等)。你可能會經常看到"成員"這個術語。欄位、方法和巢狀型別統稱為其成員。
命名
每種程式語言對於允許使用的命名方式都有自己的一套規則和約定,Java程式語言也不例外,變數命名的規則和約定可以總結如下:
-
變數名區分大小寫。變數的名稱可以是任何合法的識別符號——不限長度的Unicode字母和數字序列,以字母,“$”符,或下劃線“_”開頭。然而,習慣上,變數名總是以字母開頭,而不是“$”或“_”。此外,根據慣例,美元符號字元從未使用過。您可能會發現在某些情況下,自動生成的名稱將包含美元符號,但是變數命名時應該避免使用。下劃線字元也有類似的約定;雖然從技術上講,以“_”作為變數名的開頭是合法的,但是不鼓勵這樣做。空格是不被允許使用的。
-
後續字元可以是字母、數字、美元符號或下劃線字元。當為變數選擇名稱時,使用完整的單詞而不是模糊的縮寫。這樣做將使您的程式碼更容易閱讀和理解。在許多情況下,它還會使您的程式碼更直觀;例如,名為cadence、speed和gear的欄位比縮寫版本(如s、c和g)更直觀。還要記住,您選擇的名稱不能是關鍵字或保留字
-
如果您選擇的名稱只包含一個單詞,請將該單詞全部用小寫字母拼寫。如果由一個以上的單片語成,則將後面每個單詞的第一個字母大寫。如:gearRatio、currentGear。對於static final int NUM_GEARS = 6,約定有少許變化,將每個字母大寫,然後用下劃線分隔後面的單詞。按照慣例,下劃線字元永遠不會在其他地方使用
基本資料型別
Java程式語言是靜態型別語言,這意味著所有的變數在使用前必須先宣告。包括宣告變數的型別和名稱,正如你已經見過的:
int gear = 1;
複製程式碼
這樣做會告訴程式存在一個名為“gear”的欄位,該欄位儲存數值資料,初始值為“1”。 變數的資料型別決定了它可能包含的值,以及可能對其執行的操作。除了int,Java程式語言還支援其他7種基本資料型別。基本型別由語言預定義,並由保留關鍵字命名。不同基本資料型別之間的值並不共享。Java支援的8種基本資料型別分別為:
-
byte:byte資料型別是8-bit補碼錶示的有符號整數。最小值為-128,最大值為127(含)。byte資料型別在大陣列中節省記憶體時顯得十分有用,在大陣列中,記憶體的節省實際上非常重要。範圍限制有助於闡明程式碼時可以用其代替int;變數的範圍有限這一事實可以作為文件的一種形式。
-
short:short資料型別是16-bit補碼錶示的有符號整數。最小值為-32768,最大值為32767(含)。與byte一樣,同樣的指導原則也適用:在實際需要節省記憶體的情況下,您可以使用short來在大陣列中節省記憶體。
-
int:預設情況下,int資料型別是一個32-bit補碼錶示的有符號整數,最小值為-2^31,最大值為2^31-1。在Java SE 8及更高版本中,可以使用int資料型別表示範圍為0到2^32-1的無符號32-bit整數。使用Integer類將int資料型別用作無符號整數,更多資訊見The Number Classes一節,在Integer類中新增了compareUnsigned、divideUnsigned等靜態方法來支援無符號整數的算術運算。
-
long:long資料型別是一個64-bit補碼錶示的有符號整數,最小值為-2^64,最大值為2^64-1。在Java SE 8及更高版本中,您可以使用long資料型別來表示範圍為0到2^64-1的無符號64-bit整數。當您需要比int提供的值範圍更大的值時,可以使用這種資料型別。Long類還包含compareUnsigned、divideUnsigned等方法來支援無符號long的算術操作
-
float:float資料型別是一個單精度的32位IEEE 754浮點數,它的取值區間超出了我們的討論範圍,但是在Floating-Point Types, Formats, and Values一節中有詳細說明。與byte和short的建議一樣,如果需要在浮點數的大陣列中節省記憶體,請使用float(而不是double)。這種資料型別不應該用於儲存精確的值,比如貨幣。為此,您需要使用java.math.BigDecimal類。Numbers and Strings中涵蓋了BigDecimal和其它Java平臺提供的有用的類。
-
double:double資料型別是一個雙精度的64位IEEE 754浮點數,它的取值區間超出了我們的討論範圍,但是在Floating-Point Types, Formats, and Values一節中有詳細說明。對於包含小數的值,這種資料型別通常是預設選擇。如上所述,這種資料型別永遠不應該用於精確的值,比如貨幣。
-
boolean:boolean資料型別只有兩個可能的值:true和false。使用此資料型別儲存"真/假"條件。這個資料型別表示1-bit的資訊,但是它實際佔用記憶體的“大小”並不是精確定義的。
-
char: char資料型別是一個16位Unicode字元。 最小值為 '\u0000' (0),最大值為 '\uffff' (65535)
除了上面列出的八種基本資料型別之外,Java程式語言還通過Java .lang.String提供了對字串的特殊支援。將字串括在雙引號內將自動建立一個新的字串物件;例如: String s = "this is a string";String物件是不可變的,這意味著一旦建立,它們的值就不能更改。String類在技術上不是原始資料型別,但是考慮到該語言對它的特殊支援,您可能會這樣認為。您將在Numbers and Strings中瞭解關於String類的更多資訊
預設值
宣告欄位時並不總是需要賦值,編譯器將為已經宣告但未初始化的欄位設定合理的預設值。一般來說,根據資料型別的不同,這個預設值將是零或null。然而,依賴這些預設值通常被認為是糟糕的程式設計風格
下表總結了上述資料型別的預設值:
區域性變數略有不同;編譯器從不將預設值分配給未初始化的區域性變數。如果無法在宣告區域性變數的地方初始化該變數,請確保在嘗試使用它之前為其賦值。訪問未初始化的區域性變數將導致編譯時錯誤。
字面量(Literals)
您可能已經注意到,在初始化基本型別的變數時不使用new關鍵字。基本型別是構建在語言中的特殊資料型別;它們不是通過類建立的物件。字面量是值的原始碼表示,直接在程式碼中表示,不需要計算。如下所示,可以將字面量賦值給基本型別的變數:
boolean result = true;
char capitalC = 'C';
byte b = 100;
short s = 10000;
int i = 100000;
複製程式碼
整數字面量
如果以字母L或l結尾,整數字面量的型別為long;否則它是int型別的。建議使用大寫字母L,因為小寫字母l很難與數字1區分.整數型別byte、short、int和long的值可以從int字面量建立。long型別的值超出int的範圍,可以從long字面量建立。整數字面值可以由這些數字系統表示:
- 十進位制:以10為基數,由數字0到9組成;這是你每天使用的數字系統
- 16進位制:以16為基數,由數字0到9和字母A到F組成
- 二進位制:以2為基數,由數字0和1組成(您可以在Java SE 7和更高版本中建立二進位制字面量)
對於一般程式設計,十進位制系統可能是您將使用的唯一數字系統。但是,如果需要使用另一個數字系統,下面的示例顯示了正確的語法。字首0x表示十六進位制,0b表示二進位制:
// The number 26, in decimal
int decVal = 26;
// The number 26, in hexadecimal
int hexVal = 0x1a;
// The number 26, in binary
int binVal = 0b11010;
複製程式碼
浮點數字面量
如果以字母F或f結尾,浮點字面量的型別為float;否則,它的型別是double,並且可以選擇以字母D或d結尾。浮點型別(float和double)也可以使用E或e(用於科學表示法)、F或f(32位float字面量)和D或d(64位double字面量;這是預設值,按慣例可省略)來表示。
double d1 = 123.4;
// same value as d1, but in scientific notation
double d2 = 1.234e2;
float f1 = 123.4f;
複製程式碼
字元和字串字面量
字元和字串型別的字面量可以包含任何Unicode (UTF-16)字元。如果編輯器和檔案系統允許,可以在程式碼中直接使用這些字元。如果不允許,您可以使用“Unicode轉義”,比如“\u0108”(大寫的C)或"S\u00ED Se\u00F1or"(西班牙語中的Sí Señor)。對於char字面量,請始終使用'單引號';對於String字面量,請使用“雙引號”。Unicode轉義序列可以在程式的其他地方使用(例如在欄位名中),而不僅僅是在字元或字串字面量中。Java程式語言還支援一些用於字元和字串字面量的特殊轉義序列:\b(退格)、\t(製表符)、\n(換行)、\f(表格換行)、\r(回車)、\"(雙引號)、\'(單引號)和\\(反斜槓)
還有一個特殊的null字面量,可以用作任何引用型別的值。除基本型別的變數外,可以將null賦給任何變數。除了測試null值的存在性之外,您對null值幾乎無能為力。因此,在程式中經常使用null作為標記來指示某些物件不可用。
最後,還有一種特殊的字面量,叫做類字面量,它是由一個型別名加上“.class”組成的;例如String.class。這表示物件本身的型別(類的型別)。
在數字字面量中使用下劃線字元
在Java SE 7和更高版本中,任何下劃線字元(_)都可以出現在數字字面量中數字之間的任何位置。這個特性支援您將數字字面量進行分隔,以提高程式碼的可讀性。 例如,如果您的程式碼包含很多位數字,您可以使用下劃線將數字分成三組,類似於使用逗號或空格等標點符號作為分隔符。 下面的示例展示了在數字字面量中使用下劃線的方法:
long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;
複製程式碼
只能在數字之間放置下劃線;不能在以下位置放置下劃線:
- 在數字的開頭或結尾
- 在浮點數的小數點相鄰位置
- 在F或L字尾之前
- 在需要一串數字的位置(原文:In positions where a string of digits is expected)
下面的例子演示了數字字面量中有效和無效的下劃線位置:
// Invalid: cannot put underscores
// 小數點相鄰位置
float pi1 = 3_.1415F;
// Invalid: cannot put underscores
// 小數點相鄰位置
float pi2 = 3._1415F;
// Invalid: cannot put underscores
// 在L字尾之前
long socialSecurityNumber1 = 999_99_9999_L;
// OK (decimal literal)
int x1 = 5_2;
// Invalid: cannot put underscores
// 字面量的開頭或結尾
int x2 = 52_;
// OK (decimal literal)
int x3 = 5_______2;
// Invalid: cannot put underscores
// 在0x字首中
int x4 = 0_x52;
// Invalid: cannot put underscores
// 在數字的開頭
int x5 = 0x_52;
// OK (hexadecimal literal)
int x6 = 0x5_2;
// Invalid: cannot put underscores
// 數字的結尾
int x7 = 0x52_;
複製程式碼
陣列
陣列是一個容器物件,它儲存固定數量的單一型別值。陣列的長度是在建立陣列時確定的。建立之後,它的長度就是固定的。您已經在“Hello World!”應用程式的主方法中看到了一個陣列示例(main(String[] args))。本節更詳細地討論陣列:
陣列中的每一項都稱為一個元素,每個元素都由其數值索引訪問。如上圖所示,編號從0開始。例如,第9個元素將在索引8處訪問。
下面的程式ArrayDemo建立一個整數陣列,在陣列中放入一些值,並將每個值列印到標準輸出:
class ArrayDemo {
public static void main(String[] args) {
// declares an array of integers
int[] anArray;
// allocates memory for 10 integers
anArray = new int[10];
// initialize first element
anArray[0] = 100;
// initialize second element
anArray[1] = 200;
// and so forth
anArray[2] = 300;
anArray[3] = 400;
anArray[4] = 500;
anArray[5] = 600;
anArray[6] = 700;
anArray[7] = 800;
anArray[8] = 900;
anArray[9] = 1000;
System.out.println("Element at index 0: "
+ anArray[0]);
System.out.println("Element at index 1: "
+ anArray[1]);
System.out.println("Element at index 2: "
+ anArray[2]);
System.out.println("Element at index 3: "
+ anArray[3]);
System.out.println("Element at index 4: "
+ anArray[4]);
System.out.println("Element at index 5: "
+ anArray[5]);
System.out.println("Element at index 6: "
+ anArray[6]);
System.out.println("Element at index 7: "
+ anArray[7]);
System.out.println("Element at index 8: "
+ anArray[8]);
System.out.println("Element at index 9: "
+ anArray[9]);
}
}
複製程式碼
程式輸出:
Element at index 0: 100
Element at index 1: 200
Element at index 2: 300
Element at index 3: 400
Element at index 4: 500
Element at index 5: 600
Element at index 6: 700
Element at index 7: 800
Element at index 8: 900
Element at index 9: 1000
複製程式碼
在實際的程式設計環境中,您可能會使用受支援的迴圈結構來遍歷陣列的每個元素,而不是像前面的示例那樣單獨地編寫每一行。然而,這個例子清楚地說明了陣列語法。在 "控制流(Control Flow)"一節中您將瞭解各種迴圈結構(for、while和do-while)。
宣告引用陣列的變數
前面的程式使用以下程式碼行宣告一個陣列(名為anArray):
// declares an array of integers
int[] anArray;
複製程式碼
與其他型別變數的宣告一樣,陣列宣告有兩個元件:陣列的型別和陣列的名稱。陣列的型別被寫成type[],其中type是所包含元素的資料型別;[]是一些特殊的符號,表示這個變數包含一個陣列。陣列的大小不是其型別的一部分(這就是為什麼[]是空的)。陣列的名稱可以是任何您想要的名稱,只要它遵循前面在命名部分中討論的規則和約定即可。與其他型別的變數一樣,宣告實際上並不建立陣列;它只是告訴編譯器該變數將儲存指定型別的陣列。
類似地,您可以宣告其他型別的陣列:
byte[] anArrayOfBytes;
short[] anArrayOfShorts;
long[] anArrayOfLongs;
float[] anArrayOfFloats;
double[] anArrayOfDoubles;
boolean[] anArrayOfBooleans;
char[] anArrayOfChars;
String[] anArrayOfStrings;
複製程式碼
你也可以把括號放在陣列的名字後面:
// this form is discouraged
float anArrayOfFloats[];
複製程式碼
然而,慣例不鼓勵這種形式;方括號標識陣列型別,並應與型別指定一起出現。
建立、初始化和訪問陣列
建立陣列的一種方法是使用new操作符。下面ArrayDemo程式中的語句申請一個足夠容納10個整數的陣列,並將陣列分配給anArray變數:
// create an array of integers
anArray = new int[10];
複製程式碼
如果缺少該語句,編譯器將列印如下錯誤,編譯失敗:
ArrayDemo.java:4: Variable anArray may not have been initialized.
複製程式碼
接下來的幾行程式碼為陣列的每個元素賦值:
anArray[0] = 100; // initialize first element
anArray[1] = 200; // initialize second element
anArray[2] = 300; // and so forth
複製程式碼
每個陣列元素都由其數值索引訪問:
System.out.println("Element 1 at index 0: " + anArray[0]);
System.out.println("Element 2 at index 1: " + anArray[1]);
System.out.println("Element 3 at index 2: " + anArray[2]);
複製程式碼
或者,您可以使用快捷語法建立和初始化陣列:
int[] anArray = {
100, 200, 300,
400, 500, 600,
700, 800, 900, 1000
};
複製程式碼
陣列的長度由大括號中逗號分隔的值的數量決定。
您還可以使用兩個或多個括號(如String[][]名稱)宣告陣列的陣列(也稱為多維陣列)。因此,每個元素必須由相應數量的索引值訪問。
在Java程式語言中,多維陣列是一個陣列,其元件本身就是陣列。這與C或Fortran中的陣列不同。其結果是允許行長度變化,如下面的MultiDimArrayDemo程式所示:
class MultiDimArrayDemo {
public static void main(String[] args) {
String[][] names = {
{"Mr. ", "Mrs. ", "Ms. "},
{"Smith", "Jones"}
};
// Mr. Smith
System.out.println(names[0][0] + names[1][0]);
// Ms. Jones
System.out.println(names[0][2] + names[1][1]);
}
}
複製程式碼
程式輸出為:
Mr. Smith
Ms. Jones
複製程式碼
最後,您可以使用內建的length屬性來確定任何陣列的大小。以下程式碼將陣列的大小列印到標準輸出:
System.out.println(anArray.length);
複製程式碼
複製陣列
System類有一個arraycopy方法,您可以使用它來有效地將資料從一個陣列複製到另一個陣列:
public static void arraycopy(Object src, int srcPos,
Object dest, int destPos, int length)
複製程式碼
這兩個Object引數指定源陣列和要目的陣列,三個int引數指定源陣列中的起始位置、目標陣列中的起始位置和要複製的陣列元素的數量。
下面的程式ArrayCopyDemo宣告瞭一個char元素陣列,儲存單詞“decaffeated”。它使用System.arraycopy方法將源陣列的子串複製到第二個陣列中:
class ArrayCopyDemo {
public static void main(String[] args) {
char[] copyFrom = { 'd', 'e', 'c', 'a', 'f', 'f', 'e',
'i', 'n', 'a', 't', 'e', 'd' };
char[] copyTo = new char[7];
System.arraycopy(copyFrom, 2, copyTo, 0, 7);
System.out.println(new String(copyTo));
}
}
複製程式碼
這個程式的輸出是:
caffein
複製程式碼
操縱陣列
陣列是程式設計中使用的一個強大而有用的概念。Java SE提供了一些方法來執行與陣列相關的一些最常見的操作。例如,ArrayCopyDemo示例使用系統類的arraycopy方法,而不是手動遍歷源陣列的元素並將每個元素放入目標陣列。這是在後臺執行的,允許開發人員只使用一行程式碼來呼叫方法。
為了方便起見,Java SE在java.util.Arrays 類中提供了幾種執行陣列操作的方法(常見的任務:如複製、排序和搜尋)。例如,可以修改前面的示例,使用java.util.Arrays類的copyOfRange方法。正如您在ArrayCopyOfDemo示例中所看到的。不同之處在於,使用copyOfRange方法不需要在呼叫該方法之前建立目標陣列,因為該方法返回目標陣列。
class ArrayCopyOfDemo {
public static void main(String[] args) {
char[] copyFrom = {'d', 'e', 'c', 'a', 'f', 'f', 'e',
'i', 'n', 'a', 't', 'e', 'd'};
char[] copyTo = java.util.Arrays.copyOfRange(copyFrom, 2, 9);
System.out.println(new String(copyTo));
}
}
複製程式碼
正如您所看到的,儘管這個程式的程式碼更少,但是輸出卻是相同的(caffein)。注意,copyOfRange方法的第二個引數是要複製的範圍的初始索引,而第三個引數是要複製的範圍的最終索引。在本例中,要複製的範圍不包括索引9處的陣列元素('a')。
下面例舉java.util.Arrays類中提供的一些其他有用的方法:
- 在陣列中搜尋特定值,以獲得它所在的索引(binarySearch方法)
- 比較兩個陣列以確定它們是否相等(equals方法)
- 在每個索引處放置特定值(fill方法)
- 按升序排列陣列。可以使用sort方法順序執行,也可以使用Java SE 8中引入的parallelSort方法併發執行。在多處理器系統上對大陣列進行並行排序要比順序陣列排序快。