《Java核心技術第十版·卷1》第三章知識點總結

如果有需要這本書(第一卷)的中文版PDF,可以留下郵箱~

更加醒目的數字表示方法

Java7開始,可以給數字字面量添加下劃線,讓數字更加易讀,Java編譯器會去除這些下劃線。

// 給數值加下劃線, 不影響數值本身
int num = 100_000_000;
System.out.println(num); // 100000000

浮點數的精度問題

浮點數值不適用於無法接受舍入誤差的金融計算中。

System.out.println( 2.0-1.1 ) 將打印出0.8999999999999999, 而不是0.9。

這種舍入誤差的主要原因是浮點數值採用二進制系統表示, 而在二進制系統中無法精確地表示分數 1/10。這就好像十進制無法精確地表示分數 1/3—樣。

如果在數值計算中不允許有任何舍入誤差,就應該使用BigDecimal類

System.out.println(2.0-1.1); // 0.8999999999999999

在循環中,檢測兩個浮點數是否相等需要格外小心。下面的for循環可能永遠不會結束,由於舍入的誤差,最終可能得不到精確值。

for (double x = 0; x != 10; x += 0.1){...}

數學相關

下面是用於表示溢出和出錯情況的三個特殊的浮點數值和其對應的常量(實際應用中很少用到):

  • 正無窮大: Double.POSITIVE_INFINITY
  • 負無窮大: Double.NEGATIVE_INFINITY
  • NaN(不是一個數字): Double.NaN

正整數除以0的結果爲正無窮大。計算 0/0 或者負數的平方根結果爲 NaN

// 正無窮大
System.out.println(Double.POSITIVE_INFINITY); // Infinity
// 負無窮大
System.out.println(Double.NEGATIVE_INFINITY); // -Infinity
// 非數值
System.out.println(Double.NaN); // NaN
// 一個正整數除以0爲正無窮大
System.out.println(Double.isInfinite(1.0 / 0.0)); // true
// 0/0或負數的平方根爲NaN
System.out.println(Double.isNaN(0.0 / 0.0)); // true
// 不能這樣檢測一個特定值是否等於 Double.NaN:
if (x == Double.NaN) // 永遠都是false
// 所有“非數值”的值都認爲是不相同的。然而,可以使用 Double.isNaN 方法:
if (Double.isNaN(Double.NaN)) // 永遠都是false

一些數學符號和函數

Math類提供了一些常用的數學函數和常量

// Unicode轉義字符, 表示希臘字母π
System.out.println('\u03C0'); // π
// 表示π的近似值
System.out.println(Math.PI); // 3.141592653589793
// 平方根
System.out.println(Math.sqrt(4)); // 2.0
// 冪運算
System.out.println(Math.pow(3, 2)); // 9.0

註釋的語法錯誤

因爲**\u開頭**的字符表示Unicode轉義字符,列如: 希臘字母π (’\u03c0’);這樣造成了==註釋中出現的\u可能會帶來錯誤==。如:

// 經過測試, 下面這個註釋在IDEA中不會報錯
// \u00A0 is a newline

這樣會產生一個語法錯誤,因爲讀程序時\u00A0會替換爲一個換行符。

類似地還有下面這個註釋:

// 經過測試, 下面這個註釋在IDEA中獨佔一行時不會報錯
// Look inside c:\users

也會產生一個語法錯誤,因爲\u後面並未跟着4個十六進制數。

自增/自減運算符

建議不要在表達式中使用 ++/-- (自增運算符/自減運算符),因爲這樣的代碼很容易讓人困惑,而且可能會帶來煩人的bug。

// 不建議類似如下的操作
int m = 7;
int n = 7;
int a = 2 * ++m; // 現在a爲16, m爲8
int b = 2 * n++; // 現在b爲14, n爲8

短路特性

&& (與) 和 || (或) 運算符是按照“短路”方式來求值的:

如果第一個操作已經能夠確定表達式的值,第二個操作數就不必計算了。

& 和 | 運算符也會得到一個布爾值,這些運算符與&&和||運算符很類似,但是 & 和 | 運算符不採用“短路“方式來求值,也就是說,得到計算結果之前多個操作數都需要計算。

// 如果表達式1的結果爲false, 那麼結果不可能爲true。因此表達式2就不必計算了。
表達式1 && 表達式2
// 如果表達式1的結果爲true, 那麼結果一定爲true。因此表達式2也不必計算。
表達式1 || 表達式2

利用短路特性可以避免一些錯誤,如:

// 如果x等於0, 那麼第二部分就不會計算。因此, 如果x爲 0, 也就不會計算1/x, 除以0的錯誤就不會出現。
x != 0 && 1/x > x+y
// 如果是&運算符, 則這行代碼會報錯
x != 0 & 1/x > x+y // java.lang.ArithmeticException: / by zero

字符串的比較

在Java中使用equals方法來檢測兩個字符串是否相等

需要注意的是,==比較的是內存地址,而equals方法比較的是字面量。

調用equals方法時有一個小技巧:讓字面量來調用該方法,這樣就算變量爲null,也不會造成空指針異常。

String greeting = "Hello";
// equals方法比較字符串的字面量
System.out.println("Hello".equals(greeting)); // true
// 可以用compareTo方法代替equals
System.out.println("Hello".compareTo(greeting) == 0); // true
// == 運算符比較字符串內存地址
System.out.println("Hello" == greeting); // 可能是true
System.out.println(greeting.substring(0,3) == "Hel") // 可能是false

一定不要使用==運算符檢測兩個字符串是否相等! 這個運算符只能夠確定兩個字串是否放置在同一個位置上。當然, 如果字符串放置在同一個位置上, 它們必然相等。但是,完全有可能將內容相同的多個字符串的拷貝放置在不同的位置上。

如果虛擬機始終將相同的字符串共享, 就可以使用 == 運算符檢測是否相等。但實際上只有字符串常量是共享的,而 + 或 substring 等操作產生的結果並不是共享的。因此,千萬不要使用 == 運算符測試字符串的相等性, 以免在程序中出現糟糕的 bug。

StringBuilder類與StringBuffer類

這兩個類的API是相同的。

StringBuilder:效率高,單線程

StringBuffer:效率低,多線程

如果所有字符串在一個單線程中編輯(絕大部分情況是這樣),則應該使用StringBuilder類。

安全的輸入密碼方式

因爲輸入是可見的,所以Scanner類不適合從控制檯讀取密碼。JavaSE 6 特別引入了Console類實現這個目的。

採用 Console 對象處理輸入不如採用 Scanner 方便。每次只能讀取一行輸入, 而沒有能夠讀取一個單詞或一個數值的方法。

經測試,需要在命令行狀態下使用Console類的方法,而不能在IDE中使用,否則會報空指針異常。

// 需要在控制檯中使用,可以在輸入密碼時不顯示輸入的內容
Console console = System.console();
String username = console.readLine("請輸入賬號: ");
// 爲了安全起見, 返回的密碼存放在一維字符數組中, 而不是字符串中。
char[] pwdChars = console.readPassword("請輸入密碼: ");

沿用C語言的格式化輸出

Java SE 5.0 沿用了 C 語言庫函數中的 printf方法。

// printf方法需要手動換行,'\n'爲換行符
System.out.printf("[%s, %d cups of tea.]\n", "Hello", 3); // [Hello, 3 cups of tea.]
// 格式化輸出88.888888: 總共7位包括小數點後3位,如果長度超出,則用空格填充
System.out.printf("[%7.3f]\n", 88.888888); // [ 88.889]
// 格式化輸出8888888888(10個8): 保留位小數,並用逗號分組
System.out.printf("%,.3f\n", 8888888888.88); // [8,888,888,888.880]

文件輸入與輸出

要想對文件進行讀取,就需要一個用File對象構造一個Scanner對象。如果文件名包含反斜槓符號,就需要在每個反斜槓之前再加一個額外的反斜槓

// 這裏指定了UTF-8字符編碼,如果省略字符編碼,則會使用運行這個Java程序的機器的“默認編碼”,如果在不同的機器上運行這個程序,可能會有不同的表現。
Scanner fileScanner = new Scanner(Paths.get("src/1.txt"), "UTF-8");
// 循環讀取文件內容
while (fileScanner.hasNext()) {
	System.out.println(fileScanner.nextLine());
}

要想寫入文件,就需要構造一個PrintWriter對象。在構造器中,只需要提供文件名。如果文件不存在,自動創建該文件。可以像輸出到System.out一樣使用print、println以及printf命令。

// 寫入文件 (覆蓋寫入)
PrintWriter printWriter = new PrintWriter("src/1.txt", "UTF-8");
printWriter.println("The first new line");
printWriter.println("The second new line");
// 調用這些方法並不是直接把數據寫入文件,而是先寫入了緩衝區,需要調用flush方法將緩衝區的數據寫入文件。
printWriter.flush();

麻煩的相對路徑

相對路徑很容易寫錯,因爲它是由IDE控制,所以在寫相對路徑時可以先查看當前的路徑位置(JVM的運行位置)。

String dir = System.getProperty("user.dir");

“塊”中的變量定義

在 C++ 中,可以在嵌套的塊中重定義一個變量。在內層定義的變量會覆蓋在外層定義的變量。這樣,有可能會導致程序設計錯誤,因此在Java中不允許這樣做。

// Java中不允許類似如下的操作
int k = 0;
{
    int k = 1;
}

不受待見的goto語句

Java的設計者將goto作爲保留字,但實際上並不能使用他。(所以在IDE中這個關鍵字會高亮顯示,雖然他不能被使用,但變量名也不可以取爲"goto")

儘管goto語句被認爲是一種拙劣的程序設計風格,但是偶爾使用它跳出循環也是有益處的,特別是複雜的嵌套循環。

Java提供了一種帶標籤的break語句,其作用與goto語句很相似。

帶標籤的break語句

Java提供了一種帶標籤的break語句,用於跳出多重嵌套的循環語句

注意:標籤必須放在希望跳出的最外層循環之前,並且必須緊跟一個冒號!

事實上,標籤可以應用到任何“塊”語句中,不過不提倡使用這種方式

下面的代碼在輸出 “3,0” 之後便緊跟着輸出了“循環結束”

// 帶標籤的break語句,需要把標籤放在代碼塊外,並緊貼代碼塊
target:
for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
        System.out.println(i + "," + j);
        if (i == 3) {
            // 直接跳出嵌套循環到target位置,並且不會再次執行該代碼塊裏的內容
            break target;
        }
    }
}
System.out.println("循環結束");

高精度的大數值類型

如果基本的整數和浮點數精度不能夠滿足需求,那麼可以使用java.math包中的兩個很有用的類:BigInteger和BigDecimal

這兩個類可以處理包含任意長度數字序列的數值。BigInteger類實現了任意精度的整數運算,BigDecimal實現了任意精度的浮點數運算。

使用靜態的valueOf方法可以將普通的數值轉換爲大數值

// 使用valueOf將普通的數值轉換爲大數值
BigInteger bigInteger = BigInteger.valueOf(100);
BigDecimal bigDecimal = BigDecimal.valueOf(2.2);

*對於大數值類型的計算,需要使用大數值類中的add和multiply等方法,不可以使用算術運算符(如:+ 和 )

System.out.println(bigInteger.multiply(BigInteger.valueOf(2))); // 200

普通浮點數和大數值浮點數在計算時的區別

// 浮點數運算的區別
System.out.println(bigDecimal.add(BigDecimal.valueOf(1.1))); // 3.3
System.out.println(2.2 + 1.1); // 3.3000000000000003

C++的運算符重載

與 C++不同,Java沒有提供運算符重載功。程序員無法重定義 + 和 * 運算符,使其應用於BigInteger類的add和multiply運算。Java語言的設計者確實爲字符串的連接重載了 + 運算符,但沒有重載其他的運算符,也沒有給Java程序員在自己的類中重載運算符的機會。

// Java字符串中的“+”運算符,也就是字符串拼接
System.out.println("Hello, " + "world!"); // Hello, world!

數組聲明的不同形式

// Java風格的數組聲明
int[] a;
// C風格的數組聲明
int a[];

增強的for循環

Java有一種功能很強的循環結構,可以用來一次處理數組中的每個元素(其他類型的元素集合亦可)而不必爲指定下標值而分心。

這種循環結構被稱爲:foreach循環 或 增強for循環

**語句格式爲:for (variable : collection) {statement} **

注意:foreach中的變量只是可迭代對象中元素的引用,也就是暫存元素;所以不能通過修改foreach循環中變量的值來對實際可迭代對象中的元素產生影響。

定義一個變量用於暫存集合中的每一個元素,並執行相應的語句(當然,也可以是語句塊)。

// 下面兩個循環的輸出結果相同
int[] array = {1,2,3};
for (int x: array){
    System.out.println(x);
}
for (int i = 0; i < array.length; i++){
     System.out.println(array[i]);
}

數組拷貝

在Java中,允許將一個數組變量拷貝給另一個數組變量。這時,兩個變量將引用同一個數組(類似指針指向)。

如果希望將一個數組的所有值拷貝到一個新的數組中去,就要使用Arrays類的copyOf方法(只是值的複製)。

// 兩個變量引用同一個數組, 也就是其中一個改變時, 另一個也改變
int[] a = {1,2,3};
int[] b = a;
b[0] = 100;
System.out.println(Arrays.toString(a)); // [999, 2, 3]
System.out.println(Arrays.toString(b)); // [999, 2, 3]
// 把a數組中的值賦值給b, 並指定b數組的長度, 其中一個改變時, 另一個不變
int[] a = {1,2,3};
int[] b = Arrays.copyOf(a, a.length);
b[0] = 100;
System.out.println(Arrays.toString(a)); // [1, 2, 3]
System.out.println(Arrays.toString(b)); // [999, 2, 3]
// 如果指定的長度小於原石數組的長度, 則只拷貝最前面的數據元素。
int[] c = Arrays.copyOf(a, a.length-1);
System.out.println(Arrays.toString(c)); // [1, 2]

其他

  1. 在Java中,所有的數值類型所佔據的字節數量與平臺無關。

  2. Java沒有任何無符號(unsigned)形式的int、long、short或byte類型。

  3. 強烈建議不要在程序中使用char類型,除非確實需要處理UTF-16代碼單元。最好將字符串作爲抽象數據類型處理。

  4. Java中的final關鍵字指示常量,表示這個變量只能被賦值一次。習慣上,常量名使用全大寫

  5. 整數被 0 除將會產生一個異常, 而浮點數被 0 除將會得到無窮大NaN 結果。

    System.out.println(1/0); // java.lang.ArithmeticException: / by zero
    System.out.println(1.0/0); // Infinity
    
  6. **Java 沒有內置的字符串類型, 而是在標準 Java 類庫中提供了一個預定義類,很自然地叫做 String。**每個用雙引號括起來的字符串都是String類的一個實例。

  7. ==字符串變量指向內存中相應的位置。如果複製一個字符串變量, 原始字符串與複製的字符串共享相同的字符。==總而言之,Java 的設計者認爲共享帶來的高效率遠遠勝過於提取、 拼接字符串所帶來的低效率。

  8. Java會自動進行垃圾回收,如果一塊內存不再使用了,系統最終會將其回收。

  9. 不推薦使用switch語句,而用if … else if … else 代替,因爲某個case分支語句下可能會忘記添加break語句,這種情況相當危險。

  10. 從 JavaSE 7開始,case標籤還可以是字符串字面量。

  11. 創建一個數字數組時,所有元素都初始化爲0。boolean數組的元素會初始化爲false。對象數組的元素初始化爲null,表示這些元素還未存放任何對象。

  12. 數組長度不可變,集合長度可變。

  13. 在Java應用程序的main方法中,程序名沒有存儲在args數組中。

    // 命令行參數, 執行後args[0]是"-h", 而不是"Student"或"java"
    java Student -h world
    
  14. Math.random方法將返回一個0到1之間 (包含0、不包含1) 的隨機浮點數。用n乘以這個浮點數,就可以得到從0到 n-1 之間的一個隨機數。

    // 獲得一個從0到 n-1 之間的一個隨機數r
    int r = (int) (Math.random() * n);
    
  15. Java實際上沒有多維數組,只有一維數組。多維數組被解釋爲“數組的數組”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章