一、問題背景
博主在準備應聘的筆試、面試時,再次採用了多年以來的Java工具書《Java瘋狂講義》,並決定在每章詳細複習後都要在博客中寫下詳細的閱讀筆記。
二、閱讀筆記與知識拓展——《Java瘋狂講義》第3章(數據類型和運算符)
Java是強類型(Strong Type)語言
。強類型語言定義:
- 所有的變量必須先聲明後使用。
- 指定類型的變量只能接受類型與之匹配的值。
這意味着每個變量和每個表達式都有一個在編譯時就確定的類型。類型限制了一個變量能被賦的值, 限制了一個表達式可以產生的值,限制了在這些值上可以進行的操作, 並確定了這些操作的含義。
強類型語言可在編譯中發現源代碼的錯誤,從而保證程序的健壯性。
Java有豐富的基本數據類型,基本數據類型包括數值類型、布爾類型。數值類型包括整形、字符型和浮點型,所有數值類型之間可以進行類型轉換,此類型轉換包括自動類型轉換和強制類型轉換。
Java還有豐富的運算符,如算術運算符、位運算符、比較運算符、邏輯運算符。
3.1註釋
程序註釋用於說明某段代碼的作用。Java註釋有:單行註釋、多行註釋、文檔註釋。
- 單行註釋:以雙斜槓
//
作爲開頭標識要被註釋的內容。單行註釋只能註釋一行內容,用在註釋信息內容少的地方。
- 多行註釋:要被註釋的內容包含在
/*
和*/
之間,能註釋很多行的內容。爲了可讀性比較好,一般首行和尾行不寫註釋信息。
- 文檔註釋:要被註釋的內容包含在
/**
和*/
之間,也能註釋多行內容,一般用在類、方法和變量上面,用來描述其作用。註釋後,鼠標放在類和變量上面會自動顯示出我們註釋的內容。
文檔註釋可以通過 Javadoc 命令把文檔註釋中的內容生成文檔,並輸出到 HTML 文件中,方便記錄程序信息。還可以包含一個或多個 @ 標籤,每個 @ 標籤都在新的一行開始。關於 Javadoc 的具體標籤和使用可閱讀學習《Javadoc入門教程》一節。
多行註釋可以嵌套單行註釋,但是不能嵌套多行註釋和文檔註釋。
文檔註釋能嵌套單行註釋,不能嵌套多行註釋和文檔註釋,一般首行和尾行也不寫註釋信息。
3.2標識符和關鍵字
3.2.1分隔符(Seperator/Delimiter)
Java的分隔符爲分號;
、花括號{}
、方括號[]
、圓括號()
、空格、圓點
.
。它們都具有特殊的分隔作用,故被稱爲分隔符。
分隔符的主流英文單詞是seperator
,但分隔符有時也用少見的delimiter
來表示。
separate /ˈseprət/ adj分開的;vi/vt分開;區分
separater /ˈsɛpəˌreɪtə/ n分離器;[計]分隔符
delimit /diːˈlɪmɪt/ vt給…定界限
delimiter /dɪ’lɪmɪtɚ/ n[計]分隔符
3.2.1.1分號(semicolon
)
Java 語言裏對語句的分隔不是使用回車來完成的, Java 語言採用分號;
作爲語句的分隔, 因此 每個 Java 語句必須使用分號作爲結尾。 Java 程序允許一行書寫多個語句,每個語句之間以分號隔開即可;一個語句也可以跨多行,只要在最後結束的地方使用分號結束即可。
例如,下面語句都是合法的 Java 語句。
int age = 25 ; String name = " 李剛 " ;
String hello = "你好 ! " +
"Java";
值得指出的是, Java 語句可以跨越多行書寫, 但一個字符串、變量名不能跨越多行。例如, 下面的 Java 語句是錯誤的。
//字符串不能跨越多行
String a = "dddddd
xxxxxxx";
//變量名不能跨越多行
String na
me = "李剛";
雖然 Java 語法允許一行書寫多個語旬,但從程序可讀性角度來看,應該避免在一行書寫多個語句。
分號的英文單詞是semicolon
,其中semi-
前綴表示半
的意思,則semi-colon
直譯表示半冒號,意譯爲分號。
colon /ˈkəʊlən/ n冒號;[身體部位]結腸
semicolon /sɛmɪˈkəʊlən/ n分號
3.2.1.2花括號/大括號(braces
/curly brackets
)
花括號的作用就是定義一個代碼塊, 一個代碼塊指的就是{
和}
所包含的一段代碼, 代碼塊在邏輯上是一個整體。 對 Java 語言而言, 類定義部分必須放在一個代碼塊裏,方法體部分也必須放在 一個代碼塊裏。 除此之外,條件語句中的條件執行體和循環語句中的循環體通常也放在代碼塊裏。花括號一般是成對出現的, 有一個{
則必然有一個}
,反之亦然。
花括號/大括號的英文是braces
/curly brackets
。而所有括號的左右形式則用open
和closed
區別,如左大括號{
的英文是open brace
,而右大括號}
的英文是`closed brace"。
brace /breɪs/ n.大括號;牙箍;[腿部]支架; vt支撐;繃緊(肩或膝蓋)
embrace /ɪmˈbreɪs/ vi/vt/n擁抱;接納(變化,思想);包括
bracket /ˈbrækɪt/ n(收入,年齡,價格)範圍;支架;小括號/中括號/大括號其中的一個
3.2.1.3方括號/中括號(square brackets
)
方括號的主要作用是用於訪問數組元素, 方括號通常緊跟數組變量名,而方括號裏指定希望訪問的數組元素的索引 。
例如,如下代碼:
// 下面代碼試圖爲名爲 a 的數組的第四個元素賦值
a [3] = 3;
方括號/中括號的英文是square brackets
。
3.2.1.4圓括號/小括號(round brackets
/parentheses
)
圓括號是一個功能非常豐富的分隔符: 定義方法時必須使用圓括號來包含所有的形式參數聲明, 調用方法時也必須使用圓括號來傳入實參值;不僅如此,圓括號還可以將表達式中某個部分括成一個整體, 保證這個部分優先計算;除此之外,圓括號還可以作爲強制類型轉換的運算符。
圓括號/小括號的英文是round brackets
/parentheses
。
parentheses /pəˈrɛnθəsɪz/ n圓括號
3.2.1.5空格(blank
)
Java 語言使用空格分隔一條語句的不同部分。 Java 語言是一門格式自由的語言,所以空格幾乎可以出現在 Java 程序的任何地方,也可以出現任意多個空格,但不要使用空格把一個變量名隔開成兩個, 這將導致程序出錯。
Java 語言中的空格(blank
)包含空格符(Space
)、製表符 (Tab
) 和回車 (Enter
) 等。
除此之外, Java 源程序還會使用空格來合理縮進 Java 代碼,從而提供更好的可讀性。
3.2.1.6圓點(dot
)
圓點(.)通常用作類/對象和它的成員(包括成員變量、方法和內部類)之間的分隔符,表明調用某個類或某個實例的指定成員。
3.2.2 Java9的標識符(identifier
)規則
標識符的英文是identifier
。
identity /aɪˈdɛntɪtɪ/ n身份
identical /aɪˈdentɪkl/ adj完全相同的
identify /aɪˈdentɪfaɪ/ vt識別;vi(+with)認同;vt(sth with sth)認爲…之間密切相關
identification /aɪˌdentɪfɪˈkeɪʃn/ n識別;密切關聯,同情,認同
identifier /aɪˈdentəfaɪər/ n標識符
標識符就是用於給程序中變量、類、方法命名的符號。 Java 語言的標識符必須以字母、下劃線(_
)、 美元符($
)開頭,後面可以跟任意數目的字母、 數字、下劃線( )和美元符($
)。 此處的字母並不侷限於 26 個英文字母,甚至可以包含中文字符、日文字符等。
由於 Java 9 支持 Unicode 8.0 字符集,因此 Java 的標識符可以使用 Unicode 8.0 所能表示的多種語言的字符。Java 語言是區分大小寫的,因此 abc 和 Abc 是兩個不同的標識符。
Java 9 規定:不允許使用單獨的下劃線(_
)作爲標識符。 也就是說,下劃線必須與其他字符組合在一起才能作爲標識符。
使用標識符時,需要注意如下規則:
- 標識符可以由字母、數字、下劃線(
_
)和美元符($)組成,其中數字不能打頭。 - 標識符不能是 Java 關鍵字和保留字,但可以包含關鍵字和保留字。
- 標識符不能包含空格。
- 標識符只能包含美元符(
$
),不能包含@、#等其他特殊字符。
3.2.3Java關鍵字(keyword
)
Java語言中有一些具有特殊用途的單詞被稱爲關鍵字(keyword
), 當定義標識符時,不要讓標識符和關鍵字相同, 否則將引起錯誤。 例如,下面代碼將無法通過編譯。
// 試圖定義一個名爲 boolean的變量,但 boolean 是關鍵字,不能作爲標識符
int boolean;
Java 的所有關鍵字都是小寫的,但true
、 false
和 null
都不是 Java 關鍵字,它們是Java的字面常量(literal
)。(什麼是Java的字面常量?)
Java 一共包含 50 個關鍵字,如下表所示。
abstract | assert | boolean | break | byte |
case | catch | char | class | const |
continue | default | do | double | else |
enum | extends | final | finally | float |
for | goto | if | implements | import |
instanceof | int | interface | long | native |
new | package | private | protected | public |
return | strictfp | short | static | super |
switch | synchronized | this | throw | throws |
transient | try | void | volatile | while |
上面的 50 個關鍵字中, enum 是從 Java 5 新增的關鍵字,用於定義一個枚舉。 而 goto 和 const 這兩個關鍵字也被稱爲保留字 (reserved word) ,保留字的意思是, Java 現在還未使用這兩個關鍵字,但可能在未來的 Java 版本中使用這兩個關鍵字;不僅如此,Java 還提供了三個特殊的字面常量(literal
): true
、 false
和 null
;Java 語言的標識符也不能使用這三個特殊的字面常量。
值得注意的是:Java中的3個特殊字面常量null
, false
, true
在使用時必須是全小寫的,如果全大寫和半小寫半大寫是錯誤的。
上述文段涉及的重要英文單詞如下:
boolean /ˈbuːliən/ n布爾值
null /nɔl/或者/nʌl/ n空值
literal /ˈlɪtərəl/ adj字面意思上的;n[計]字面常量
enum /`iˌnəm/ n枚舉(enumeration /ɪˌnjuːməˈreɪʃn/ 的縮寫)
enumerate /ɪˈnjuːməˌreɪt/ vt列舉(一系列事物)
3.3數據類型分類
Java是強類型(Strong Type)語言
。強類型語言定義:
- 所有的變量必須先聲明後使用。
- 指定類型的變量只能接受類型與之匹配的值。
這意味着每個變量和每個表達式都有一個在編譯時就確定的類型。類型限制了一個變量能被賦的值, 限制了一個表達式可以產生的值,限制了在這些值上可以進行的操作, 並確定了這些操作的含義。
強類型語言可在編譯中發現源代碼的錯誤,從而保證程序的健壯性。
聲明變量的語法非常簡單,只要指定變量的類型和變量名即可,如下所示:
type varName[ = 初始值 ] ;
上面語法中,聲明變量時既可指定初始值,也可不指定初始值(上述語法的中括號[]
括起來的內容代表可選填項)。隨着變量的作用範圍的不同(變量有成員變量和局部變量之分,具體請參考本書 5.3 節內容),變量還可能使用其他修飾符。但不管是哪種變量,聲明變量至少需要指定變量類型和變量名兩個部分。聲明變量時的變量類型可以是 Java 語言支持的所有類型。
Java 語言支持的類型分爲兩類:基本類型 (Primitive Type)
和引用類型 (Reference Type)
。
基本類型
包括boolean 類型
和數值類型
。數值類型有整數類型和浮點類型。整數類型包括 byte、short、int、 long、 char, 浮點類型包括 float 和 double。
char代表字符型,實際上字符型也是一種整數類型,相當於無符號整數類型。
引用類型
包括類、接口和數組類型
,還有一種特殊的null類型
。 所謂引用數據類型就是對一個對象的引用,對象包括實例和數組兩種。實際上,引用類型變量就是一個指針,只是 Java 語言裏不再使用指針這個說法。
空類型 (null type) 就是 null 值的類型, 這種類型沒有名稱。因爲 null 類型沒有名稱,所以不可能聲明一個 null 類型的變量或者轉換到 null 類型。空引用(null)是 null 類型變量唯一的值。空引用(null)可以轉換爲任何引用類型,也就是說null可以賦值給任意的引用類型變量,如Object object = null;
。
在實際開發中 , 程序員可以忽略 null 類型,假定 null 只是引用類型的一個特殊的直接量(字面常量)。
空引用( null )只能被轉換成引用類型 , 不能轉換成基本類型,因此不要把一個 null 值賦給基本數據類型的變量。如
int number=null;
會導致編譯器報錯。
3.4基本數據類型
Java 的基本數據類型分爲兩大類: boolean 類型和數值類型。而數值類型又可以分爲整數類型和浮
點類型,整數類型裏的字符類型也可被單獨對待。 因此常把 Java 裏的基本數據類型分爲 4 類 8 種,如下圖所示。
Java 只包含上述 8 種基本數據類型,值得指出的是,String字符串不是基本數據類型, String字符串是一個類,也就是一個引用數據類型。
3.4.1整型(整數類型)
通常所說的整型,實際指的是如下 4 種類型:
- byte:一個byte類型整數在內存中佔8位,表數範圍是-128(-27)~127(27-1)
- short:一個short類型整數在內存中佔16位,表數範圍是-32768(-215)~32767(215-1)
- int:一個byte類型整數在內存中佔32位,表數範圍是-2147483648(-231)~2147483647(231-1)
- long:一個byte類型整數在內存中佔64位,表數範圍是(-263)~(263-1)
int 是最常用的整數類型,因此在通常情況下, 直接給出一個整數值默認就是 int 類型。除此之外, 有如下兩種情形必須指出。
- 如果直接將一個較小的整數值(在 byte 或 short 類型的表數範圍內)賦給一個 byte 或 short 變量,系統會自動把這個整數值當成 byte 或者 short 類型來處理。
- 如果使用一個巨大的整數值(超出了 int 類型的表數範圍)時, Java 不會自動把這個整數值當成 long 類型來處理。如果希望系統把一個整數值當成 long 類型來處理,應在這個整數值後增加 l 或者 L 作爲後綴。通常推薦使用 L,因爲英文字母 l 很容易跟數字 1 搞混。
下面的代碼片段驗證了上面的結論:
// 下面代碼是正確的,系統會自動把 56 當成 byte 類型處理
byte a = 56;
//下面代碼是錯誤的,系統不會把 9999999999999 當成 1ong 類型處理 所以超出 int 的表數範圍,從而引起錯誤
// 1ong bigVa1ue = 9999999999999 ;
//下面代碼是正確的,在巨大的整數值後使用 L 後綴,強制使用J.ong 類型
1ong bigVa1ue2 = 9223372036854775807L;
可以把一個較小的整數值(在 int 類型的表數範圍以內)直接賦給一個 long 類型的變量,這並不是因爲 Java 會把這個較小的整數值當成 long 類型來處理, Java依然把這個整數值當成 int 類型來處理,只是因爲 int 類型的值會自動類型轉換到 long 類型。
Java 中整數值有 4 種表示方式: 十進制、 二進制、八進制和十六進制,其中二進制的整數以 0b 或 0B 開頭;八進制的整數以 0 開頭;十六進制的整數以 0x 或者 0X 開頭,其中 10~15 進制分別以 a-f (此處的a-f不區分大小寫)來表示。
(1)
二進制的整數以 0b 或 0B 開頭
是因爲字母B全稱爲Binary /ˈbaɪnəri/ adj/n二進制(的),如0B1011
。
(2)八進制的整數以 0 開頭
是因爲數字0長得像英文單詞octal
的首字母o,而英文前綴oct-
是第八的意思,其中octal /‘ɒkt(ə)l/ adj/n八進制(的),如07654
。
(3)十六進制的整數以 0x 或者 0X 開頭
是因爲字母X全稱爲hexadecimal /heksəˈdesɪml/ adj/n十六進制(的),其中十六進制數中有字母a-f
是因爲在十六進制裏面,數字10~15分別用字母a-f表示,如0xa3f4
。
(4)字符型值也可以採用十六進制編碼方式
來表示,範圍是’\u0000’~’\uFFFF’, 一共可以表示 65536 個字符,其中前 256 個 (’\u0000’ ~ ‘\u00FF’) 字符和8位ASCII碼中的字符完全重合。當用十六進制編碼方式
(’\u0000’~’\uFFFF’)表示一個字符時,如果\u
後面的4個符號中高位有若干個0
,這些0
是不可以省略的,如\u0003
如果寫成\u3
在IDE中是會報錯的。
(5)字符型值還可以採用八進制轉義序列
來表示,範圍是’\000’~’\377’, 一共可以表示256個字符,其中前 256 個 (’\000’ ~ ‘\377’) 字符和8位ASCII碼中的字符完全重合。當用八進制轉義序列
(’\000’~’\377’)表示一個字符時,如果\
後面的3個符號中高位有若干個0
,這些0
是可以省略的,如\003
可以寫成\3
。
下面的代碼片段分別使用二進制、八進制和十六進制的數。
//以 0b 開頭的整數值是二進制的整數
int binaryValue=0b1011;
//以 0 開頭的整數值是八進制的整數
int octa1Va1ue = 013 ;
//以 0x 或 0X 開頭的整數值是十六進制的整數
int hexVa1ue1 = 0x13 ;
int hexVa1ue2 = 0XaF;
在某些時候,程序需要直接使用二進制整數, 二進制整數更"真實",更能表達整數在內存中的存在形式。不僅如此, 有些程序(尤其在開發一些遊戲時)使用二進制整數會更便捷。
從 Java 7 開始新增了對二進制整數的支持, 二進制的整數以 0b 或者 0B 開頭。程序片段如下:
// 定義兩個 8 位的二進制整數
int binaryValue1 = 0b11010100 ;
byte binaryValue2 = 0B01101001;
// 定義一個 32 位的二迸制整數, 最高位是符號位
int binaryValue3 = 0B10000000000000000000000000000011;
System . out . print1n(binaryValue1) ; // 輸出 212
System . out . println(binaryValue2) ; // 輸出 105
System . out . println(binaryValue3); //輸出 - 2147483645
從上面粗體字可以看出, 當定義 32 位的二進制整數時,最高位其實是符號位,當符號位是1時, 表明它是一個負數,負數在計算機裏是以補碼的形式存在的,因此還需要換算成原碼。
所有數字在計算機底層都是以二進制形式存在的,原碼是直接將一個數值換算成二進制數。 但計算機以補碼的形式保存所有的整數。 補碼的計算規則: 正數的反碼和補碼都與該正數的原碼相同;負數的補碼是其反碼加1;反碼是對原碼按住取反,只是最高位(符號位)保持不變。
其中原碼、反碼和補碼之間符號位改變的特殊情況分析可見博主的文章原碼、反碼和補碼之間符號位改變的特殊情況分析。
將上面的二進制整數 binaryValue3 轉換成十進制數的過程如下圖所示。
正如前面所指出的,整數值默認就是int類型,因此使用二進制形式定義整數時,二進制整數默認佔 32 位, 其中第 32 位是符號位:如果在二進制整數後添加 l 或 L 後綴,那麼這個二進制整數默認佔64 位,其中第 64 位是符號位。
例如如下程序:
//定義一個 8 位的二進制整數,該數值默認佔 32 位,因此它是一個正數
//只是強制類型轉換成 byte 時產生了溢出,最終導致 binVa14 變成了 -23
byte binVa14 = (byte)0b11101001;
//定義一個 32 位的二進制整數,最高位是 1//但由於數值後添加了 L 後綴, 因此該整數實際佔 64 位,第 32 位的 1 不是符號位
//因此 binVa15 的值等於 2 的 31 次方+ 2 + 1
long binVa15 = 0B10000000000000000000000000000011L;
System.out.println(binVa14) ; //輸出 -23
System.out.println(binVa15 ); //輸出 2147483651
上面程序中粗體字代碼與前面程序片段的粗體字代碼基本相同,只是在定義二進制整數時添加了"L" 後綴,這就表明把它當成 long 類型處理,因此該整數實際佔 64 位。 此時的第 32 位不再是符號位, 因此它依然是一個正數。
至於程序中的byte binVa14 = (byte)0b11101001;
代碼,其中 0b11101001 依然是一個 32 位的正整數, 只是程序進行強制類型轉換時發生了溢出,導致它變成了負數。關於強制類型轉換的知識請參考本章 3.5 節。
3.4.2字符型
字符型通常用於表示單個的字符, 字符型值必須使用單引號'
括起來。 Java 語言使用 16 位的Unicode 字符集
作爲編碼方式,而 Unicode 被設計成支持世界上所有書面語言的字符,包括中文字符, 因此 Java 程序支持各種語言的字符。
字符型值有如下三種表示形式:
- 直接通過單個字符來指定字符型值,例如’A’、 '9’和’0’等。
- 通過轉義字符(Escape character)表示特殊字符型值,例如’\n’、 '\t’等。(什麼是轉義字符?)
- 直接使用 Unicode 值來表示字符型值,格式是’\uXXXX’,其中字母u的全稱是Unicode,而 XXXX 代表一個十六進制的整數。
Java 語言中常用的轉義字符(Escape character)如下表所示:
轉義字符 | 說明 | Unicode表達方式 |
---|---|---|
\b | 退格符 | \u0008 |
\n | 換行符 | \u000a |
\r | 回車符 | \u000d |
\t | 製表符 | \u0009 |
\" | 雙引號\u0022 | |
\’ | 單引號 | \u0027 |
\\ | 反斜線 | \u005c |
字符型值也可以採用十六進制編碼方式
來表示,範圍是’\u0000’~’\uFFFF’, 一共可以表示 65536 個字符,其中前 256 個 (’\u0000’ ~ ‘\u00FF’) 字符和8位ASCII碼中的字符完全重合。當用十六進制編碼方式
(’\u0000’~’\uFFFF’)表示一個字符時,如果\u
後面的4個符號中高位有若干個0
,這些0
是不可以省略的,如\u0003
如果寫成\u3
在IDE中是會報錯的。
除了用十六進制編碼方式來表示,字符型值還可以採用八進制轉義序列
來表示,範圍是’\000’~’\377’, 一共可以表示256個字符,其中前 256 個 (’\000’ ~ ‘\377’) 字符和8位ASCII碼中的字符完全重合。當用八進制轉義序列
(’\000’~’\377’)表示一個字符時,如果\
後面的3個符號中高位有若干個0
,這些0
是可以省略的,如\003
可以寫成\3
。
char類型的變量值完全可以參與加減乘除等數學運算,也可以比較大小, 實際上都是用該字符對應的編碼參與運算。
如果把 0-65535 範圍內的一個 int 整數賦值給 char 類型變量,系統會自動把這個 int 整數當成 char 類型來處理。
下面程序簡單示範了字符型變量的用法:
public class CharTest {
public static void main(String[] args){
//直接指定單個字符a作爲字符值
char aCharacter='a';
System.out.println(aCharacter);
//直接指定轉義字符\r作爲字符值
char enterChar='\r';
System.out.println(enterChar);
//使用“香”的Unicode編碼值作爲字符值
char chineseChar='\u9999';
System.out.println(chineseChar);
//將char字符型'瘋'賦值給int變量,以證明字符型相當於無符號整數,即可以把
char chineseChar2='瘋';
int intChineseChar2=chineseChar2;
System.out.println(intChineseChar2);
//將int整型98賦值給char字符型變量,以證明字符型相當於無符號整數
//即可把0-65535範圍內的一個int整數賦值給char字符型變量,編譯器會自動把這個int整數轉化成char字符型
char b=98;
System.out.println(b);
//若把0-65535範圍外的數值,如-66或99999賦值給char字符型變量,編譯器會報錯
//char c=-66;
}
}
由上述代碼可得:
- char字符型相當於無符號整數,其表數範圍是 0~65535。即可把0-65535範圍內的一個int整數賦給char字符型變量,編譯器會自動把這個int整數轉化成char字符型。
- 任意一個char字符可賦值給int整型變量。因爲char字符型是2字節,而int整型是4字節,即int整型的表數範圍完全覆蓋char字符型的表數範圍。
Java 沒有提供表示字符串的基本數據類型,而是通過 String 類來表示字符串,由於字符串由多個字符組成,因此字符串要使用雙引號括起來。 如下代碼:
\\下面代碼定義了一個 s 變量, 它是一個字符串實例的引用,它是一個引用類型的變量
String s = "滄海月明珠有淚,藍因玉暖日生煙。 " ;
讀者必須注意: char 類型字符使用單引號括起來,而字符串使用雙引號括起來。
值得指出的是, Java 語言中的單引號、雙引號和反斜線都有特殊的用途,如果一個字符串中包含了這些特殊字符,則應該使用轉義字符的表示形式。 例如,在 Java 程序中表示一個絕對路徑:"c:\codes"
,但這種寫法得不到期望的結果, 因爲Java會把單個反斜線\
和字母c
當成轉義字符\c
。所以應該寫成這種形式:"c:\\codes"
,只有同時寫兩個反斜線, Java 纔會把第一個反斜線和後一個反斜線當成轉義字符從而組成真正的反斜線\
。
在 Java 程序中表示一個絕對路徑:"c:\codes"
的實測代碼如下:
上述代碼的最後兩個System.out.println
輸出結果如下:
3.4.3浮點型
Java 的浮點類型有兩種:float 和 double。Java 的浮點類型有固定的表數範圍和字段長度, 字段長度和表數範圍與機器無關。 Java 的浮點數遵循 IEEE754標準,採用二進制數據的科學計數法來表示浮點數, 對於 float 型數值,第 1 位是符號位, 接下來 8 位表示指數, 再接下來的 23 位表示尾數:對於 double 類型數值,第 1 位也是符號位,接下來的 11 位表示指數, 再接下來的 52 位表示尾數。
因爲 Java 浮點數使用二進制數據的科學計數法來表示浮點數,因此可能不能精確表示一個浮點數。 例如把5.2345556f值賦給一個 float 類型變量,接着輸出這個變量時看到這個變量的值已經發生了改變。 使用 double 類型的浮點數比 float 類型的浮點數更精確,但如果浮點數的精度足夠高(小數點後的數字很多時),依然可能發生這種情況。 如果開發者需要精確保存一個浮點數,則可以考慮使用 BigDecimal 類。
//不帶'f'的"5.2345556"默認爲double,故輸出時精度無誤
System.out.println(5.2345556);
//帶'f'的"5.2345556"是float,小數過多故輸出時精度有誤,輸出結果爲5.2345557
System.out.println(5.2345556f);
//帶'f'的"5.2345556"是float,小數過多故輸出時精度有誤,輸出結果爲5.2345557
float number=5.2345556f;
System.out.println(number);
double 類型代表雙精度浮點數, float 類型代表單精度浮點數。一個 double 類型的數值佔 8 字節、64 位, 一個 float 類型的數值佔 4 字節、 32 位。
Java 語言的浮點數有兩種表示形式:
- 十進制數形式:這種形式就是簡單的浮點數,例如 5.12、 512.0、 .512。浮點數必須包含一個小數點,否則會被當成 int 類型處理。
- 科學計數法形式:例如 5.12e2 (即5.12x102, 5.12E2 (也是 5.12x 102)。
必須指出的是,只有浮點類型的數值纔可以使用科學計數法形式表示。例如, 51200 是一個 int 類型的值, 但 512E2 則是浮點類型的值。
Java語言的浮點類型默認是 double 類型,如果希望 Java 把一個浮點類型值當成 float 類型處理,應該在這個浮點類型值後緊跟 f或F。 例如, 5.12 代表一個 double 類型的值,佔 64 位的內存空間;5.12f 或者 5.12F 才表示一個 float 類型的值, 佔 32 位的內存空間。當然,也可以在一個浮點數後添加 d 或 D 後綴, 強制指定是 double 類型,但通常沒必要。
Java還提供了三個特殊的浮點數值:正無窮大、負無窮大和非數,用於表示溢出和出錯。例如,使用一個正數除以 0 將得到正無窮大,使用一個負數除以 0 將得到負無窮大, 0.0 除以 0.0 或對一個負數開方將得到一個非數。 正無窮大通過 Double 或 Float 類的 POSITIVE_INFINTY 表示;負無窮大通過 Double 或 Float 類的 NEGATIVE_INFINITY 表示,非數通過 Double 或 Float 類的 NaN 表示(NaN指Not A Number)。
必須指出的是,所有的正無窮大數值都是相等的,所有的負無窮大數值都是相等的; 而 NaN 不與任何數值相等, 甚至和 NaN 都不相等。
只有浮點數除以 0 纔可以得到正無窮大或負無窮大,因爲 Java 語言會自動把和浮點數運算的 0 ( 整數)當成 0.0 (浮點數)處理。如果一個整數值除以0,則會拋出一個異常:ArithmeticException: / by zero (除以 0 異常)。
下面程序示範了上面介紹的關於浮點數的各個知識點:
public class FloatTest {
public static void main(String[] args){
float floatNumber=3.2345556f;
//下面將看到floatNumber在控制檯終端被輸出的值變了,爲3.2345555
System.out.println(floatNumber);
double c=Double.NEGATIVE_INFINITY;
float d= Float.NEGATIVE_INFINITY;
//看到 float 和 double 的負無窮大是相等的,下面輸出結果爲true
System.out.println(c==d);
// 0.0除以0.0的結果是非數,下面輸出結果爲NaN
System.out.println(0.0/0.0);
//兩個非數之間是不相等的,下面輸出結果爲false
System.out.println(0.0/0.0==Float.NaN);
//所有正無窮大都是相等的,下面輸出結果爲true
System.out.println(1.0/0==2.0/0.0);
//整數除以0將拋出除以0的異常java.lang.ArithmeticException: / by zero
System.out.println(3/0);
}
}
3.4.4數值中使用下劃線分隔
正如前面程序中看到的,當程序中用到的數值位數特別多時,程序員眼睛"看花"了都看不清到底有多少位數。 爲了解決這種問題, Java 7 引入了一個新功能:程序員可以在數值中使用下劃線,不管是整型數值,還是浮點型數值,都可以自由地使用下劃線。通過使用下劃線分隔, 可以更直觀地分辨數值中到底包含多少位。如下面程序所示:
public class UnderScoreNumber {
public static void main(String[] args) {
int binaryValue=0B1000_0_001_010101_11;
double height=1_46_7893.1_41_5926;
//自由地使用的下劃線不影響原來的數值,下面輸出結果爲33111
System.out.println(binaryValue);
//自由地使用的下劃線不影響原來的數值,下面輸出結果爲1467893.1415926
System.out.println(height);
}
}
3.4.5布爾型
布爾型只有一個 boolean 類型,用於表示邏輯上的"真"或"假"。在 Java 語言中, boolean 類型的數值只能是 true 或 false,不能用0或者非0來代表。其他基本數據類型的值也不能轉換成 boolean 類型。
Java 規範並沒有強制指定 boolean 類型的變量所佔用的內存空間 。 雖然 boolean 類型
的變量或值只要1位即可保存,但由於大部分計算機在分配內存時允許分配的最小內存單元是字節(8位), 因此 bit 大部分時候實際上佔用 8 位。
如下面代碼定義了兩個 boolean 類型的變量,並指定初始值:
//定義舊的值爲 true
boolean bl = true;
//定義 b2 的值爲 false
boolean b2 = false ;
字符串"true"和"false"不能直接賦值給 boolean 類型的變量,但如果使用一個 boolean 類型的值和字符串進行連接運算,則 boolean 類型的值將會自動轉換成字符串。 看下面代碼(程序清單同上)。
//正確的boolean類型變量的賦值方法
boolean b=true;
//字符串"true"和"false"不能轉換成 boolean 類型,下面的寫法boolean c="true";是錯誤的
//boolean c="true";
//使用boolean類型的值和字符串進行連接運算,boolean類型的值會自動轉換成字符串
String string = true + "";
//下面將輸出true
System.out.println(string);
boolean 類型的值或變量主要用做旗幟標誌FLAG來進行流程控制, Java 語言中使用 boolean 類型的變量或值控制的流程主要有:if條件控制語句、while 循環控制語句、do while 循環控制語句、for 循環控制語句。
除此之外, boolean 類型的變量和值還可在三目運算符?:
中使用。這些內容在後面將會有更詳細的介紹。
3.5基本類型的類型轉換
在 Java 程序中,不同的基本類型的值經常需要進行相互轉換。 Java 語言所提供的 7 種數值類型之間可以相互轉換,有兩種類型轉換方式:自動類型轉換和強制類型轉換。
3.5.1 自動類型轉換
Java 所有的數值型變量可以相互轉換,如果系統支持把某種基本類型的值直接賦給另一種基本類型的變量,則這種方式被稱爲自動類型轉換
。 當把一個表數範圍小的數值或變量直接賦給另一個表數範圍大的變量時,系統將可以進行自動類型轉換;否則就需要強制轉換。
表數範圍小的可以向表數範圍大的進行自動類型轉換,就如同有兩瓶水,當把小瓶裏的水倒入大瓶中時不會有任何問題。 Java 支持自動類型轉換的類型如下圖所示:
上圖中所示的箭頭左邊的數值類型可以自動類型轉換爲箭頭右邊的數值類型。 下面程序示範了自動類型轉換。
public class AutoConversion {
public static void main(String[] args) {
int integerNumber=6;
//int類型可以自動轉換爲 float 類型
float floatNumber=integerNumber;
//下面將輸出 6.0
System.out.println(floatNumber);
//定義一個byte類型的整數變量
byte byteNumber=9;
//下面代碼將出錯,因byte類型不能自動類型轉換爲char類型
//char character = byteNumber;
//byte類型變量可以自動類型轉換爲 double 類型
double doubleNumber = byteNumber ;
//下面將輸出 9.0
System.out.println (doubleNumber) ;
}
}
不僅如此,當把任何基本類型的值和字符串值進行連接運算時,基本類型的值將自動類型轉換爲字符串類型,雖然字符串類型不是基本類型,而是引用類型。 因此,如果希望把基本類型的值轉換爲對應的字符串時,可以把基本類型的值和一個空字符串進行連接。
+
不僅可作爲加快向使用 ,還可作爲字符串連接運算符使用。
看如下代碼:
public class PrimitiveAndString {
public static void main(String[] args){
//下面代碼是錯誤的,因爲5是一個整數,可以賦值給char字符型變量,但不能賦值給一個String字符串對象
//String string1 = 5;
//一個基本類型的值和字符串進行連接運算時,基本類型的值自動轉換爲字符串
String string2=3.5f+"";
//下面輸出3.5
System.out.println(string2);
//下面語句輸出7Hello!,因爲+運算符是從左往右的
System.out.println(3+4+"Hello!");
// 下面語句輸出 Hello!34 ,因爲 Hello!+3會把3當成字符串處理,而後再把4當成字符串處理
System.out.println("Hello! " + 3 + 4);
}
}
上面程序中有一個3+4+"Hello!"
表達式,這個表達式先執行3+4
運算,這是執行兩個整數之間的加法得到7,然後進行7 +"Hello!"
運算,此時會把7當成字符串進行處理,從而得到 7Hello!
。 反之,對於 "Hello!"+ 3 + 4
表達式,先進行"Hello! "+ 3
運算,得到一個 Hello! 3
字符串 ,再 和 4 進行連接運算, 4 也被轉換成字符串進行處理
3.5.2 強制類型轉換(Narrow Conversion)
如果希望把下圖中箭頭右邊的類型轉換爲箭頭左邊的類型,則必須進行強制類型轉換,強制類型轉換的語法格式是:(targetType) value
,強制類型轉換的運算符是圓括號()
。當進行強制類型轉換時,類似於把一個大瓶子裏的水倒入一個小瓶子,如果大瓶子裏的水不多還好,但如果大瓶子裏的水很多,將會引起溢出 ,從而造成數據丟失。 這種轉換也被稱爲"縮小轉換 (Narrow Conversion)"。
conversion /kənˈvɜːrʒn/ n轉換
下面程序示範了強制類型轉換:
public class NarrowConversion {
public static void main(String[] args){
int integerValue=233;
//強制把一個int型值轉換爲byte類型的值並賦值給byte類型的變量
byte byteValue=(byte)integerValue;
//強制類型轉換爲byte類型後的233的輸出結果爲-23
System.out.println(byteValue);
double doubleValue=3.98;
//強制把一個double類型的值轉換爲int類型的值,輸出結果爲3
System.out.println((int)3.98);
}
}
在上面程序中,把一個浮點數3.98強制類型轉換爲整數3時, Java 將直接截斷浮點數的小數部分。 除此之外,上面程序還把 233 強制類型轉換爲 byte 類型的整數,從而變成了-23, 這就是典型的溢出 。下圖示範了這個轉換過程。
從上圖可以看出, 32 位int類型的 233 在內存中如圖上所示,強制類型轉換爲 8 位的byte類型,則需要截斷前面的 24 位,只保留右邊 8 位,最左邊的 1 是一個符號位,此處表明這是一個負數,負數在計算機裏是以補碼形式存在的,因此還需要換算成原碼。將補碼減 1 得到反碼形式,再將反碼取反就可以得到原碼。最後的二進制原碼爲 10010111,這個 byte 類型的值爲-(16+4+2+1),也就是-23。從上圖很容易看出, 當試圖強制把表數範圍大的類型轉換爲表數範圍小的類型時,必須格外小心, 因爲非常容易引起信息丟失。
經常上網的讀者可能會發現有些網頁上會包含臨時生成的驗證字符串,那麼這個隨機字符串是如何生成的呢?可以先隨機生成一個在指定範圍內的int數字(如果希望生成小寫字母,就在 97~122 之間), 然後將其強制轉換成char類型,再將多次生成的字符連綴起來即可。
下面程序示範瞭如何生成一個 6 位的隨機字符串,這個程序中用到了後面的循環控制,不理解循環的讀者可以參考後面章節的介紹。
public class RandomString {
public static void main(String[] args) {
//定義一個空字符串
String string="";
//進行6次循環
for (int i = 0; i < 6; i++) {
//隨機生成一個97~122之間的int類型整數,其中Math.random()隨機返回大於等於0.0且小於1.0的僞隨機double值
int integerValue=(int) ( Math.random()*26+97 );
//將integerValue強制轉換爲char類型後連接到string後面
string=string+(char)integerValue;
}
//輸出隨機生成的字符串string
System.out.println(string);
}
}
還有下面一行容易出錯的代碼:
//直接把 5.6 賦值給 f1oat 類型變量將出現錯誤, 因爲5.6默認是 doub1e 類型
float a = 5.6 ;
上面代碼中的 5.6 默認是一個 double 類型的浮點數,因此將 5.6 賦值給一個 float 類型變量將導致錯誤,必須使用強制類型轉換纔可以,即將上面代碼改爲如下形式:
f1oat a = (f1oat)5.6 ;
在通常情況下,字符串不能直接轉換爲基本類型,但通過基本類型對應的包裝類則可以實現把字符串轉換成基本類型。例如,把字符串轉換成int類型,則可通過如下代碼實現:
String a = "45";
//使用包裝類Integer的方法parseInt()將一個字符串轉換成int類型
int iVa1ue =Integer.parseInt (a);
System.out.println(a);
Java 爲 8 種基本類型都提供了對應的包裝類: boolean 對應 Boolean、byte 對應 Byte、short 對應Short、 int 對應 Integer、 long 對應 Long、 char 對應 Character、 float 對應 Float、 double 對應 Double , 8 個包裝類都提供了一個 parseXxx(String str)靜態方法用於將字符串轉換成基本類型。關於包裝類的介紹,請參考本書第6章。
3.5.3表達式類型的自動提升
當一個算術表達式中包含多個基本類型的值時,整個算術表達式的數據類型將發生自動提升。 Java定義瞭如下的自動提升規則。
- 所有的 byte 類型、 short 類型和 char 類型變量之間的運算操作都將運算結果提升到 int 類型。所有byte、short、char類型變量與字面常量相加的表達式的類型將被提升到int類型。
- 整個算術表達式的數據類型自動提升到與表達式中最高等級操作數同樣的類型。操作數的等級排列如下圖所示,位於箭頭右邊類型的等級高於位於箭頭左邊類型的等級。
下面程序示範了一個典型的錯誤:
public class AutoPromotion {
public static void main(String[] args) {
//定義一個short類型變量
short shortValue1=555;
//字面常量之間的運算結果仍然是字面常量,不會變成int類型,故下條語句正確
short shortValue2=666+5;
byte byteValue1=34;
//下面語句中byte、short、char類型變量與字面常量相加的表達式的類型將被提升到int類型,故報錯
//short shortResult2=shortValue1+8;
//下面語句中即便兩個short類型變量相加,其運算結果任然是int類型,故報錯
//short shortResult2=shortValue1+shortValue2;
//下面語句中即便short類型變量與byte類型變量相加,其運算結果任然是int類型,故報錯
//short shortResult3=shortValue1+byteValue1;
}
}
上面的shortValue+8
表達式的類型將被提升到 int 類型, 這樣就把右邊的 int 類型值賦給左邊的 short 類型變量, 從而引起錯誤。
下面代碼是表達式類型自動提升的正確示例代碼:
byte b = 40;
char c = 'a';
int i = 23;
double d = .314;
//右邊表達式中最高等級操作數爲d(double類型)
//則右邊表達式的類型爲double類型, 故賦給一個double類型變量
double result = b + c + i*d ;
//將輸出144.222
System.out.println(result);
必須指出, 表達式的類型將嚴格保持和表達式中最高等級操作數相同的類型。下面代碼中兩個 int 類型整數進行除法運算, 即使無法除盡, 也將得到一個 int 類型結果:
//右邊表達式中兩個操作數都是int類型,故右邊表達式的類型爲int
//雖然 23/3 不能除盡,但依然得到一個int類型整數
int intResult = 23 / 3;
System.out.println(intResult); // 將輸出 7
從上面程序中可以看出,當兩個整數進行除法運算時, 如果不能整除, 得到的結果將是把小數部分截斷取整後的整數。
如果表達式中包含了字符串,則又是另一番情形了。 因爲當把加號(+)放在字符串和基本類型值之間時, 這個加號是一個字符串連接運算符, 而不是進行加法運算。 看如下代碼:
//輸出字符串Hello!a7
System.out.println("Hello!"+'a'+7);
//輸出字符串104Hello!,因爲char字符型的a的ASCII碼值爲97
System.out.println('a'+7+"Hello!");
對於第一個表達式"Hello!"+'a'+7
, 先進行"Hello!"+'a'
運算, 把'a'
轉換成字符串 , 拼接成字符串Hello!a
, 接着進行"Hello!a" + 7
運算,這也是一個字符串連接運算, 得到結果是Hello!a7
。 對於第二個表達式, 先進行'a' + 7
加法運算,其中'a'
自動提升到 int 類型,變成 a 對應的ASCII值97,從97 + 7
將得到104
,然後進行104 + "Hello!"
運算 ,104
會自動轉換成字符串,將變成兩個字符串進行連接運算, 從而得到104Hello!
。
3.6直接量(字面常量literal)
直接量是指在程序中通過源代碼直接給出的值, 例如在int a = 5;
這行代碼中, 爲變量a
所分配的初始值5
就是一個直接量(字面常量literal)。
3.6.1直接量的類型
並不是所有的數據類型都可以指定直接量, 能指定直接量的通常只有三種類型: 基本類型、字符串類型和 null 類型。 具體而言 , Java 支持如下 8 種類型的直接量:
- int 類型的直接量:在程序中直接給出的整型數值, 可分爲二進制、十進制、八進制和十六進制 4 種,其中二進制需要以 0B 或 0b 開頭, 八進制需要以 0 開頭, 十六進制需要以 0x 或 0X 開頭。
例如 123、 012 (對應十進制的 10)、 0x12 (對應十進制的 18 ) 等。 - long 類型的直接量:在整型數值後添加 l 或 L 後就變成了 long 類型的直接量。 例如 3L、0x12L(對應十進制的18L)。
- float 類型的直接量: 在一個浮點數後添加 f或 F 就變成了 float 類型的直接量,這個浮點數可以 是標準小數形式,也可以是科學計數法形式。 例如 5.34F、 3.14E5f。
- double 類型的直接量:直接給出一個標準小數形式或者科學計數法形式的浮點數就是 double 類型的直接量。 例如 5.34、 3.14E5
- boolean 類型的直接量: 這個類型的直接量只有 true 和 false。
- char 類型的直接量: char 類型的直接量有三種形式,分別是用單引號括起來的字符、轉義字符和Unicode值表示的字符。 例如’a’、 W和’\u0061’。
- String 類型的直接量: 一個用雙引號括起來的字符序列就是 String 類型的直接量。
- null 類型的直接量: 這個類型的直接量只有一個值,即 null。
在上面的 8 種類型的直接量中, null 類型是一種特殊類型,它只有一個值:null,而且這個直接量可以賦給任何引用類型的變量,用以表示這個引用類型變量中保存的地址爲空,即還未指向任何有效對象。
3.6.2直接量的賦值
通常總是把一個直接量賦值給對應類型的變量,例如下面代碼都是合法的。
int a = 5;
char c = 'a';
boolean b = true ;
float f = 5.12f;
double d = 4.12;
String author = "李剛";
String book = "瘋狂 Android 講義";
除此之外, Java還支持數值之間的自動類型轉換,因此允許把一個數值直接量直接賦給另一種類型的變量,這種賦值必須是系統所支持的自動類型轉換,例如把 int 類型的直接量賦給一個 long 類型的變量。 Java 所支持的數值之間的自動類型轉換圖如下圖所示,箭頭左邊類型的直接量可以直接賦給箭頭右邊類型的變量;如果需要把下圖中箭頭右邊類型的直接量賦給箭頭左邊類型的變量,則需要強制類型轉換。
String 類型的直接量不能賦給其他類型的變量, null 類型的直接量可以直接賦給任何引用類型的變量,包括 String 類型。 boolean 類型的直接量只能賦給 boolean 類型的變量,不能賦給其他任何類型的變量。
關於字符串直接量有一點需要指出, 當程序第一次使用某個字符串直接量時, Java 會使用常量池(constant pool) 來緩存該字符串直接量,如果程序後面的部分需要用到該字符串直接量時, Java 會直接使用常量池 (constant pool) 中的字符串直接量。
由於 String 類是一個典型的不可變類(final類),因此 String 對象創建出來就不可能被改變,因此無須擔心共享 String 對象會導致混亂。 關於不可變類的概念參考本書第 6 章。
常量池( constant pool )指的是在編譯期被確定,並被保存在己編譯的.class 文件中的一些數據。 它包括關於類、方法、 接口中的常量, 也包括字符串直接量。
看下面的程序:
String s0="hello";
String s1="hello";
String s2="he"+"llo";
//下面結果輸出爲true
System.out.println(s0==s1);
//下面結果輸出爲true
System.out.println(s0==s2);
Java會確保每個字符串常量只有一個,不會產生多個副本。 例子中的 s0 和 s1 中的"hello"都是字符
串常量,它們在編譯期就被確定了,所以s0 == s1
返回 true;而"he"
和"llo"
也都是字符串常量,當一個字符串由多個字符串常量連接而成時,它本身也是字符串常量, s2 同樣在編譯期就被解析爲一個字符串常量,所以 s2 也是常量池中"hello"
的引用 。 因此,程序輸出s0 == s1
返回 true,s1 == s2
也返回 true。
3.7運算符
運算符是一種特殊的符號,用以表示數據的運算、賦值和比較等。 Java 語言使用運算符將一個或多個操作數連綴成執行性語句,用以實現特定功能。
Java 語言中的運算符可分爲如下幾種:算術運算符、賦值運算符、比較運算符、邏輯運算符、位運算符、類型相關運算符。
3.7.1 算數運算符
Java 支持所有的基本算術運算符,這些算術運算符用於執行基本的數學運算:加、減、乘、 除、求餘、自加和自減等。 下面是 7 個基本的算術運算符。
3.7.1.1 +加法運算符
除下面代碼中的加法運算之外,+還可以作爲字符串的連接運算符:
double a=5.2;
double b=3.1;
double sum=a+b;
//sum的值爲8.3
System.out.println(sum);
//+還可以作爲字符串的連接運算符
String s2="he"+"llo";
3.7.1.2 -減法運算符
-除法不僅可以作爲減法運算符,還可以作爲求負的運算符:
double a=5.2;
double b=3.1;
double sub=a-b;
//sub的值爲2.1,其中sub是subtract的縮寫
System.out.println(sub);
//-除法不僅可以作爲減法運算符,還可以作爲求負的運算符
double x=-5.0;
//將x求負,其值變成5.0
x=-x;
3.7.1.3 *乘法運算符
double a=5.2;
double b=3.1;
double multiply=a*b;
//multiply的值爲16.12
System.out.println(multiply);
3.7.1.4 /除法運算符
除法運算符有些特殊,如果除法運算符的兩個操作數都是整數類型,則計算結果也是整數, 就是將自然除法的結果截斷取整(不會四捨五入),例如19/4
的結果是4
,而不是5
。如果除法運算符的兩個操作數都是整數類型,則除數不可以是0, 否則將引發除以零異常。
但如果除法運算符的兩個操作數有一個是浮點數,或者兩個都是浮點數,則計算結果也是浮點數,這個結果就是自然除法的結果。 而且此時允許除數是0, 或者 0.0,得到結果是正無窮大或負無窮大。
看下面的代碼:
public class DivisionTest {
public static void main(String[] args){
double a=5.2;
double b=3.1;
double division=a/b;
//下面輸出的division的值是1.6774193548387097
System.out.println(division);
//輸出正無窮大:Infinity,Infinity爲基本類型的包裝類型Double和Float中的常量POSITIVE_INFINITY
System.out.println(5/0.0);
//輸出負無窮大:-Infinity,-Infinity爲基本類型的包裝類型Double和Float中的常量NEGATIVE_INFINITY
System.out.println(-5/0.0);
//下面代碼將出現異常
//java.1ang.ArithmeticException: / by zero
//System.out.println(-5/0);
}
}
3.7.1.5 %求餘運算符
求餘運算的結果不一定總是整數, 它的計算結果是使用第一個操作數除以第二個操作數, 得到一個整除的結果後剩下的值就是餘數。由於求餘運算也需要進行除法運算,因此如果求餘運算的兩個操作數都是整數類型,則求餘運算的第二個操作數不能是 0,否則將引發除以零異常。 如果求餘運算的兩個操作數中有一個或者兩個都是浮點數,則允許第二個操作數是 0 或 0.0,只是求餘運算的結果是非數NaN
。 0 或 0.0 對零以外的任何數求餘都將得到 0 或 0.0。
看如下程序:
public class ModTest {
public static void main(String[] args) {
double a=5.2;
double b=3.1;
double mod=a%b;
System.out.println(mod);//mod的值爲2.1
System.out.println(5%0.0);//輸出非數NaN
System.out.println(-5%0.0);//輸出非數NaN
System.out.println(0%5.0);//輸出0.0
System.out.println(0%0.0);//輸出非數NaN
//下面代碼將出現異常: java.1ang .ArithmeticException : / by zero
//System.out.println(-5%0);
}
}
3.7.1.6 ++自加運算符
++自加運算符運算符有兩個要點:
- 自加是單目運算符,只能操作一個操作數;
- 自加運算符只能操作單個數值型 (整型、浮點型都行)的變量, 不能操作常量或表達式。
++自加運算符運算符既可以出現在操作數的左邊,也可以出現在操作數的右邊。 但出現在左邊和右邊的效果是不一樣的。 如果把++放在左邊,則先把操作數加1,然後才把操作數放入表達式中運算;如果把++放在右邊,則先把操作數放入表達式中運算,然後才把操作數加 1。 看如下代碼:
int a=5;
//讓a先執行算術運算,然後自加
int b=a++ + 6;
//輸出 a 的值爲 6 , b 的值爲 11
System.out.println(a+"\n"+b);
int aa=5;
//讓aa先自加,然後執行算術運算
int bb=++aa + 6;
//輸出 aa 的值爲 6 , bb 的值爲 12
System.out.println(aa+"\n"+bb);
3.7.1.7 --自減運算符
–自減運算符也是單目運算符,用法與++自加運算符基本相似,只是將操作數的值減1。
自加和自減只能用於操作變量,不能用於操作數值直接量、常量或表達式。例如, 5++、6–等寫法都是錯誤的。
Java並沒有提供其他更復雜的運算符,如果需要完成乘方、開方等運算,則可藉助於java.lang.Math類的工具方法完成複雜的數學運算,見如下代碼:
import com.sun.tools.javac.Main;
public class MathTest {
public static void main(String[] args) {
double a=3.2;
//求a的5次方,並將計算結果賦給b
double b=Math.pow(a,5);
//下面輸出b的值爲335.5443200000001
System.out.println(b);
//求a的平方根,並將結果賦給c,輸出的c的值爲1.7888543819998317
double c=Math.sqrt(a);
System.out.println(c);
//計算隨機數,返回一個0~1之間的僞隨機數
double d=Math.random();
System.out.println(d);
//求1.57的sin函數值:1.57被當成弧度數,輸出的e的值爲0.9999996829318346
double e= Math.sin(1.57);
System.out.println(e);
}
}
Math 類下包含了豐富的靜態方法,用於完成各種複雜的數學運算。
3.7.2賦值運算符
賦值運算符用於爲變量指定變量值,與C類似, Java 也使用=
作爲賦值運算符。通常,使用賦值運算符將一個直接量值賦給變量。例如如下代碼:
String str = "Java" ; //爲變量 str 賦值 Java
double pi = 3.14; //爲變量pi賦值 3 . 14
boolean visited = true ; //爲變量 visited 賦值 true
除此之外,也可使用賦值運算符將一個變量的值賦給另一個變量:
String str = "Java" ; //爲變量 str 賦值 Java
String str2=str; //將變量 str 的值賦給 str2
按前面關於交量的介紹,可以把交量當成一個可盛裝數據的容器。 而賦值運算就是將被賦的值"裝入"變量的過程。 賦值運算符是從右向左執行計算的,程序先計算得到
=
右邊的值,然後將該值"裝入"=
左邊的變量,因此賦值運算符=
左邊只能是變量。
值得指出的是,賦值表達式是有值的,賦值表達式的值就是右邊被賦的值。 例如String st2 = str;
賦值表達式的值就是 str
。 因此,賦值運算符支持連續賦值,通過使用多個賦值運算符,可以一次爲多個變量賦值。 如下代碼是正確的:
String string="Java";
String string2;
//賦值表達式string2=string的輸出結果爲Java,因此可證明:賦值表達式是有值的,賦值表達式的值就是右邊被賦的值
System.out.println((string2=string));
int a;
int b;
int c;
//通過爲 a, b, c 賦值常數7, 三個變量的值都是 7
a=b=c=7;
//輸出三個變量的值,其結果都爲7
System.out.print1n(a + " \ n " + b + " \n " + c) ;
雖然 Java支持這種一次爲多個變量賦值的寫法,但這種寫法導致程序的可讀性降低, 因此不推薦這樣寫。
賦值運算符還可用於將表達式的值賦給變量。 如下代碼是正確的:
doub1e d1 = 12.34;
doub1e d2 = d1 + 5;//將表達式的值賦給 d2
System.out.print1n(d2) ;//輸出 d2 的值, 將輸出 17.34
賦值運算符還可與其他運算符結合,擴展成功能更加強大的賦值運算符,參考 3.7.4 節。
3.7.3位運算符
Java 支持的位運算符有如下 7 個:
&
: 按位與。當兩位同時爲1時才返回1。|
: 按位或。 只要有一位爲1即可返回1。~
: 按位非。 單目運算符,將操作數的每個位(包括符號位)全部取反。^
:按位異或。當兩位相同時返回0,不同時返回1。<<
:左移運算符。>>
: 右移運算符。>>>
: 無符號右移運算符。
一般來說,位運算符只能操作整數類型的變量或值。 位運算符的運算法則如下表所示:
按位非
只需要一個操作數,這個運算符將把操作數在計算機底層的二進制碼按位(包括符號位)取反。 如下代碼測試了按位與
和按位或
運算的運行結果。
//因爲5、9的二進制分別是0101和1001,故他兩的按位與後的結果是0001(2)即1(10)
System.out.println(5 & 9);
//因爲5、9的二進制分別是0101和1001,故他兩的按位或後的結果是1101(2)即13(10)
System.out.println(5 | 9);
下面是按位異或和按位取反的執行代碼:
System.out.println(~-5) ; //將輸出4,原理見下圖
System.out.println(5 ^ 9) ; //將輸出12
程序執行~-5
的結果是4
,執行5 ^ 9
的結果是12
。 下面通過下圖來介紹運算原理:
而5 ^ 9
的運算過程如下圖所示:
左移運算符<<
是將操作數的二進制碼整體左移指定位數,左移後右邊空出來的位以0填充。 例如如下代碼:
System.out.println(5 << 2) ; //輸出 20
System.out.println( - 5 << 2) ; //輸出 - 20
下面以-5 爲例來介紹左移運算的運算過程,如下圖所示:
在上圖中, 上面的 32 位數是-5的補碼,左移兩位後得到一個新的二進制補碼,這個二進制補碼的最高位是1表明是一個負數,換算成十進制數就是-20。
Java的右移運算符有兩個:>>
和>>>
,對於>>
運算符而言,把第一個操作數的二進制碼右移指定位數後,左邊空出來的位以原來的符號位填充,即如果第一個操作數原來是正數,則左邊補0;如果第一個操作數是負數,則左邊補1。>>>
是無符號右移運算符, 它把第一個操作數的二進制碼右移指定位數後, 左邊空出來的位總是以0填充。
看下面代碼:
System.out.println( - 5 >> 2) ; //輸出 -2
System.out.println( - 5 >>> 2) ; //輸出 10 73741822
下面用示意圖來說明>>和>>>運算符的運算過程:
從下圖來看,-5右移2位後左邊空出2位, 空出來的2位以符號位補充。從圖中可以看出,右移運算後得到的結果的正負與第一個操作數的正負相同。 右移後的結果依然是一個負數,這是一個二進制補碼,換算成十進制數就-2。
從下圖來看, -5 無符號右移 2 位後左邊空出 2 位, 空出來的 2 位以 0 補充。 從圖中可以看出,無符號右移運算後的結果總是得到一個正數。 下圖中下面的正數是 1073741822 (230~2)。
進行移位運算時還要遵循如下規則:
- 對於低於 int 類型(如 byte、 short 和 char) 的操作數總是先自動類型轉換爲 int 類型後再移位。
- 對於 int 類型的整數移位
a>>b
, 當 b>32 時,系統先用 b 對 32 求餘 (因爲 int 類型只有 32 位),得到的結果纔是真正移位的位數。 例如, a>>33 和a>>1 的結果完全一樣,而 a>>32 的結果和 a 相同。 - 對於 long 類型的整數移位
a>>b
,當 b>64 時,總是先用 b 對 64 求餘(因爲 long 類型是 64 位), 得到的結果纔是真正移位的位數。
當進行移位運算時,只要被移位的二進制碼沒有發生有效住的數字丟失(對於正數而言,通常指被移出的位全部都是 0),不難發現左移 n 位就相當於來以 2 的 n 次方,右移 n位則是除以 2 的 n 次方。 不僅如此,進行移位運算不會改變操作數本身,只走得到了一個新的運算結果,而原來的操作數本身是不會改變的。
3.7.4 擴展後的賦值運算符
賦值運算符可與算術運算符、位移運算符結合,擴展爲功能更加強大的運算符。 擴展後的賦值運算符如下:
- +=:對於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。
只要能使用這種擴展後的賦值運算符,通常都推薦使用它們。因爲這種運算符不僅具有更好的性能,而且程序會更加健壯。 下面程序示範了+=運算符的用法。
byte a = 4;
//下面語句出錯,因爲5默認是int類型,a+5就是int類型,把int類型賦給byte類型的變量所以出錯
//a = a + 5 ;
byte b = 4 ;
//下面語句不會出現錯誤,因爲b+=125的原理是b=(byte)(b+125)
b+=125;
//最後b的輸出結果是-127,因爲byte類型範圍是-128~127,而4+125=129溢出了,
//故byte會截取低8位的129的二進制補碼10000001,而10000001最高位符號位是1,
//那麼10000001是一個負數的補碼,先減1得到其反碼10000000,再取反得到其原碼11111111,即-127
System.out.println(b);
byte c = 6;
//下面語句也不會出現錯誤,因爲c++的原理是c=(byte)(c+1);
c++;
//最後結果輸出爲7
System.out.println(c);
運行上面程序, 不難發現:若int a=1
,則a=a+ 5
和a += 5
雖然運行結果相同, 但底層的運行機制還是存在一定差異的。 因此,如果可以使用這種擴展後的運算符,則推薦使用它們。
3.7.5比較運算符
比較運算符用於判斷兩個變量或常量的大小,比較運算的結果是一個布爾值(true或false)。Java支持的比較運算符如下:
>
:大於,只支持左右兩邊操作數是數值類型。如果前面變量的值大於後面變量的值,則返回 true。>=
:大於等於, 只支持左右兩邊操作數是數值類型。如果前面變量的值大於等於後面變量的值,則返回 true。<
:小於,只支持左右兩邊操作數是數值類型。如果前面變量的值小於後面變量的值, 則返回 true。<=
:小於等於,只支持左右兩邊操作數是數值類型。如果前面變量的值小於等於後面變量的值,則返回 true。==
:等於,如果進行比較的兩個操作數都是數值類型,即使它們的數據類型不相同,只要它們的值相等,也都將返回 true。 例如 97 == 'a’返回 true, 5.0 == 5 也返回 true。 如果兩個操作數都是引用類型,那麼只有當兩個引用變量的類型具有父子關係時纔可以比較,而且這兩個引用必須指向同一個對象纔會返回 true。Java 也支持兩個 boolean 類型的值進行比較,例如,true==false將返回 false。!=
:不等於,如果進行比較的兩個操作數都是數值類型,無論它們的數據類型是否相同, 只要它們的值不相等,也都將返回 true。 如果兩個操作數都是引用類型,只有當兩個引用變量的類型具有父子關係時纔可以比較,只要兩個引用指向的不是同一個對象就會返回true。
基本類型的變量、值不能和引用類型的變量、值使用==進行比較, boolean 類型的變量、值不能與其他任意類型的變量、值使用進行比較;如果兩個引用類型之間沒有父子繼承關係,那麼它們的變量也不能使用==進行比較。
下面程序示範了比較運算符的使用:
public class ComparableOperatorTest {
public static void main(String[] args) {
//如果進行比較的兩個操作數都是數值類型,即使它們的數據類型不相同,依然可進行比較
System.out.println(5>4.0);//返回true
System.out.println(5==5.0);//返回true
System.out.println(97=='a');//返回true
System.out.println(true==false);//返回false
//創建2個ComparableOperatorTest對象,分別賦給tl和t2兩個引用
ComparableOperatorTest t1 =new ComparableOperatorTest();
ComparableOperatorTest t2 =new ComparableOperatorTest();
//tl 和 t2 是同一個類的兩個實例的引用,所以可以比較
//但t1和t2引用不同的對象,所以返回 false
System.out.println("t1是否等於t2:" + (t1 == t2)) ;
//直接將t1的值賦給t3,即讓t3指向t1指向的對象
ComparableOperatorTest t3 = t1 ;
// t1 和 t3 指向同一個對象,所以返回 true
System.out.println( "t1是否等於t3:" + ( t1 == t3)) ;
}
}
值得注意的是, Java 爲所有的基本數據類型都提供了對應的包裝類,關於包裝類實例的比較有些特殊,具體介紹可以參考 6.1 節。
3.7.6邏輯運算符
邏輯運算符用於操作兩個布爾型的變量或常量。 邏輯運算符主要有如下 6 個:
&&
:與,前後兩個操作數必須都是 true 才返回 true, 否則返回 false。&
:不短路與,作用與&&
相同,但不會短路。||
:或,只要兩個操作數中有一個是 true,就可以返回 true,否則返回 false。|
:不短路或,作用與||
相同,但不會短路。!
:非,只需要一個操作數,如果操作數爲 true,則返回 false;如果操作數爲false,則返回 true。^
:異或,當兩個操作數不同時才返回 true,如果兩個操作數相同則返回 false。
下面代碼示範了或、與、非、異或 4 個邏輯運算符的執行示意:
public class LogicOperatorTest {
public static void main(String[] args) {
//直接對 false 求非運算,將返回 true
System.out.println(!false) ;
//5>3 返回 true , '6' 轉換爲整數 54 ,'6' >10 返回 true ,求與後返回 true
System.out.println(5 > 3 && '6' > 10) ;
//4>=5 返回 false , 'c' > 'a' 返回 true。求或後返回 true
System.out.println(4 >= 51 || 'c' > 'a');
//4>=5 返回 false, ' c'> 'a' 返回 true。兩個不同的操作數求異或返回 true
System.out.println(4 >= 5 ^ 'c' > 'a' ) ;
}
}
對於|
與||
的區別,參見如下代碼:
//定義變量 a, b,併爲兩個變量賦值
int a = 5 ;
int b = 10 ;
//對 a > 4 和 b++ > 10 求或運算
if (a > 4 | b++ > 10) {
//輸出 a 的值是 5 , b 的值是 11
System.out.println( "a的值是:" + a + ", b 的值是:" + b) ;
}
執行上面程序,看到輸出 a 的值爲 5, b 的值爲 11, 這表明 b++ > 10 表達式中的b++得到了運行,但實際上沒有運行的必要,因爲 a> 4 己經返回了 true,則整個表達式一定返回 true。 再看如下代碼,只是將上面示例的不短路邏輯或改成了短路邏輯或:
//定義變量 c,d,併爲兩個變量賦值
int c=5;
int d=10;
//c>4 || d++ >10求或運算
if(c>4 || d++ >10){
//輸出c的值是5, d的值是10
System.out.println( "c的值是:" + c + ", d 的值是:" + d) ;
}
上面代碼執行的結果是: c 的值爲 5,而 d 的值爲 10。
對比兩段代碼, 後面的代碼僅僅將不短路或改成短路或, 程序最後輸出的d值不再是 11,這表明表達式d++ > 10
沒有獲得執行的機會。 因爲對於短路邏輯或||
而言,如果第一個操作數返回 true,||
將不再對第二個操作數求值,直接返回 true。不會計算d++>10
這個邏輯表達式,因而d++
沒有獲得執行的機會。 因此,最後輸出的 d 值爲 10。而不短路或|
總是執行前後兩個操作數(或表達式)。
&
與&&
的區別與此類似:&
總會計算前後兩個操作數,而&&
先計算左邊的操作數,如果左邊的操作數爲 false,則直接返回 false,根本不會計算右邊的操作數。
3.7.7三目運算符
三目運算符只有一個:?:
, 三目運算符的語法格式如下:
(expression) ? if-true-statement : if-false-statement;
三目運算符的規則是: 先對邏輯表達式 expression 求值,如果邏輯表達式返回 true,則返回第二個操作數的值,如果邏輯表達式返回 false,則返回第三個操作數的值。 看如下代碼:
String string=5>3? "5大於3" : "5不大於3";
System.out.println(string);
大部分時候, 三目運算符都是作爲 if else 的精簡寫法。因此,如果將上面代碼換成 if else 的寫法, 則代碼如下:
String str2 = null;
if (5 > 3)
str2 = " 5 大於 3 ";
else {
str2 = " 5 不大於 3 ";
}
System.out.println(str2);
這兩種代碼寫法的效果是完全相同的。 三日運算符和 if else 寫法的區別在於:if後的代碼塊可以有多個語句, 但三目運算符是不支持多個語句的。
三目運算符可以嵌套,嵌套後的三目運算符可以處理更復雜的情況,如下程序所示:
int a = 11;
int b = 12 ;
//三目運算符支持嵌套
System.out.println(a>b? "a大於b" : (a<b? "a小於b" : "a等於b") ) ;
上面程序中粗體字代碼是一個由三目運算符構成的表達式,這個表達式本身又被嵌套在三目運算符中。 通過使用嵌套的三目運算符,即可讓三目運算符處理更復雜的情況。
3.7.8運算符的結合性和優先級
所有的數學運算都認爲是從左向右運算的, Java 語言中大部分運算符也是從左向右結合的,只有單目運算符、賦值運算符和三目運算符例外,其中,單目運算符、賦值運算符和三日運算符是從右向左結合的,也就是從右向左運算。
乘法和加法是兩個可結合的運算,也就是說,這兩個運算符左右兩邊的操作數可以互換位置而不會影響結果。
運算符有不同的優先級,所謂優先級就是在表達式運算中的運算順序。下表列出了包括分隔符在內的所有運算符的優先級順序,上一行中的運算符總是優先於下一行的。
根據上表中運算符的優先級,下面分析一下int a = 3;int b = a + 2 * a;
語句的執行過程。 程序先執行 2*a 得到 6,再執行 a+6 得到 9。 如果使用()
就可以改變程序的執行順序,例如int b = (a + 2) * a
, 則先執行 a+2 得到結果 5,再執行 5*a 得到 15。 在上表中還提到了兩個類型相關的運算符instanceof
和(type)
,這兩個運算符與類、繼承有關,此處不作介紹,在第5章將有更詳細的介紹。
因爲Java運算符存在這種優先級的關係,因此經常看到有些學生在做SCJP,或者某些公司的面試題,有如下Java 代碼:int a = 5; int b = 4; int c = a++ - --b * ++a / b-- >>2 % a--;
, c 的值是多少?這樣的語句實在太恐怖了,即使多年的老程序員看到這樣的語句也會眩暈。
這樣的代碼只能在考試中出現,如果筆者帶過的 team 裏有 member 寫這樣的代碼,恐怕他馬上就得走人了,因爲他完全不懂程序開發:源代碼就是一份文檔,源代碼的可讀性比代碼運行效率更重要。
因此在這裏要提醒讀者:
- 不要把一個表達式寫得過於複雜,如果一個表達式過於複雜,則把它分成幾步來完成。
- 不要過多地依賴運算符的優先級來控制表達式的執行)11頁序,這樣可讀性太差,儘量使用o來控制表達式的執行順序。
參考文獻:
[1]Java註釋:單行、多行和文檔註釋
[2]What are () {} and [] called?
[3]數據類型之二:字面量(literal)