簡單資料型別的取值範圍
byte
:8 位,1 位元組,最大資料儲存量是 255,數值範圍是 −128 ~ 127。short
:16 位,2 位元組,最大資料儲存量是 65536,數值範圍是 −32768 ~ 32767。int
:32 位,4 位元組,最大資料儲存容量是 2^32 - 1,數值範圍是 −2^31 ~ 2^31 - 1。long
:64 位,8 位元組,最大資料儲存容量是 2^64 - 1 數值範圍是 −2^63 ~ 2^63 - 1。float
:32 位,4 位元組,數值範圍是 3.4e−45 ~ 1.4e38,直接賦值時必須在數字後加上f
或F
。double
:64 位,8 位元組,數值範圍在 4.9e−324 ~ 1.8e308,賦值時可以加 d 或 D,也可以不加。boolean
:只有true
和false
兩個取值。char
:16 位,2 位元組,儲存 Unicode 碼,用單引號'
賦值。
字元型
關係運算子和邏輯運算子
在 Java 程式設計中,關係運算子(Relational Operator)和邏輯運算子(Logical Operator)顯得十分重要。關係運算子定義值與值之間的相互關係,邏輯(logical)運算子定義可以用真值和假值連線在一起的方法。
關係運算子
在數學運算中有大於、小於、等於、不等於關係,在程式中可以使用關係運算子來表示上述關係。下圖中列出了 Java 中的關係運算子,通過這些關係運算子會產生一個結果,這個結果是一個布林值,即 true
或 false
。在 Java 中,任何型別的資料,都可以用 ==
比較是不是相等,用 !=
比較是否不相等,只有數字才能比較大小,關係運算的結果可以直接賦予布林變數。
邏輯運算子
布林邏輯運算子是最常見的邏輯運算子,用於對布林型運算元進行布林邏輯運算,Java 中的布林邏輯運算子如下圖示。
邏輯運算子與關係運算子運算後得到的結果一樣,都是布林型別的值。在 Java 程式設計中,&&
和 ||
布林邏輯運算子不總是對運算子右邊的表示式求值,如果使用邏輯與 &
和邏輯或 |
,則表示式的結果可以由運算子左邊的運算元單獨決定。通過下表,同學們可以瞭解常用邏輯運算子 &&
、||
、!
運算後的結果。
位邏輯運算子
在 Java 程式設計中,使用位邏輯運算子來操作二進位制資料。讀者必須注意,位邏輯運算子只能操作二進位制資料。如果用在其他進位制的資料中,需要先將其他進位制的資料轉換成二進位制資料。位邏輯運算子(Bitwise Operator)可以直接操作整數型別的位,這些整數型別包括 long
、int
、short
、char
和 byte
。Java 語言中位邏輯運算子的具體說明如下表所示。
因為位邏輯運算子能夠在整數範圍內對位操作,所以這樣的操作對一個值產生什麼效果是很重要的。具體來說,瞭解 Java 如何儲存整數值並且如何表示負數是非常有用的。下表中演示了運算元 A 和運算元 B 按位邏輯運算的結果。
移位運算子把數字的位向右或向左移動,產生一個新的數字。Java 的右移運算子有兩個,分別是 >>
和 >>>
。
>>
運算子:把第一個運算元的二進位制碼右移指定位數後,將左邊空出來的位以原來的符號位填充。即,如果第一個運算元原來是正數,則左邊補 0;如果第一個運算元是負數,則左邊補 1。>>>
:把第一個運算元的二進位制碼右移指定位數後,將左邊空出來的位以 0 填充。
條件運算子
條件運算子是一種特殊的運算子,也被稱為三目運算子。它與前面所講的運算子有很大不同,Java 中提供了一個三目運算子,其實這跟後面講解的 if
語句有相似之處。條件運算子的目的是決定把哪個值賦給前面的變數。在 Java 語言中使用條件運算子的語法格式如下所示。
變數 = (布林表示式) ? 為 true 時賦予的值 : 為 false 時賦予的值;
賦值運算子
注意:在 Java 中可以對賦值運算子進行擴充套件,其中最為常用的有如下擴充套件操作。
另外,在後面的學習中我們會接觸到
equals()
方法,此方法和賦值運算子==
的功能類似。要想理解兩者之間的區別,我們需要從變數說起。Java 中的變數分為兩類,一類是值型別,它儲存的是變數真正的值,比如基礎資料型別,值型別的變數儲存在記憶體的棧中;另一類是引用型別,它儲存的是物件的地址,與該地址對應的記憶體空間中儲存的才是我們需要的內容,比如字串和物件等,引用型別的變數儲存在記憶體中的堆中。賦值運算子==
比較的是值型別的變數,如果比較兩個引用型別的變數,比較的就是它們的引用地址。equals()
方法只能用來比較引用型別的變數,也就是比較引用的內容。
==
運算子比較的是左右兩邊的變數是否來自同一個記憶體地址。如果比較的是值型別(基礎資料型別,如int
和char
之類)的變數,由於值型別的變數儲存在棧裡面,當兩個變數有同一個值時,其實它們只用到同一個記憶體空間,所以比較的結果是true
。
eqluals()
方法是 Object 類的基本方法之一,所以每個類都有自己的equals()
方法,功能是比較兩個物件是否是同一個,通俗的理解就是比較這兩個物件的內容是否一樣。
賦值運算子是等號 =
,Java 中的賦值運算與其他計算機語言中的賦值運算一樣,起到賦值的作用。在 Java 中使用賦值運算子的格式如下所示。
+=
:對於x+=y
,等效於x=x+y
。-=
:對於x-=y
,等效於x=x−y
。*=
:對於x*=y
,等效於x=x*y
。/=
:對於x/=y
,等效於x=x/y
。%=
:對於x%=y
,等效於x=x%y
。&=
:對於x&=y
,等效於x=x&y
。|=
:對於x|=y
,等效於x=x|y
。^=
:對於x^=y
,等效於x=x^y
。<<=
:對於x<<=y
,等效於x=x<<y
。>>=
:對於x>>=y
,等效於x=x>>y
。>>>=
:對於x>>>=y
,等效於x=x>>>y
。
其中,變數 var
的型別必須與表示式 expression
的型別一致。 賦值運算子有一個有趣的屬性,它允許我們對一連串變數進行賦值。請看下面的程式碼。 在上述程式碼中,使用一條賦值語句將變數 x
、y
、z
都賦值為 100。這是由於 =
運算子表示右邊表示式的值,因此 z = 100
的值是 100,然後該值被賦給 y
,並依次被賦給 x
。使用字串賦值是給一組變數賦予同一個值的簡單辦法。在賦值時型別必須匹配,否則將會出現編譯錯誤。
運算子的優先順序
數學中的運算都是從左向右運算的,在 Java 中除了單目運算子、賦值運算子和三目運算子外,大部分運算子也是從左向右結合的。單目運算子、賦值運算子和三目運算子是從右向左結合的,也就是說,它們是從右向左運算的。乘法和加法是兩個可結合的運算,也就是說,這兩個運算子左右兩邊的操作符可以互換位置而不會影響結果。
運算子有不同的優先順序,所謂優先順序,就是在表示式運算中的運算順序。下表中列出了包括分隔符在內的所有運算子的優先順序,上一行中的運算子總是優先於下一行的。
字串的初始化
在 Java 程式中,使用關鍵字 new
來建立 String 例項,具體格式如下所示。
String a = new String();
上面這行程式碼建立了一個名為 a 的 String 類的例項,並把它賦給變數,但它此時是一個空的字串。接下來就為這個字串複製,賦值程式碼如下所示。
a = "I am a person.";
在 Java 程式中,我們將上述兩句程式碼合併,就可以產生一種簡單的字串表示方法。
String s = new String("I am a person.");
除了上面的表示方法,還有表示字串的如下一種形式。
String s = ("I am a person.");
String 類
在 Java 程式中可以使用 String 類來操作字串,在該類中有許多方法可以供程式設計師使用。
索引
在 Java 程式中,通過索引函式 charAt()
可以返回字串中指定索引的位置。讀者需要注意的是,這裡的索引數字從零開始,使用格式如下所示。
public char charAt(int index)
追加字串
追加字串函式 concat()
的功能是在字串的末尾新增字串,追加字串是一種比較常用的操作,具體語法格式如下所示。
public String concat(String s)
StringBuffer 類
StringBuffer 類是 Java 中另一個重要的操作字串的類,當需要對字串進行大量的修改時,使用 StringBuffer 類是最佳選擇。接下來將詳細講解 StringBuffer 類中的常用方法。
追加字元
在 StringBuffer 類中實現追加字元功能的方法的語法格式如下所示。
public synchronized StringBuffer append(char b)
插入字元
前面的字元追加方法總是在字串的末尾新增內容,倘若需要在字串中新增內容,就需要使用方法 insert()
,語法格式如下所示。
public synchronized StringBuffer insert(int offset, String s)
上述語法格式的含義是:將第 2 個引數的內容新增到第 1 個引數指定的位置,換句話說,第 1 個參數列示要插入的起始位置,第 2 個引數是需要插入的內容,可以是包括 String 在內的任何資料型別。
顛倒字元
字元顛倒方法能夠將字元顛倒,例如 "我是誰",顛倒過來就變成 "誰是我",很多時候需要顛倒字元。字元顛倒方法 reverse()
的語法格式如下所示。
public synchronized StringBuffer reverse()
自動型別轉換
如果系統支援把某種基本型別的值直接賦給另一種基本型別的變數,這種方式被稱為自動型別轉換。當把一個取值範圍小的數值或變數直接賦給另一個取值範圍大的變數時,系統可以進行自動型別轉換。
Java 中所有數值型變數之間可以進行型別轉換,取值範圍小的可以向取值範圍大的進行自動型別轉換。就好比有兩瓶水,當把小瓶裡的水倒入大瓶時不會有任何問題。Java 支援自動型別轉換的型別如下圖所示。
在上圖所示的型別轉換圖中,箭頭左邊的數值可以轉換為箭頭右邊的數值。當對任何基本型別的值和字串進行連線運算時,基本型別的值將自動轉換為字串型別,儘管字串型別不再是基本型別,而是引用型別。因此,如果希望把基本型別的值轉換為對應的字串,可以對基本型別的值和一個空字串進行連線。
Java 11 新特性:新增的 String 函式
在新發布的 JDK 11 中,新增了 6 個字串函式。下面介紹各個字串函式。
-
String.repeat(int)
函式
String.repeat(int)
的功能是根據 int 引數的值重複 String。 -
String.lines()
函式
String.lines()
的功能是返回從該字串中提取的行,由行終止符分隔。行要麼是零個或多個字元的序列,後面跟著一個行結束符;要麼是一個或多個字元的序列,後面是字串的結尾。一行不包括行終止符。在 Java 程式中,使用函式String.lines()
返回的流包含該字串中出現的行的順序。 -
String.strip()
函式
String.strip()
的功能是返回一個字串,該字串的值為該字串,其中所有前導和尾部空白均被刪除。如果該 String 物件表示空字串,或者如果該字串中的所有程式碼點是空白的,則返回一個空字串。否則,返回該字串的子字串,該字串從第一個不是空白的程式碼點開始,直到最後一個不是空白的程式碼點,幷包括最後一個不是空白的程式碼點。在 Java 程式中,開發者可以使用此函式去除字串開頭和結尾的空白。 -
String.stripLeading()
函式
String.stripLeading()
的功能是返回一個字串,其值為該字串,並且刪除字串前面的所有空白。如果該 String 物件表示空字串,或者如果該字串中的所有程式碼點是空白的,則返回空字串。 -
String.stripTrailing()
函式
String.stripTrailing()
的功能是返回一個字串,其值為該字串,並且刪除字串後面的所有空白。如果該 String 物件表示空字串,或者如果該字串中的所有程式碼點是空白的,則返回空字串。 -
String.isBlank()
函式
String.isBlank()
的功能是判斷字串是否為空或僅包含空格。如果字串為空或僅包含空格則返回true
;否則,返回false
。
定義常量時的注意事項
在 Java 語言中,主要利用 final
關鍵字(在 Java 類中靈活使用 static
關鍵字)來進行 Java 常量的定義。當常量被設定後,一般情況下就不允許再進行更改。在定義常量時,需要注意如下 3 點。
- 在定義 Java 常量的時候,就需要對常量進行初始化。也就是說,必須在宣告常量時就對它進行初始化。跟區域性變數或類成員變數不同,在定義一個常量的時候,進行初始化之後,在應用程式中就無法再次對這個常量進行賦值。如果強行賦值的話,編譯器會彈出錯誤資訊,並拒絕接受這一新值。
- 需要注意
final
關鍵字的使用範圍。final
關鍵字不僅可以用來修飾基本資料型別的常量,還可以用來修飾物件的引用或方法,比如陣列就是物件引用。為此,可以使用final
關鍵字定義一個常量的陣列。這是 Java 語言中的一大特色。一個陣列物件一旦被final
關鍵字設定為常量陣列之後,它就只能恆定地指向一個陣列物件,無法將其指向另一個物件,也無法更改陣列中的值。 - 需要注意常量的命名規則。在定義變數或常量時,不同的語言,都有自己的一套編碼規則。這主要是為了提高程式碼的共享程度與易讀性。在 Java 中定義常量時,也有自己的一套規則。比如在給常量取名時,一般都用大寫字母。在 Java 語言中,區分大小寫字母。之所以採用大寫字母,主要是為了跟變數進行區分。雖然說給常量取名時採用小寫字母,也不會有語法上的錯誤,但是為了在編寫程式碼時能夠一目瞭然地判斷變數與常量,最好還是能夠將常量設定為大寫字母。另外,在常量中,往往通過下劃線來分隔不同的字元,而不像物件名或類名那樣,通過首字母大寫的方式來進行分隔。這些規則雖然不是強制性的,但是為了提高程式碼的友好性,方便開發團隊中的其他成員閱讀,這些規則還是需要遵守的。 總之,Java 開發人員需要注意,被定義為
final
的常量需要採用大寫字母命名,並且中間最好使用下劃線作為分隔符來連線多個單詞。定義為final
的資料不論是常量、物件引用還是陣列,在主函式中都不可以改變,否則會被編輯器拒絕並提示錯誤資訊。
char 型別中單引號的意義
char
型別使用單引號括起來,而字串使用雙引號括起來。關於 String 類的具體用法以及對應的各個方法,讀者可以參考查閱 API 文件中的資訊。其實 Java 語言中的單引號、雙引號和反斜線都有特殊的用途,如果在一個字串中包含這些特殊字元,應該使用轉義字元。
例如希望在 Java 程式中表示絕對路徑 c:\daima
,但這種寫法得不到我們期望的結果,因為 Java 會把反斜線當成轉義字元,所以應該寫成 c:\\daima
的形式。只有同時寫兩個反斜線,Java 才會把第一個反斜線當成轉義字元,與後一個反斜線組成真正的反斜線。
正無窮和負無窮的問題
Java 還提供了 3 個特殊的浮點數值——正無窮大、負無窮大和非數,用於表示溢位和出錯。
例如,使用一個正浮點數除以 0 將得到正無窮大,使用一個負浮點數除以 0 將得到負無窮大,用 0.0 除以 0.0 或對一個負數開方將得到一個非數。
正無窮大通過 Double 或 Float 的 POSITIVE_INFINITY
表示,負無窮大通過 Double 或 Float 的 NEGATIVE_INFINITY
表示,非數通過 Double 或 Float 的 NaN
表示。
請注意,只有用浮點數除以 0 才可以得到正無窮大或負無窮大,因為 Java 語言會自動把和浮點數運算的 0(整數)當成 0.0(浮點數)來處理。如果用一個整數除以 0,則會丟擲 “ArithmeticException:/by zero”(除以 0 異常)
。
移位運算子的限制
Java 移位運算子只能用於整型,不能用於浮點型。也就是說,>>、>>>和<<這 3 個移位運算子並不適合所有的數值型別,它們只適合對 byte
、short
、char
、int
和 long
等整型數進行運算。除此之外,進行移位運算時還有如下規則:
- 對於低於
int
型別(如byte
、short
和char
)的運算元來說,總是先自動型別轉換為int
型別後再移位。 - 對於
int
型別的整數移位,例如a >> b
,當b > 32
時,系統先用b
對 32 求餘(因為int
型別只有 32 位),得到的結果才是真正移位的位數。例如,a >> 33
和a >> l
的結果完全一樣,而a >> 32
的結果和a
相同。 - 對
long
型別的整數移位時,例如a >> b
,當b > 64
時,總是先用b
對 64 求餘(因為long
型別是 64 位),得到的結果才是真正移位的位數。 當進行位移運算時,只要被移位的二進位制碼沒有發生有效位的數字丟失現象(對於正數而言,通常指被移出的位全部都是 0),不難發現左移 n 位就相當於乘以 2n,右移則相當於除以 2n。這裡存在一個問題:左移時,左邊捨棄的位通常是無效的,但右移時,右邊捨棄的位常常是有效的,因此通過左移和右移更容易看出這種執行結果,並且位移運算不會改變運算元本身,只是得到一個新的運算結果,原來的運算元本身是不會改變的。
if 語句
if 語句由保留字 if
、條件語句和位於後面的語句組成。條件語句通常是一個布林表示式,結果為 true
和 false
。如果條件為 true
,則執行語句並繼續處理其後的下一條語句;如果條件為 false
,則跳過語句並繼續處理緊跟整個 if 語句的下一條語句。例如在下圖中,當條件(condition)為 true
時,執行 statement1 語句;當條件為 false
時,執行 statement2 語句。
if 語句的語法格式如下所示。
if (條件表示式)
語法說明:if
是該語句中的關鍵字,後續緊跟一對小括號,這對小括號任何時候都不能省略。小括號的內部是具體的條件,語法上要求條件表示式的結果為 boolean
型別。後續為功能程式碼,也就是當條件成立時執行的程式碼。在書寫程式時,一般為了直觀地表達包含關係,功能程式碼需要縮排。
例如下面的演示程式碼。
int a = 10; //定義 int 型變數 a 的初始值是 10
if (a >= 0)
System.out.println("a 是正數"); //a 大於或等於 0 時的輸出內容
if ( a % 2 == 0)
System.out.println("a 是偶數"); //a 能夠整除 2 時的輸出內容
在上述演示程式碼中,第一個條件判斷變數 a
的值是否大於或等於零,如果該條件成立,輸出 "a 是正數";第二個條件判斷變數 a
是否為偶數,如果成立,也輸出 "a 是偶數"。
再看下面的程式碼的執行流程。
int m = 20; //定義 int 型變數 m 的初始值是 20
if ( m > 20) //如果變數 m 的值大於 20
m += 20; //將 m 的值加上 20
System.out.println(m); //輸出 m 的值
按照前面的語法格式說明,只有 m += 20
這行程式碼屬於功能程式碼,而後續的輸出語句和前面的條件形成順序結構,所以該程式執行以後輸出的結果為 20。當條件成立時,如果需要執行的語句有多句,可以使用語句塊來進行表述,具體語法格式如下所示。
if (條件表示式) {
功能程式碼塊;
}
這種語法格式中,使用功能程式碼塊來代替前面的功能程式碼,這樣可以在程式碼塊內部書寫任意多行程式碼,而且也使整個程式的邏輯比較清楚,所以在實際的程式碼編寫中推薦使用這種方式。
if語句的延伸
在第一種 if 語句中,大家可以看到,並不對條件不符合的內容進行處理。因為這是不允許的,所以 Java 引入了另外一種條件語句 if…else
,基本語法格式如下所示。
if (condition) // 設定條件 condition
statement1; // 如果條件 condition 成立,執行 statement1 這一行程式碼
else // 如果條件 condition 不成立
statement2; // 執行 statement2 這一行程式碼
if…else
語句的執行流程如下圖所示。
有多個條件判斷的 if 語句
if 語句實際上是一種功能十分強大的條件語句,可以對多種情況進行判斷。可以判斷多個條件的語句是 if-else-if
,語法格式如下所示。
if (condition1)
statement1;
else if (condition2)
statement2;
else
statement3;
上述語法格式的執行流程如下。
- 判斷第一個條件 condition1,當為
true
時執行 statement1,並且程式執行結束。當 condition1 為false
時,繼續執行後面的程式碼。 - 當 condition1 為
false
時,接下來先判斷 condition2 的值,當 condition2 為true
時執行 statement2,並且程式執行結束。當 condition2 為false
時,執行後面的 statement3。也就是說,當前面的兩個條件 condition1 和 condition2 都不成立(為false
)時,才會執行 statement3。
if-else-if
的執行流程如下圖所示。
在 Java 語句中,if…else
可以巢狀無限次。可以說,只要遇到值為 true
的條件,就會執行對應的語句,然後結束整個程式的執行。
switch 語句的形式
switch 語句能夠對條件進行多次判斷,具體語法格式如下所示。
switch(整數選擇因子) {
case 整數值 1 : 語句; break;
case 整數值 2 : 語句; break;
case 整數值 3 : 語句; break;
case 整數值 4 : 語句; break;
case 整數值 5 : 語句; break;
//..
case 整數值 n : 語句; break;
default: 語句;
}
其中,整數選擇因子
必須是 byte
、short
、int
和 char
型別,每個整數必須是與 整數選擇因子
型別相容的一個常量,而且不能重複。整數選擇因子
是一個特殊的表示式,能產生整數。switch 能將整數選擇因子的結果與每個整數做比較。發現相符的,就執行對應的語句(簡單或複合語句)。沒有發現相符的,就執行 default
語句。
在上面的定義中,大家會注意到每個 case
均以一個 break
結尾。這樣可使執行流程跳轉至 switch 主體的末尾。這是構建 switch 語句的一種傳統方式,但 break
是可選的。若省略 break
,將會繼續執行後面的 case
語句的程式碼,直到遇到 break
為止。儘管通常不想出現這種情況,但對有經驗的程式設計師來說,也許能夠善加利用。注意,最後的 default
語句沒有 break
,因為執行流程已到達 break
的跳轉目的地。當然,如果考慮到程式設計風格方面的原因,完全可以在 default
語句的末尾放置一個 break
,儘管它並沒有任何實際用處。
switch 語句的執行流程如下圖所示。
無 break 的情況
多次出現了 break
語句,其實在 switch 語句中可以沒有 break
這個關鍵字。一般來說,當 switch 遇到一些 break
關鍵字時,程式會自動結束 switch 語句。如果把 switch 語句中的 break
關鍵字去掉了,程式將繼續向下執行,直到整個 switch 語句結束。
case 語句後沒有執行語句
當 case 語句後沒有執行語句時,即使條件為 true
,也會忽略掉不執行。
default 可以不在結尾
通過前面的學習,很多初學者可能會誤認為 default
一定位於 switch
的結尾。其實不然,它可以位於 switch
中的任意位置
for 迴圈
在 Java 程式中,for 語句是最為常見的一種迴圈語句,for 迴圈是一種功能強大且形式靈活的結構,下面對它進行講解。
書寫格式
for 語句是一種十分常見的迴圈語句,語法格式如下所示。
for(initialization;condition;iteration){
statements;
}
從上面的語法格式可以看出,for 迴圈語句由如下 4 部分組成。
initialization
:初始化操作,通常用於初始化迴圈變數。condition
:迴圈條件,是一個布林表示式,用於判斷迴圈是否持續。iteration
:迴圈迭代器,用於迭代迴圈變數。statements
:要迴圈執行的語句(可以有多條語句)。
上述每一部分間都用分號分隔,如果只有一條語句需要重複執行,大括號就沒有必要有了。
在 Java 程式中,for 迴圈的執行過程如下。
- 當迴圈啟動時,先執行初始化操作,通常這裡會設定一個用於主導迴圈的迴圈變數。重要的是要理解初始化表示式僅被執行一次。
- 計算迴圈條件。
condition
必須是一個布林表示式,它通常會對迴圈變數與目標值做比較。如果這個布林表示式為真,則繼續執行迴圈體statements
;如果為假,則迴圈終止。 - 執行迴圈迭代器,這部分通常是用於遞增或遞減迴圈變數的一個表示式,以便接下來重新計算迴圈條件,判斷是否繼續迴圈。
while 迴圈語句
在 Java 程式裡,除了 for 語句以外,while 語句也是十分常見的迴圈語句,其特點和 for 語句十分類似。while 迴圈語句的最大特點,就是不知道迴圈多少次。在 Java 程式中,當不知道某個語句塊或語句需要重複執行多少次時,通過使用 while 語句可以實現這樣的迴圈功能。當迴圈條件為真時,while 語句重複執行一條語句或某個語句塊。while 語句的基本使用格式如下所示。
while (condition) // condition 表示式是迴圈條件,其結果是一個布林值
{
statements;
}
while 語句的執行流程如下圖所示。
do...while 迴圈語句
許多軟體程式中會存在這種情況:當條件為假時也需要執行語句一次。初學者可以這麼理解,在執行一次迴圈後才測試迴圈的條件表示式。在 Java 語言中,我們可以使用 do…while
語句實現上述迴圈。
書寫格式
在 Java 語言中,do…while
迴圈語句的特點是至少會執行一次迴圈體,因為條件表示式在迴圈的最後。do…while
迴圈語句的使用格式如下所示。
do{
statements;
}
while (condition) // condition 表示迴圈條件,是一個布林值
在上述格式中,do…while 語句先執行 statement
一次,然後判斷迴圈條件。如果結果為真,迴圈繼續;如果為假,迴圈結束。
do…while
迴圈語句的執行流程如下圖所示。
break 語句的應用
在本小節前面的內容中,我們事實上已經接觸過 break 語句,瞭解到它在 switch 語句裡可以終止一條語句。其實除這個功能外,break 還能實現其他功能,例如退出迴圈。break 語句根據使用者使用的不同,可以分為無標號退出迴圈和有標號退出迴圈兩種。
無標號退出迴圈是指直接退出迴圈,當在迴圈語句中遇到 break 語句時,迴圈會立即終止,迴圈體外面的語句也將會重新開始執行。
return 語句的使用
在 Java 程式中,使用 return 語句可以返回一個方法的值,並把控制權交給呼叫它的語句。return 語句的語法格式如下所示。
return [expression];
expression
表示表示式,是可選引數,表示要返回的值,它的資料型別必須同方法宣告中返回值的型別一致,這可以通過強制型別轉換實現。
在編寫 Java 程式時,return 語句如果被放在方法的最後,它將用於退出當前的程式,並返回一個值。如果把單獨的 return 語句放在一個方法的中間,會出現編譯錯誤。如果非要把 return 語句放在方法的中間,可以使用條件語句 if,然後將 return 語句放在這個方法的中間,用於實現將程式中未執行的全部語句退出。
continue 語句
在 Java 語言中,continue 語句不如前面幾種跳轉語句應用得多,其作用是強制當前這輪迭代提前返回,也就是讓迴圈繼續執行,但不執行當前迭代中 continue 語句生效之後的語句。
使用 for 迴圈的技巧
控制 for 迴圈的變數經常只用於該迴圈,而不用在程式的其他地方。在這種情況下,可以在迴圈的初始化部分宣告變數。當我們在 for 迴圈內宣告變數時,必須記住重要的一點:該變數的作用域在 for 迴圈執行後就結束了(因此,該變數的作用域僅限於 for 迴圈內)。由於迴圈控制變數不會在程式的其他地方使用,因此大多數程式設計師都在 for 迴圈中宣告它。
另外,初學者經常以為,只要在 for 後面的括號中控制了迴圈迭代語句,就萬無一失了,其實不是這樣的。請看下面的程式碼。
public class TestForError {
public static void main(String[] args) {
// 迴圈的初始化條件、迴圈條件、迴圈迭代語句都在下面一行
for (int count = 0; count < 10; count++) {
System.out.println(count);
// 再次修改了迴圈變數
count *= 0.1;
}
System.out.println("迴圈結束!");
}
}
在上述程式碼中,我們在迴圈體內修改了 count
變數的值,並且把這個變數的值乘以 0.1,這會導致 count
的值永遠都不超過 10,所以上述程式是一個死迴圈。
其實在使用 for 迴圈時,還可以把初始化條件定義在迴圈體之外,把迴圈迭代語句放在迴圈體內。把 for 迴圈的初始化語句放在迴圈之前定義還有一個好處,那就是可以擴大初始化語句中定義的變數的作用域。在 for 迴圈裡定義的變數,其作用域僅在該迴圈內有效。for 迴圈終止以後,這些變數將不可被訪問。
跳轉語句的選擇技巧
由此可見,continue
的功能和 break
有點類似,區別在於 continue
只是中止當前迭代,接著開始下一次迭代;而 break
則完全終止迴圈。我們可以將 continue
的作用理解為:略過當前迭代中剩下的語句,重新開始新一輪的迭代。
宣告一維陣列
陣列本質上就是某類元素的集合,每個元素在陣列中都擁有對應的索引值,只需要指定索引值就可以取出對應的資料。在 Java 中宣告一維陣列的格式如下所示。
int[] arrar;
也可以用下面的格式。
int array[];
雖然這兩種格式的形式不同,但含義是一樣的,各個引數的具體說明如下。
int
:陣列型別。array
:陣列名稱。[]
:一維陣列的內容通過這個符號括起來。
除上面宣告的整型陣列外,還可以宣告多種資料型別的陣列,例如下面的程式碼。
boolean[] array; // 宣告布林型陣列
float[] array; // 宣告浮點型陣列
double[] array; // 宣告雙精度型陣列
建立一維陣列
建立陣列實質上就是為陣列申請相應的儲存空間,陣列的建立需要用大括號 {}
括起來,然後將一組相同型別的資料放在儲存空間裡,Java 編譯器負責管理儲存空間的分配。建立一維陣列的方法十分簡單,具體格式如下所示。
int[] a = {1,2,3,5,8,9,15};
上述程式碼建立了一個名為 a
的整型陣列,但是為了訪問陣列中的特定元素,應指定陣列元素的位置序號,也就是索引(又稱下標),一維陣列的內部結構如下圖所示。
上面這個陣列的名稱是 a
,方括號的數值表示陣列元素的索引,這個序號通常也被稱為下標。這樣就可以很清楚地表示每一個陣列元素,陣列 a
的第一個值就用 a[0]
表示,第 2 個值就用 a[1]
表示,以此類推。
初始化一維陣列
在 Java 程式裡,一定要將陣列看作一個物件,它的資料型別和前面的基本資料型別相同。很多時候我們需要對陣列進行初始化處理,在初始化的時候需要規定陣列的大小。當然,也可以初始化陣列中的每一個元素。下面的程式碼演示了 3 種初始化一維陣列的方法。
int[] a = new int[8]; // 使用 new 關鍵字建立一個含有 8 個元素的 int 型別的陣列 a
int[] a = new int{1,2,3,4,5,6,7,8}; // 初始化並設定陣列 a 中的 8 個陣列元素
int[] a = {1,2,3,4}; // 初始化並設定陣列 a 中的 4 個陣列元素
對上面程式碼的具體說明如下所示。
int
:陣列型別。a
:陣列名稱。new
:物件初始化語句。
在初始化陣列的時候,當使用關鍵字 new
建立陣列後,一定要明白它只是一個引用,直到將值賦給引用,開始進行初始化操作後才算真正結束。在上面 3 種初始化陣列的方法中,同學們可以根據自己的習慣選擇一種初始化方法。
宣告二維陣列
在前面已經學習了宣告一維陣列的知識,宣告二維陣列也十分簡單,因為它與宣告一維陣列的方法十分相似。很多程式設計師習慣將二維陣列看作一種特殊的一維陣列,其中的每個元素又是一個陣列。宣告二維陣列的語法格式如下所示。
float A[][]; //float 型別的二維陣列 A
char B[][]; //char 型別的二維陣列 B
int C[][]; //int 型別的二維陣列 C
上述程式碼中各個引數的具體說明如下所示。
float
、char
和int
:表示陣列和型別。A
、B
和C
:表示陣列的名稱。[][]
:二維陣列的內容通過這個符號括起來。
建立二維陣列的過程,實際上就是在計算機上申請一塊儲存空間的過程,例如下面是建立二維陣列的程式碼。
int A[][]={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
};
上述程式碼建立了一個二維陣列,A
是陣列名,實質上這個二維陣列相當於一個 3 行 4 列的矩陣。當需要獲取二維陣列中的值時,可以使用索引來顯示,具體格式如下所示。
array[i - 1][j - 1];
上述程式碼中各個引數的具體說明如下所示。
i
:陣列的行數。j
:陣列的列數。
下面以一個二維陣列為例,看一下 3 行 4 列的陣列的內部結構,如下圖所示。
初始化二維陣列
初始化二維陣列的方法非常簡單,可以看作是由多個一維陣列構成,也是使用下面的語法格式實現的。
array = new int[][]{
{第一個陣列第一個元素的值,第一個陣列第二個元素的值,第一個陣列第三個元素的值},
{第二個陣列第一個元素的值,第二個陣列第二個元素的值,第二個陣列第三個元素的值},
};
或者使用 new
關鍵字。
array = new int[3][4]; // 建立一個 3 行 4 列的陣列。
使用 new
關鍵字初始化的陣列,其所有元素的值都預設為該陣列的型別的預設值。這裡是 int
型別陣列,則預設值為 0.
上述程式碼中各個引數的具體說明如下所示。
array
:陣列名稱。new
:物件例項化語句。int
:陣列型別。
宣告三維陣列
宣告三維陣列的方法十分簡單,與宣告一維、二維陣列的方法相似,具體格式如下所示。
float a[][][];
char b[][][];
上述程式碼中各個引數的具體說明如下所示。
float
和char
:陣列型別。a
和b
:陣列名稱。
建立三維陣列的方法
在 Java 程式中,建立三維陣列的方法也十分簡單,例如下面的程式碼。
int [][][] a = new int[2][2][3];
初始化三維陣列
初始化三維陣列的方法十分簡單,例如可以用下面的程式碼初始化一個三維陣列。
int[][][]a={
//初始化三維陣列
{{1,2,3}, {4,5,6}}
{{7,8,9},{10,11,12}}
}
通過上述程式碼,可以定義並且初始化三維陣列中元素的值。
複製陣列
複製陣列是指複製陣列中的數值,在 Java 中可以使用 System
的方法 arraycopy()
實現陣列複製功能。方法 arraycopy()
有兩種語法格式,其中第一種語法格式如下所示。
System.arraycopy(arrayA,indexA,arrayB,indexB,a.length);
arrayA
:來源陣列名稱。indexA
:來源陣列的起始位置。arryaB
:目的陣列名稱。indexB
:要從來源陣列複製的元素個數。
上述陣列複製方法 arraycopy()
有一定侷限,可以考慮使用方法 arraycopy()
的第二種格式,使用第二種格式可以複製陣列內的任何元素。第二種語法格式如下所示。
System.arraycopy(arrayA,2,arrayB,3,3);
arrayA
:來源陣列名稱。2
:來源陣列從起始位置開始的第2
個元素。arrayB
:目的陣列名稱。3
:目的陣列從其實位置開始的第3
個元素。3
:從來源陣列的第 2 個元素開始複製3
個元素。
比較陣列
比較陣列就是檢查兩個陣列是否相等。如果相等,則返回布林值 true
;如果不相等,則返回布林值 false
。在 Java 中可以使用方法 equals()
比較陣列是否相等,具體格式如下所示。
Arrays.equals(arrayA,arrayB);
arrayA
:待比較陣列的名稱。arrayB
:待比較陣列的名稱。
如果兩個陣列相等,就會返回 true
;否則返回 false
。
排序陣列
排序陣列是指對陣列內的元素進行排序,在 Java 中可以使用方法 sort()
實現排序功能,並且排序規則是預設的。方法 sort()
的語法格式如下所示。
Arrays.sort(array);
引數 array
是待排序陣列的名稱。
搜尋陣列中的元素
在 Java 中可以使用方法 binarySearch()
搜尋陣列中的某個元素,語法格式如下所示。
int i = binarySearch(a,"abcde");
a
:要搜尋的陣列名稱。abcde
:需要在陣列中查詢的內容。
填充陣列
在 Java 程式設計裡,可以使用 fill()
方法向陣列中填充元素。fill()
方法的功能十分有限,只能使用同一個數值進行填充。使用 fill()
方法的語法格式如下所示。
int a[] = new int[10];
Arrays.fill(array,11);
其中,引數 a
是將要填充的陣列的名稱,上述格式的含義是將數值 11 填充到陣列 a
中。
遍歷陣列
在 Java 語言中,foreach 語句是從 Java 1.5 開始出現的新特徵之一,在遍歷陣列和遍歷集合方面,foreach 為開發人員提供極大的方便。從實質上說,foreach 語句是 for 語句的特殊簡化版本,雖然 foreach 語句並不能完全取代 for 語句,但是任何 foreach 語句都可以改寫為 for 語句版本。
foreach 並不是一個關鍵字,習慣上將這種特殊的 for 語句稱為 foreach
語句。從英文字面意思理解,foreach 就是「為每一個」的意思。foreach 語句的語法格式如下所示。
for(type 變數 x : 遍歷物件 obj){
引用了 x 的 Java 語句;
}
其中,type
是陣列元素或集合元素的型別,變數 x
是一個形參,foreach 迴圈自動將陣列元素、集合元素依次賦給變數 x
。
動態初始化陣列的規則
在執行動態初始化時,程式設計師只需要指定陣列的長度即可,即為每個陣列元素指定所需的記憶體空間,系統將負責為這些陣列元素分配初始值。在指定初始值時,系統按如下規則分配初始值。
- 陣列元素的型別是基本型別中的整數型別(
byte
、short
、int
和long
),陣列元素的值是 0。 - 陣列元素的型別是基本型別中的浮點型別(
float
、double
),陣列元素的值是 0.0。 - 陣列元素的型別是基本型別中的字元型別(
char
),陣列元素的值是 '\u0000'。 - 陣列元素的型別是基本型別中的布林型別(
boolean
),陣列元素的值是false
。 - 陣列元素的型別是引用型別(類、介面和陣列),陣列元素的值是
null
。
引用型別
如果記憶體中的一個物件沒有任何引用的話,就說明這個物件已經不再被使用了,從而可以被垃圾回收。不過由於垃圾回收器的執行時間不確定,可被垃圾回收的物件實際被回收的時間是不確定的。對於一個物件來說,只要有引用存在,它就會一直存在於記憶體中。如果這樣的物件越來越多,超出 JVM 中的記憶體總數,JVM 就會丟擲 OutOfMemory 錯誤。雖然垃圾回收器的具體執行是由 JVM 控制的,但是開發人員仍然可以在一定程度上與垃圾回收器進行互動,目的在於更好地幫助垃圾回收器管理好應用的記憶體。這種互動方式就是從 JDK 1.2 開始引入的 java.lang.ref
包。
強引用
在一般的 Java 程式中,見到最多的就是強引用(strong reference)。例如 Date date = new Date()
,其中的 date 就是一個物件的強引用。物件的強引用可以在程式中到處傳遞。很多情況下,會同時有多個引用指向同一個物件。強引用的存在限制了物件在記憶體中的存活時間。假如物件 A 中包含物件 B 的一個強引用,那麼一般情況下,物件 B 的存活時間就不會短於物件 A。如果物件 A 沒有顯式地把物件 B 的引用設為 null
的話,那麼只有當物件 A 被垃圾回收之後,物件 B 才不再有引用指向它,才可能獲得被垃圾回收的機會。 除了強引用之外,java.lang.ref
包還提供了對一個物件的另一種不同的引用方式。JVM 的垃圾回收器對於不同型別的引用有不同的處理方式。
軟引用
軟引用(soft reference)在強度上弱於強引用,通過類 SoftReference 來表示。它的作用是告訴垃圾回收器,程式中的哪些物件不那麼重要,當記憶體不足的時候是可以被暫時回收的。當 JVM 中的記憶體不足時,垃圾回收器會釋放那些只被軟引用指向的物件。如果全部釋放完這些物件之後,記憶體仍不足,則會丟擲 OutOfMemory 錯誤。軟引用非常適合於建立快取。當系統記憶體不足時候,快取中的內容是可以被釋放的。比如考慮一個影像編輯器的程式。該程式會把影像檔案的全部內容讀取到記憶體中,以方便進行處理。使用者也可以同時開啟多個檔案。當同時開啟的檔案過多時,就可能造成記憶體不足。如果使用軟引用來指向影像檔案的內容,垃圾回收器就可以在必要的時候回收這些記憶體。
陣列的初始化
在 Java 中不存在只分配記憶體空間而不賦初始值的情況。因為一旦為陣列的每個陣列元素分配記憶體空間,記憶體空間裡儲存的內容就是陣列元素的值,即使記憶體空間儲存的內容為空,「空」也是值,用 null
表示。不管以哪一種方式初始化陣列,只要為陣列元素分配了記憶體空間,陣列元素就有了初始值。獲取初始值的方式有兩種:一種由系統自動分配;另一種由程式設計師指定。
Java 物件導向的幾個核心概念
類
只要是一門物件導向的程式語言(例如 C++、C#等),那麼就一定有類這個概念。類是指將相同屬性的東西放在一起,類是一個模板,能夠描述一類物件的行為和狀態。請看下面兩個例子。
- 在現實生活中,可以將人看成一個類,這類稱為人類。
- 如果某個男孩想找一個物件(女朋友),那麼所有的女孩都可能是這個男孩的女朋友,所有的女孩就是一「類」。
Java 中的每一個源程式至少都會有一個類,在本書前面介紹的例項中,用關鍵字 class
定義的都是類。Java 是物件導向的程式設計語言,類是物件導向的重要內容,我們可以把類當成一種自定義資料型別,可以使用類來定義變數,這種型別的變數統稱為引用型變數。也就是說,所有類都引用資料型別。
物件
物件是實際存在某個類中的每一個個體,因而也稱為例項(instance)。物件的抽象是類,類的具體化就是物件,也可以說類的例項是物件。類用來描述一系列物件,類會概述每個物件包括的資料和行為特徵。因此,我們可以把類理解成某種概念、定義,它規定了某類物件所共同具有的資料和行為特徵。
接著前面的兩個例子。
- 人這個「類」的範圍實在是太籠統了,人類裡面的秦始皇是一個具體的人,是一個客觀存在的人,我們就將秦始皇稱為一個物件。
- 想找物件(女朋友)的男孩已經找到目標了,他的女朋友名叫「大美女」。注意,假設叫這個名字的女孩人類中僅有這一個,此時名叫「大美女」的這個女孩就是一個物件。
在物件導向的程式中,首先要將一個物件看作一個類,假定人是物件,任何一個人都是一個物件,類只是一個大概念而已,而類中的物件是具體的,它們具有自己的屬性(例如漂亮、身材好)和方法(例如會作詩、會程式設計)。
Java 中的物件
通過上面的講解可知,我們的身邊有很多物件,例如車、狗、人等。所有這些物件都有自己的狀態和行為。拿一條狗來說,它的狀態有名字、品種、顏色;行為有叫、搖尾巴和跑。
現實物件和軟體物件之間十分相似。軟體物件也有狀態和行為,軟體物件的狀態就是屬性,行為通過方法來體現。在軟體開發過程中,方法操作物件內部狀態的改變,物件的相互呼叫也是通過方法來完成的。
注意:類和物件有以下區別。
類描述客觀世界裡某一類事物的共同特徵,而物件則是類的具體化,Java 程式使用類的構造器來建立該類的物件。
類是建立物件的模板和藍圖,是一組類似物件的共同抽象定義。類是一個抽象的概念,不是一個具體的事物。
物件是類的例項化結果,是真實的存在,代表現實世界的某一事物。
屬性
屬性有時也稱為欄位,用於定義該類或該類的例項所包含的資料。在 Java 程式中,屬性通常用來描述某個物件的具體特徵,是靜態的。例如姚明(物件)的身高為 2.6m,小白(物件)的毛髮是棕色的,二郎神(物件)額頭上有隻眼睛等,都是屬性。
方法
方法用於定義該類或該類例項的行為特徵或功能實現。 每個物件都有自己的行為或者使用它們的方法,比如說一隻狗(物件)會跑會叫等。我們把這些行為稱為方法,它是動態的,可以使用這些方法來操作一個物件。
類的成員
屬性和方法都被稱為所在類的成員,因為它們是構成一個類的主要部分,如果沒有這兩樣東西,那麼類的定義也就沒有內容了。
定義類
在 Java 語言中,定義類的語法格式如下所示。
[修飾符] class 類名
{
零到多個構造器的定義…
零到多個屬性…
零到多個方法…
}
在上面定義類的語法格式中,修飾符可以是 public
、final
或 static
,也可以完全省略它們,類名只要是一個合法的識別符號即可,但這僅滿足了 Java 的語法要求;如果從程式的可讀性方面來看,那麼 Java 類名必須由一個或多個有意義的單詞構成,其中每個單詞的首字母大寫,其他字母全部小寫,單詞與單詞之間不要使用任何分隔符。
在定義一個類時,它可以包含 3 個最常見的成員,它們分別是構造器、屬性和方法。這 3 個成員可以定義零個或多個。如果 3 個成員都只定義了零個,則說明定義了一個空類,這沒有太大的實際意義。類中各個成員之間的定義順序沒有任何影響,各個成員之間可以相互呼叫。需要注意的是,一個類的 static
方法需要通過例項化其所在類來訪問該類的非 static
成員。
下面的程式碼定義一個名為 person 的類,這是具有一定特性(人類)的一類事物,而 Tom 則是類的一個物件例項,其程式碼如下所示。
class person {
int age; //人具有 age 屬性
String name; //人具有 name 屬性
void speak(){ //人具有 speak 方法
System.out.println("My name is"+name);
}
}
public static void main(String args[]){
//類及類屬性和方法的使用
person Tom=new person(); //建立一個物件
Tom.age=27; //物件的 age 屬性是 27
Tom.name="TOM"; //物件的 name 屬性是 TOM
Tom.speak(); //物件的方法是 speak
}
一個類需要具備對應的屬性和方法,其中屬性用於描述物件,而方法可讓物件實現某個具體功能。例如在上述例項程式碼中,類、物件、屬性和方法的具體說明如下所示。
- 類:程式碼中的
person
就是一個類,它代表人類。 - 物件:程式碼中的 Tom(注意,不是 TOM)就是一個物件,它代表一個具體的人。
- 屬性:程式碼中有兩個屬性:
age
和name
,其中屬性 age 表示物件 Tom 這個人的年齡是 27,屬性 name 表示物件 Tom 這個人的名字是 TOM。 - 方法:程式碼中的
speak()
是一個方法,它表示物件 Tom 這個人具有說話這一技能。
定義屬性
在 Java 中定義屬性的語法格式如下所示。
[修飾符] 屬性型別 屬性名 [=預設值];
上述格式的具體說明如下所示。
- 修飾符:修飾符可以省略,也可以是
public
、protected
、private
、static
、final
,其中public
、protected
、private
最多隻能出現一個,它可以與static
、final
組合起來修飾屬性。 - 屬性型別:屬性型別可以是 Java 語言允許的任何資料型別,它包括基本型別和現在介紹的複合型別。
- 屬性名:屬性名只要是一個合法的識別符號即可,但這只是從語法角度來說的。如果從程式可讀性角度來看,那麼作者建議屬性名應該由一個或多個有意義的單詞構成,第一個單詞的首字母小寫,後面每個單詞的首字母大寫,其他字母全部小寫,單詞與單詞之間不需使用任何分隔符。
- 預設值:在定義屬性時可以定義一個由使用者指定的預設值,如果使用者沒有指定預設值,則該屬性的預設值就是其所屬型別的預設值。
定義方法
在 Java 中定義方法的語法格式如下所示。
[修飾符] 方法返回值型別 方法名 ([形參列表。..]){
由零條或多條可執行語句組成的方法體;
}
-
修飾符:它可以省略,也可以是
public
、protected
、private
、static
、final
、abstract
,其中public
、protected
、private
這 3 個最多隻能出現一個;abstract
和final
最多隻能出現一個,它們可以與static
組合起來共同修飾方法。 -
方法返回值型別:返回值型別可以是 Java 語言允許的任何資料型別,這包括基本型別、複合型別與 _
void
型別_。如果宣告瞭方法的返回值型別,則方法體內就必須有一個有效的return
語句,該語句可以是一個變數或一個表示式,這個變數或者表示式的型別必須與該方法宣告的返回值型別相匹配。當然,如果一個方法中沒有返回值,那麼我們也可以將返回值宣告成void
型別。 -
方法名:方法名的命名規則與屬性的命名規則基本相同,我們建議方法名以英文的動詞開頭。
形參列表:形參列表用於定義該方法可以接受的引數,形參列表由零到多組“引數型別形參名”組合而成,多組引數之間以英文逗號(,
)隔開,形參型別和形參名之間以英文空格隔開。一旦在定義方法時指定了形參列表,則在呼叫該方法時必須傳入對應的引數值——誰呼叫方法,誰負責為形參賦值。
在方法體中的多條可執行性語句之間有著嚴格的執行順序,在方法體前面的語句總是先執行,在方法體後面的語句總是後執行。
同學們實際上在前面的章節中已經多次接觸過方法,例如 public static void main(String args[]){...}
這段程式碼中就使用了方法 main()
,在下面的程式碼中也定義了幾個方法。
public class test_class {
//定義一個無返回值的方法
public void cheng(){ //方法名是 cheng
System.out.println("我已經長大了"); //方法 cheng 的功能是輸出文字"我已經長大了"
//…
}
//定義一個有返回值的方法
public int Da(){ //方法名是 Da
int a=100; //定義變數 a,設定初始值是 100
return a; //方法 Da 的功能返回變數 a 的值
}
定義構造器
構造器是一個建立物件時自動呼叫的特殊方法,目的是執行初始化操作。構造器的名稱應該與類的名稱一致。當 Java 程式在建立一個物件時,系統會預設初始化該物件的屬性,基本型別的屬性值為 0(數值型別)、false
(布林型別),把所有的引用型別設定為 null
。構造器是類建立物件的根本途徑,如果一個類沒有構造器,那麼這個類通常將無法建立例項。為此 Java 語言提供構造器機制,系統會為該類提供一個預設的構造器。一旦為類提供了構造器,那麼系統將不再為該類提供構造器。
定義構造器的語法格式與定義方法的語法格式非常相似,在呼叫時,我們可以通過關鍵字 new
來呼叫構造器,從而返回該類的例項。下面,我們先來看一下定義構造器的語法格式。
[修飾符] 構造器名 ([形參列表。..]);
{
由零條或多條可執行語句組成的構造器執行體;
}
上述格式的具體說明如下所示。
-
修飾符:修飾符可以省略,也可以是
public
、protected
、private
其中之一。 -
構造器名:構造器名必須和類名相同。
-
形參列表:這和定義方法中的形參列表的格式完全相同。
與一般方法不同的是,構造器不能定義返回值的型別,也不能使用 void
定義構造器沒有返回值。如果為構造器定義了返回值的型別,或使用 void
定義構造器沒有返回值,那麼在編譯時就不會出錯,但 Java 會把它當成一般方法來處理。下面的程式碼演示了使用構造器的過程。
public class Person { //定義類 Person
public String name; //定義屬性 name
public int age; //定義屬性 age
public Person(String name, int age) { //構造器函式 Person()
this.name = name; //開始自定義構造器,新增 name 屬性
this.age = age; //繼續自定義構造器,新增 age 屬性
}
public static void main(String[] args) {
// 使用自定義的構造器建立物件(構造器是建立物件的重要途徑)
Person p = new Person("小明", 12); //建立物件 p,名字是“小明”,年齡是 12
System.out.println(p.age); //輸出物件 p 的年齡
System.out.println(p.name); //輸出物件 p 的名字
}
}
public 修飾符
在 Java 程式中,如果將屬性和方法定義為 public
型別,那麼此屬性和方法所在的類和及其子類、同一個包中的類、不同包中的類都可以訪問這些屬性和方法。
private 修飾符
在 Java 程式裡,如果將屬性和方法定義為 private 型別,那麼該屬性和方法只能在自己的類中訪問,在其他類中不能訪問。
protected 修飾符
在編寫 Java 應用程式時,如果使用修飾符 protected
修飾屬性和方法,那麼該屬性和方法只能在自己的子類和類中訪問。
其他修飾符
前面幾節講解的修飾符是在 Java 中最常用的修飾符。除了這幾個修飾符外,在 Java 程式中還有許多其他的修飾符,具體說明如下所示。
- 預設修飾符:如果沒有指定訪問控制修飾符,則表示使用預設修飾符,這時變數和方法只能在自己的類及同一個包下的類中訪問。
static
:由static
修飾的變數稱為靜態變數,由static
修飾的方法稱為靜態方法。final
:由final
修飾的變數在程式執行過程中最多賦值一次,所以經常定義它為常量。transient
:它只能修飾非靜態變數,當序列化物件時,由transient
修飾的變數不會序列化到目標檔案。當物件從序列化檔案中重構物件時(反序列化過程),不會恢復由transient
欄位修飾的變數。volatile
:和transient
一樣,它只能修飾變數。這個關鍵字的作用就是告訴編譯器,只要是被此關鍵字修飾的變數都是易變、不穩定的。abstract
:由abstract
修飾的成員稱為抽象方法,用abstract
修飾的類可以擴充套件(增加子類),且不能直接例項化。用abstract
修飾的方法不能在宣告它的類中實現,且必須在某個子類中重寫。synchronized
:該修飾符只能應用於方法,不能修飾類和變數。此關鍵字用於在多執行緒訪問程式中共享資源時實現順序同步訪問資源。
方法與函式的關係
不論是從定義方法的語法上來看,還是從方法的功能上來看,都不難發現方法和函式之間的相似性。儘管實際上方法是由傳統函式發展而來的,但方法與傳統的函式有著顯著不同。在結構化程式語言裡,函式是老大,整個軟體由許多的函式組成。在物件導向的程式語言裡,類才是老大,整個系統由許多的類組成。因此在 Java 語言裡,方法不能獨立存在,方法必須屬於類或物件。在 Java 中如果需要定義一個方法,則只能在類體內定義,不能獨立定義一個方法。一旦將一個方法定義在某個類體內,並且這個方法使用 static
來修飾,則這個方法屬於這個類;否則,這個方法屬於這個類的物件。
在 Java 語言中,型別是靜態的,即我們當定義一個類之後,只要不再重新編譯這個類檔案,那麼該類和該類物件所擁有的方法是固定的,且永遠都不會改變。因為 Java 中的方法不能獨立存在,它必須屬於一個類或者一個物件,所以方法也不能像函式那樣獨立執行。在執行方法時必須使用類或物件作為呼叫者,即所有方法都必須使用 類。方法
或 物件。方法
的格式來呼叫。此處可能會產生一個問題,當同一個類的不同方法之間相互呼叫時,不可以直接呼叫嗎?在此需要明確一個原則:當在同一個類的一個方法中呼叫另外一個方法時,如果被調方法是普通方法,則預設使用 this
作為呼叫者;如果被調方法是靜態方法,則預設使用類作為呼叫者。儘管從表面上看起來某些方法可以獨立執行,但實際上它還是使用 this
或者類來作為呼叫者。
永遠不要把方法當成獨立存在的實體,正如現實世界由類和物件組成,而方法只能作為類和物件的附屬,Java 語言裡的方法也是一樣。講到此處,可以總結 Java 裡的方法有如下主要屬性。
- 方法不能獨立定義,只能在類體裡定義。
- 從邏輯意義上來看,方法要麼屬於該類本身,要麼屬於該類的一個物件。
- 永遠不能獨立執行方法,執行方法必須使用類或物件作為呼叫者。
長度可變的方法
自 JDK 1.5 之後,在 Java 中可以定義形參長度可變的引數,從而允許為方法指定數量不確定的形參。如果在定義方法時,在最後一個形參型別後增加 3 點 ...
,則表明該形參可以接受多個引數值,它們當成陣列傳入。在下面的例項程式碼中定義了一個形參長度可變的方法。
不使用 void 關鍵字構造方法名
前面小節節已經講解了構造器的知識,在此提醒同學們,構造方法沒有返回型別,不用 void
修飾,只有一個 public
之類的修飾符而已。
遞迴方法
如果一個方法在其方法體內呼叫自身,那麼這稱為方法遞迴。方法遞迴包含一種隱式的迴圈,它會重複執行某段程式碼,但這種重複執行無須迴圈控制。
使用 this
在講解變數時,曾經將變數分為區域性變數和全域性變數兩種。當區域性變數和全域性變數的資料型別和名稱都相同時,全域性變數將會被隱藏,不能使用。為了解決這個問題,Java 規定可以使用關鍵字 this
去訪問全域性變數。使用 this
的語法格式如下所示。
this. 成員變數名
this. 成員方法名()
建立和使用物件
在 Java 程式中,一般通過關鍵字 new
來建立物件,計算機會自動為物件分配空間,然後訪問變數和方法。對於不同的物件,變數也是不同的,方法由物件呼叫。
使用靜態變數和靜態方法
在前面已經講過,只要使用修飾符 static
關鍵字在變數和方法前面,那麼這個變數和方法就稱作靜態變數和靜態方法。靜態變數和靜態方法的訪問只需要類名,通過運算 .
即可以實現對變數的訪問和對方法的呼叫。
抽象類和抽象方法的基礎
抽象方法和抽象類必須使用 abstract
修飾符來定義,有抽象方法的類只能定義成抽象類,類裡可以沒有抽象方法。所謂抽象類是指只宣告方法的存在而不去實現它的類,抽象類不能例項化,也就是不能建立物件。在定義抽象類時,要在關鍵字 class
前面加上關鍵字 abstract
,其具體格式如下所示。
abstract class 類名{
類體
}
- 抽象類必須使用
abstract
修飾符來修飾,抽象方法也必須使用abstract
修飾符來修飾,方法不能有方法體。 - 抽象類不能例項化,無法使用關鍵字 new 來呼叫抽象類的構造器建立抽象類的例項。
- 抽象類裡不能包含抽象方法,這個抽象類也不能建立例項。
- 抽象類可以包含屬性、方法(普通方法和抽象方法都可以)、構造器、初始化塊、內部類、列舉類 6 種。抽象類的構造器不能建立例項,主要用於被其子類呼叫。
- 含有抽象方法的類(包括直接定義一個抽象方法;繼承一個抽象父類,但沒有完全實現父類包含的抽象方法;實現一個介面,但沒有完全實現介面包含的抽象方法)只能定義成抽象類。
由此可見,抽象類同樣能包含與普通類相同的成員。只是抽象類不能建立例項,普通類不能包含抽象方法,而抽象類可以包含抽象方法。
抽象方法和空方法體的方法不是同一個概念。例如 public abstract void test()
是一個抽象方法,它根本沒有方法體,即方法定義後沒有一對花括號。然而,但 public void test(){}
是一個普通方法,它已經定義了方法體,只是這個方法體為空而已,即它的方法體什麼也不做,因此這個方法不能使用 abstract
來修飾。
抽象類必須有一個抽象方法
建立抽象類最大的要求是必須有一個抽象方法
抽象類的作用
抽象類不能建立例項,它只能當成父類來繼承。從語義的角度看,抽象類是從多個具體類中抽象出來的父類,它具有更高層次的抽象。從多個具有相同特徵的類中抽象出一個抽象類,以這個抽象類作為其子類的模板,從而避免子類設計的隨意性。
抽象類體現的是一種模板模式的設計,抽象類為多個子類的通用模板,子類在抽象類的基礎上進行擴充套件、改造,但總體上子類會大致保留抽象類的行為方式。如果編寫一個抽象父類,父類提供了多個子類的通用方法,並把一個或多個方法留給其子類實現,那麼這就是一種模板模式,模板模式也是最常見、最簡單的設計模式之一。接下來看一個模板模式的例項程式碼,在它演示的抽象父類中,父類的普通方法依賴於一個抽象方法,而抽象方法則推遲到子類中實現。
軟體包的定義
定義軟體包的方法十分簡單,只需要在 Java 源程式的第一句中新增一段程式碼即可。在 Java 中定義包的格式如下所示。
package 包名;
package
宣告瞭多程式中的類屬於哪個包,在一個包中可以包含多個程式,在 Java 程式中還可以建立多層次的包,具體格式如下所示。
package 包名1[.包名2[.包名3]];
新建 UseFirst.java
檔案,編寫以下程式碼。
package China.CQ; //載入一個包,其中父目錄函式“China”的子目錄是"CQ"
public class UseFirst { //定義類
public static void main(String[] args){
System.out.println("這個程式定義了一個包");
}
}
執行上述程式碼後將會建立一個多層次的包。由此可見,定義軟體包的過程實際上就是新建一個資料夾,將編譯後的檔案放在新建資料夾中。定義軟體包實際上完成的就是這個事情。
在程式裡插入軟體包
在 Java 程式中插入軟體包的方法十分簡單,只需使用 import 語句插入所需的類即可。在上一節實驗中,已經對插入軟體包這個概念進行了初次的接觸。在 Java 程式中插入軟體包的格式如下所示。
import 包名1.[包名2[.包名3]].(類名1*);
上述格式中,各個引數的具體說明如下所示。
- 包名 1:一級包。
- 包名 2:二級包。
- 類名:是需要匯入的類名。也可以使用
*
號,表示將匯入這個包中的所有類。
掌握 this 的好處
關鍵字 this
最大的作用就讓類中的一個方法訪問該類的另一個方法或屬性。其實 this
關鍵字是很容易理解的,接下來作者舉兩個例子進行對比,相信大家看後對 this
的知識就完全掌握了。
第一段程式碼演示了沒有使用 this
的情況,具體程式碼如下所示。
class A {
private int aa, bb; // 宣告兩個 int 型別變數
public int returnData(int x, int y) { // 一個返回整數的方法
aa = x;
bb = y;
return aa + bb;
}
}
在第二段程式碼中使用 this
,具體程式碼如下所示。
在下面的程式碼中需要重點注意在 MyDate newDay=new MyDate(this);
語句中 this
的作用。
class MyDate {
private int day;
private int month;
private int year; // 定義 3 個成員變數
public MyDate(int day, int month, int year) {
this.day = day;
this.month = month;
this.year = year;
} // 構造方法
public MyDate(MyDate date) {
this.day = date.day;
this.month = date.month;
this.year = date.year; // 將引數 Date 類中的成員變數賦給 MyDate 類
} // 構造方法
public int getDay() {
return day;
}// 方法
public void setDay(int day) {
this.day = day; // 引數 day 賦給此類中的 ddy
}
public MyDate addDays(int moreDay) {
MyDate newDay = new MyDate(this);
newDay.day = newDay.day + moreDay;
return newDay; // 返回整個類
}
public void print() {
System.out.println("My Date: " + year + "-" + month + "-" + day);
}
}
public class TestMyDate {
public static void main(String args[]) {
MyDate myBirth = new MyDate(19, 11, 1987); // 利用建構函式初始化
MyDate next = myBirth.addDays(7);
// addDays() 的返回值是類,將其返回值賦給變數 next
next.print();
}
}
事實上,前兩個類從本質說是相同的,而為什麼在第二個類中使用 this
關鍵字呢?注意,第二個類中的方法 returnData (int aa,int bb)
的形式引數分別為 aa
和 bb
,這剛好和 private int aa,bb;
裡的變數名是一樣的。現在問題來了:究竟如何在 returnData
的方法體中區別形式引數 aa
和全域性變數 aa
呢?兩個 bb
也是如此嗎?這就是引入 this
關鍵字的用處所在了。this.aa
表示的是全域性變數 aa
,而沒有加 this
的 aa
表示形式引數 aa
,bb
也是如此。
在此建議,在程式設計中不能過多使用 this
關鍵字。這從上面的程式碼中也可以看出,當相同的變數名加上 this
關鍵字過多時,有時會讓人分不清。這時可以按照第三段程式碼進行修改,避免使用 this
關鍵字。
class A {
private int aa, bb; // 宣告兩個 int 型別變數
public int returnData(int aa1, int bb1) {
aa = aa1; // 在 aa 後面加上數字 1 加以區分,其他以此類推
bb = bb1;
return aa + bb;
}
}
由此可以看出,儘管上面的第一段程式碼、第二段程式碼、第三段程式碼都是一樣的,但是第三段程式碼既避免了使用 this
關鍵字,又避免了第一段程式碼中引數意思不明確的缺點,所以建議使用與第三段程式碼一樣的方法。
推出抽象方法的原因
當編寫一個類時,常常會為該類定義一些方法,這些方法用以描述該類的行為方式,這時這些方法都有具體的方法體。在某些情況下,某個父類只是知道其子類應該包含什麼樣的方法,但卻無法準確知道這些子類如何實現這些方法,例如定義一個 Shape 類,這個類應該提供一個計算周長的方法 scalPerimeter()
,不同 Shape 子類對周長的計算方法是不一樣的,也就是說 Shape 類無法準確知道其子類計算周長的方法。
很多人以為,既然 Shape 不知道如何實現 scalPerimeter()
方法,那麼就乾脆不要管它了。其實這是不正確的作法,假設有一個 Shape 引用變數,該變數實際上會引用到 Shape 子類的例項,那麼這個 Shape 變數就無法呼叫 scalPerimeter()
方法,必須將其強制型別轉換為其子類型別才可呼叫 scalPerimeter()
方法,這就降低了 Shape 的靈活性。 究竟如何既能在 Shape 類中包含 scalPerimeter()
方法,又無須提供其方法實現呢?Java 中的做法是使用抽象方法滿足該要求。抽象方法是隻有方法簽名,並沒有方法實現的方法。
static 修飾的作用
使用 static
修飾的方法屬於這個類,或者說屬於該類的所有例項所共有。使用 static
修飾的方法不但可以使用類作為呼叫者來呼叫,也可以使用物件作為呼叫者來呼叫。值得指出的是,因為使用 static
修飾的方法還是屬於這個類的,所以使用該類的任何物件來呼叫這個方法都將會得到相同的執行結果,這與使用類作為呼叫者的執行結果完全相同。
不使用 static
修飾的方法則屬於該類的物件,它不屬於這個類。因此不使用 static
修飾的方法只能用物件作為呼叫者來呼叫,不能使用類作為呼叫者來呼叫。使用不同物件作為呼叫者來呼叫同一個普通方法,可能會得到不同的結果。
陣列內是同一型別的資料
Java 是一門是物件導向的程式語言,能很好地支援類與類之間的繼承關係,這樣可能產生一個陣列裡可以存放多種資料型別的假象。例如有一個水果陣列,要求每個陣列元素都是水果,實際上陣列元素既可以是蘋果,也可以是香蕉,但這個陣列中的陣列元素型別還是唯一的,只能是水果型別。
另外,由於陣列是一種引用型別的變數,因此使用它定義一個變數時,僅表示定義了一個引用變數(也就是定義了一個指標),這個引用變數還未指向任何有效的記憶體,因此定義陣列時不能指定陣列的長度。由於定義陣列僅是定義了一個引用變數,並未指向任何有效的記憶體空間,所以還沒有記憶體空間來儲存陣列元素,這時這個陣列也不能使用,只有陣列初始化後才可以使用。
繼承的定義
類的繼承是指從已經定義的類中派生出一個新類,是指我們在定義一個新類時,可以基於另外一個已存在的類,從已存在的類中繼承有用的功能(例如屬性和方法)。這時已存在的類便被稱為父類,而這個新類則稱為子類。在繼承關係中,父類一般具有所有子類的共性特徵,而子類則會為自己增加一些更具個性的方法。類的繼承具有傳遞性,即子類還可以繼續派生子類,因此,位於上層的類在概念上就更抽象,而位於下層的類在概念上就更具體。
父類和子類
繼承是物件導向的機制,利用繼承可以建立一個公共類,這個類具有多個專案的共同屬性。我們可再用一些具體的類來繼承該類,同時加上自己特有的屬性。在 Java 中實現繼承的方法十分簡單,具體格式如下所示。
<修飾符> class <子類名> extends <父類名> {
[<成員變數定義>]…
[<方法定義>]…
}
我們通常所說的子類一般指的是某父類的直接子類,而父類也可稱為該子類的直接超類。如果存在多層繼承關係,比如,類 A 繼承的是類 B,則它們之間的關係就必須符合下面的要求。
- 若存在另外一個類 C,類 C 是類 B 的子類,類 A 是類 C 的子類,那麼可以判斷出類 A 是類 B 的子類。
- 在 Java 程式中,一個類只能有一個父類,也就是說在 extends 關鍵字前只能有一個類,它不支援多重繼承。
呼叫父類的構造方法
構造方法是 Java 類中比較重要的方法,一個子類可以訪問構造方法,這在前面已經使用過多次。Java 語言呼叫父類構造方法的具體格式如下所示。
super(引數);
訪問父類的屬性和方法
在 Java 程式中,一個類的子類可以訪問父類的屬性和方法,具體語法格式如下所示。
super.[方法和全域性變數];