2020.8.5Java 核心技術第三章後
字串
子串
String類的 substring方法可以從一個較大的字串提取出一個子串。例如:
String greeting = "Hello";
String s = greeting.substring(0,3);
建立了一個由字元“ Hel” 組成的字串。
拼接
String a="Hello "; String b="World!"; String c=a+b;//c="Hello Wrold!" int age=18; String My_age="age= "+age//My_age="age= 18" //多個字串進行拼接時,使用靜態方法String.join(str0,str1,str2,...);str0為定界符,如:System。out.println(String.join(" / ", "S", "M", "L", "XL"));//輸出結果為:S / M / L / XL
不可變字串
String類沒有提供用於修改字串的方法6 如果希望將 greeting 的內容修改為“ Help!”, 不能直接地將 greeting的最後兩個位置的字元修改為 ‘ p’ 和 ‘ !' 、這對於 C 程式設計師來說,將會感到無從下手 。如何修改這個字串呢? 在 Java中實現這項操作非常容易。首先提取需 要的字元, 然後再拼接上替換的字串:
greeting = greeting.substring(0, 3) + "p!";
上面這條語句將 greeting 當前值修改為“ Help !”。
檢測字串是否相等
可以使用 equals方法檢測兩個字串是否相等。對於表示式:
s.equals(t)
如果字串 s 與字串 t 相等, 則返回 true ; 否則, 返回 false。需要注意,s與 t 可以是字串變數, 也可以是字串字面量。
空串與 Null 串
空串 "" 是長度為 0 的字串。可以呼叫以下程式碼檢查一個字串是否為空:
if (str.length()= 0)
或
if (str.equals(""))
空串是一個 Java 物件, 有自己的串長度(0 ) 和內容(空) 。不過,String 變數還可以存 放一個特殊的值, 名為 null, 這表示目前沒有任何物件與該變數關聯。要檢查一個字串是否為 null, 要使用以下條件:
if (str == null)
有時要檢查一個字串既不是 null 也不為空串,這種情況下就需要使用以下條件:
if (str != null && str.lengthO != 0)
首先要檢查str 不為 null。 如果在一個 null值上呼叫方法, 會出現 錯誤。
碼點和程式碼單元
Java 字串由 char 值序列組成。char 資料型別是一 個採用 UTF-16 編碼表示 Unicode 碼點的程式碼單元。大多數的常用 Unicode 字元使用一個代 碼單元就可以表示,而輔助字元需要一對程式碼單元表示。
length 方法將返回採用 UTF-16 編碼表示的給定字串所需要的程式碼單元數量。例如:
String greeting = "Hello"; int n = greeting.length() ; // is 5.
要想得到實際的長度,即碼點數量,可以呼叫:
int cpCount = greeting.codePointCount(0, greeting.length());
呼叫s.charAt(n) 將返回位置 n 的程式碼單元,n 介於 0 ~ s.length()-l 之間。例如:
char first = greeting.charAtO); // first is 'H' char last = greeting.charAt(4); // last is ’o’
要想得到第 i 個碼點,應該使用下列語句
int index = greeting.offsetByCodePoints(0,i); int cp = greeting.codePointAt(index);[注] 類似於 C 和 C++, Java 對字串中的程式碼單元和碼點從 0 開始計數。
使用 UTF-16 編碼表示字元⑪(U+1D546) 需要兩個程式碼單元。呼叫
char ch = sentence.charAt(i );
返回的不是一個空格,而是⑪的第二個程式碼單元。為了避免這個問題, 不要使用 char 型別。
這太底層了。 如果想要遍歷一個字串,並且依次査看每一個碼點, 可以使用下列語句:
int i=0; String sentence="Hello world!"; int cp = sentence.codePointAt(i); if (Character.isSupplementaryCodePoint(cp)) { i += 2; }else{ i++; }
可以使用下列語句實現回退操作:
int i=0; String sentence="Hello world!"; int cp = sentence.codePointAt(i); if (Character.isSupplementaryCodePoint(cp)) { i += 2; }else{ i++; }
顯然, 這很麻煩。更容易的辦法是使用 codePoints 方法, 它會生成一個 int 值的“ 流”, 每個 int 值對應一個碼點。可以將它轉換為一個數組,再完成遍歷。
int[] codePoints = str.codePoints().toArray();
反之,要把一個碼點陣列轉換為一個字串, 可以使用建構函式。
String str = new String(codePoints, 0, codePoints.length);
構建字串
有些時候, 需要由較短的字串構建字串, 例如, 按鍵或來自檔案中的單詞。採用字 符串連線的方式達到此目的效率比較低。每次連線字串, 都會構建一個新的 String 物件, 既耗時, 又浪費空間。使用 StringBuildei•類就可以避免這個問題的發生。
如果需要用許多小段的字串構建一個字串, 那麼應該按照下列步驟進行。 首先, 構 建一個空的字串構建器:
StringBuilder builder = new StringBuilder();
當每次需要新增一部分內容時, 就呼叫 append 方法。
builder.append(ch); // appends a single character bui1der.append(str); // appends a string
在需要構建字串時就凋用 toString 方法, 將可以得到一個 String 物件, 其中包含了構建器 中的字元序列。
String completedString = builder.toString();
輸入輸出
讀取輸入
前面已經看到,列印輸出到“ 標準輸出流”(即控制檯視窗)是一件非常容易的事情,只要 呼叫 System.out.println 即可。然而,讀取“ 標準輸人流” System.in就沒有那麼簡單了。要想通 過控制檯進行輸人,首先需要構造一個 Scanner 物件,並與“ 標準輸人流” System.in 關聯。
Scanner in = new Scanner(System.in); System.out.print("What is your name? "); String name = in.nextLine();//讀取有空格的字串 String firstName = in.next();//讀取沒有空格的字串 System.out.print("How old are you? "); int age = in.nextlnt();//讀取整型資料 int meny = in.nextlnt();//讀取浮點型資料
格式化輸出
int a=11; char b='m'; String c="abc"; float d=12.3333f; System.out.println(a); System.out.print(a); System.out.printf("a=%d,b=%c,c=%s,d=%.2f",a,b,c,d);
可以使用靜態的 String.format 方法建立一個格式化的字串, 而不列印輸出:
String message = String.format("Hello, %s. Next year, you'll be %d", name, age);
列印當前的日期和時間:
System.out.printf("%tc", new java.util.Date());
System.out.printf("%1$s %2$tB %2$te, %2$tY", "Due date:", new java.util.Date()); //打印出 Due date: 八月 5, 2020[注] 參教索引值從 1開始, 而不是從 0 開 始, %1$...對 第 1 個 參 數 格 式 化。 這 就 避 免 了與 0 標誌混淆。
檔案輸入與輸出
要想對檔案進行讀取,就需要一個用 File 物件構造一個 Scanner 物件,如下所示:
Scanner in = new Scanner(Paths.get("D:\\myfile.txt"), "UTF-8");
如果檔名中包含反斜槓符號,就要記住在每個反斜槓之前再加一個額外的反斜槓:
"D:\\myfile.txt"
現在,就可以利用前面介紹的任何一個 Scanner方法對檔案進行讀取。
要想寫入檔案, 就需要構造一個 PrintWriter 物件。在構造器中,只需要提供檔名:
PrintWriter out = new PrintWriter("D:\myfile.txt", "UTF-8");
如果檔案不存在,建立該檔案。可以像輸出到 System.out—樣使用 print、 println 以及 printf 命令。
public static strictfp void main(String args[]) throws IOException{ PrintWriter out = new PrintWriter("D:\\myfile.txt", "UTF-8"); String string="I Love Learning!"; out.println(string);//將string字串列印在myfile.txt檔案中 out.close(); Scanner in = new Scanner(Paths.get("D:\\myfile.txt"), "UTF-8"); String s=in.nextLine();//從myfile.txt檔案中讀取包含空格的字串,並將其賦值給變數s System.out.println(s); in.close(); }[警告] 可以構造一個帶有字串引數的 Scanner, 但 這 個 Scanner 將字串解釋為資料, 而不是檔名。例如, 如果呼叫: Scanner in = new Scannerr(“myfile.txt"); // ERROR? 這個 scanner 會將引數作為包含 10 個字元的資料: ‘ m’, ‘ y’, ‘ f’ 等。在這個示 例中所顯示的並不是人們所期望的效果。
省指定一個相對檔名時,例如, “ myfile.txt”, “ mydirectory/myfile.txt” 或“ ../myfile.txt”, 檔案位於 Java 虛擬機器啟動路徑的相對位置 , 如果在命令列方式下用下列命令啟動程式:
java MyProg
啟動路徑就是命令直譯器的當前路後。 然而,如果使用整合開發環境, 那麼啟動路 徑將由 IDE 控制。 可以使用下面的呼叫方式找到路徑的位置:
String dir = System.getProperty("user.dir");
如果覺得定位檔案比較煩惱, 則可以考慮使用絕對路徑, 例如: “ c:\mydirectory\ myfile.txt” 或者“ /home/me/mydirectory/myfile.txt” 。
當採用命令列方式啟動一個程式時, 可以利用 Shell 的重定向語法將任意檔案關聯 到 System.in 和 System.out:
java MyProg <D:\myfile.txt > output.txt
這樣,就不必擔心處理 IOException 異常了。
控制流程
在深入學習控制結構之前,需要了解塊(block) 的概念。
塊(即複合語句)是指由一對大括號括起來的若干條簡單的 Java語句。塊確定了變數的作 用域。一個塊可以巢狀在另一個塊中。下面就是在 main方法塊中巢狀另一個語句塊的示例。
public static void main(String[] args) { int n; ... { int k; ... } // k is only defined up to here }
但是,不能在巢狀的兩個塊中宣告同名的變數。例如,下面的程式碼就有錯誤,而無法通過編譯:
public static void main(String[] args) { int n; ... { int n;//Error can't redefine n in inner block int k; ... } }[注] 在 C++ 中, 可以在巢狀的塊中重定義一個變數。在內層定義的變數會覆蓋在 外層定義的變數。這樣,有可能會導致程式設計錯誤, 因此在 Java 中不允許這樣做。
條件語句
在 Java 中,條件語句的格式為
if (condition) statement
這裡的條件必須用括號括起來。
[注] 使用塊 (有時稱為複合語句)可以在 Java 程式結構中原本只能放置一條(簡單)語 句的地方放置多條語句。if (yourSales >= target) { performance = "Satisfactory"; bonus = 100 + 0.01* (yourSales - target); } else { performance = "Unsatisfactory"; bonus = 0; }
其中 else 部分是可選的。else 子句與最鄰近的 if 構成一組。
if (yourSales >= 2 * target) { performance = "Excellent"; bonus = 1000; } else if (yourSales >= 1.5 * target) { performance = "Fine"; bonus = 500; } else if (yourSales >= target) { performance = "Satisfactory"; bonus = 100; } else { System.out.println("You're fired"); }
迴圈
當條件為 true 時,while 迴圈執行一條語句(也可以是一個語句塊) 。一般格式為
while (condition) statement
如果開始迴圈條件的值就為 false, 則 while 迴圈體一次也不執行,如下圖所示:
程式將計算需要多長時間才能夠儲存一定數量的退休金,假定每年存 人相同數量的金額,而且利率是固定的。
import java.util.*; /** * This program demonstrates a <code>while</code> loop. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class Retirement { public static void main(String[] args) { // read inputs Scanner in = new Scanner(System.in); System.out.print("How much money do you need to retire? "); double goal = in.nextDouble(); System.out.print("How much money will you contribute every year? "); double payment = in.nextDouble(); System.out.print("Interest rate in %: "); double interestRate = in.nextDouble(); double balance = 0; int years = 0; // update account balance while goal isn't reached while (balance < goal) { // add this year's payment and interest balance += payment; double interest = balance * interestRate / 100; balance += interest; years++; } System.out.println("You can retire in " + years + " years."); } }
while 迴圈語句首先檢測迴圈條件。因此, 迴圈體中的程式碼有可能不被執行 t 如果希望 迴圈體至少執行一次, 則應該將檢測條件放在最後。使用 do/while 迴圈語句可以實現這種操 作方式。它的語法格式為:
do statement while (condition);
這種迴圈語句先執行語句 (通常是一個語句塊), 再檢測迴圈條件;然後重複語句’ 冉檢測循 環條件, 首先計算退休賬戶中的餘額,然後再詢問是否打算退休:
import java.util.*; /** * This program demonstrates a <code>do/while</code> loop. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class Retirement2 { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("How much money will you contribute every year? "); double payment = in.nextDouble(); System.out.print("Interest rate in %: "); double interestRate = in.nextDouble(); double balance = 0; int year = 0; String input; // update account balance while user isn't ready to retire do { // add this year's payment and interest balance += payment; double interest = balance * interestRate / 100; balance += interest; year++; // print current balance System.out.printf("After year %d, your balance isJava %,.2f%n", year, balance); // ask if ready to retire and get input System.out.print("Ready to retire? (Y/N) "); input = in.next(); } while (input.equals("N")); } }
確定迴圈
for 迴圈語句是支援迭代的一種通用結構, 利用每次迭代之後更新的計數器或類似的變數 來控制迭代次數。下面的程式將數字 1 ~ 10 輸出到螢幕上。
for (int i = 1 ;i <= 10; i++) System.out.println(i);[注] 在迴圈中,檢測兩個浮點數是否相等需要格外小心。下面的 for 迴圈 for (double x = 0; x != 10; x += 0.1) . . . 可能永遠不會結束。 由於舍入的誤差, 最終可能得不到精確值。 例如, 在上面的 迴圈中, 因為 0.1 無法精確地用二進位制表示, 所以,x 將從 9.999 999 999 999 98 跳到 10.099 999 999 999 98。
下面程式是一個應用 for 迴圈的典型示例。這個程式用來計算抽獎中獎的概率。例 如, 如果必須從 1 ~ 50 之間的數字中取 6 個數字來抽獎,那麼會有 (50x 49 x 48 x 47x 46 x 45)/ (1 x 2 x 3 x 4 x 5x 6) 種可能的結果,所以中獎的機率是 1/15890700。祝你好運!
import java.util.*; /** * This program demonstrates a <code>for</code> loop. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class LotteryOdds { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("How many numbers do you need to draw? "); int k = in.nextInt(); System.out.print("What is the highest number you can draw? "); int n = in.nextInt(); /* * compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k) */ int lotteryOdds = 1; for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds * (n - i + 1) / i; System.out.println("Your odds are 1 in " + lotteryOdds + ". Good luck!"); } } [注] “ 通用 for 迴圈”(又稱為 for each 迴圈) , 這是 Java SE 5.0 新增 加的一種迴圈結構。
多重選擇:switch 語句
switch語句將從與選項值相匹配的 case標籤處開始執行直到遇到 break語句,或者執行到 switchi吾句的結束處為止。如果沒有相匹配的 case 標籤, 而有 default 子句, 就執行這個子句。
switch (key) { case value: break; default: break; }[警告] 有可能觸發多個 case 分支。如果在 case 分支語句的末尾沒有 break 語句, 那麼就 會接著執行下一個 case 分支語句。這種情況相當危險, 常常會引發錯誤。 為此,我們在 程式中從不使用 switch 語句 如果你比我們更喜歡 switch 語句, 編譯程式碼時可以考慮加上-Xlint:fallthrough 選項, 如下所示: javac -Xlint:fallthrough Test.java 這樣一來, 如果某個分支最後缺少一個 break 語句, 編譯器就會給出一個警告訊息。 如果你確實正是想使用這種“ 直通式”(fallthrough) 行為, 可以為其外圍方法加一個 標註@SuppressWamings("fallthrough")。 這樣就不會對這個方法生成警告了 。 (標註是為編譯器或處理 Java 原始檔或類檔案的工具提供資訊的一種機制。 )
case標籤可以是:
•型別為 char、byte、short 或 int 的常量表達式。
•列舉常量。
•從 Java SE 7開始, case標籤還可以是字串字面量。
中斷控制流程語句
goto;//不建議使用
break;
continue;
大數值
如果基本的整數和浮點數精度不能夠滿足需求, 那麼可以使用java.math 包中的兩個 很有用的類:Biglnteger 和 BigDecimal 這兩個類可以處理包含任意長度數字序列的數值。 Biglnteger類實現了任意精度的整數運算, BigDecimal 實現了任意精度的浮點數運算。
使用靜態的 valueOf方法可以將普通的數值轉換為大數值:
Biglnteger a = Biglnteger.valueOf(100);
遺憾的是,不能使用人們熟悉的算術運算子(如:+ 和 *) 處理大數值。 而需要使用大數 值類中的 add 和 multiply 方法。
Biglnteger c = a.add(b); // c = a + b Biglnteger d = c.nultipiy(b.add(Biglnteger.valueOf(2))); // d = c * (b + 2)
中彩概率程式的改進, 使其可以採用大數值進行運算。 假設你被邀請參加抽獎活動, 並從 490 個可能的數值中抽取 60 個, 這個程式將會得到中彩 概率 1/71639584346199555741511622254009293341171761278926349349335101345948110466 8848。祝你好運!
import java.math.*; import java.util.*; /** * This program uses big numbers to compute the odds of winning the grand prize in a lottery. * @version 1.20 2004-02-10 * @author Cay Horstmann */ public class BigIntegerTest { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("How many numbers do you need to draw? "); int k = in.nextInt(); System.out.print("What is the highest number you can draw? "); int n = in.nextInt(); /* * compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k) */ BigInteger lotteryOdds = BigInteger.valueOf(1); for (int i = 1; i <= k; i++) lotteryOdds = lotteryOdds.multiply(BigInteger.valueOf(n - i + 1)).divide( BigInteger.valueOf(i)); System.out.println("Your odds are 1 in " + lotteryOdds + ". Good luck!"); } }
數 組
陣列是一種資料結構, 用來儲存同一型別值的集合。通過一個整型下標可以訪問陣列中 的每一個值。
可以使用下面兩種形式宣告陣列
int[] a;
或
int a[];
大多數 Java 應用程式設計師喜歡使用第一種風格, 因為它將型別 int[] (整型陣列)與變 量名分開了。
for each 迴圈
Java 有一種功能很強的迴圈結構, 可以用來依次處理陣列中的每個元素(其他型別的元 素集合亦可)而不必為指定下標值而分心。 這種增強的 for 迴圈的語句格式為:
for (variable : collection) statement
for (int element : a) System.out.println(element);
使用傳統的 for 迴圈也可以獲得同樣的效果:
for (int i = 0; i < a.length; i++) Java
System,out.println(a[i]);
但是,for each 迴圈語句顯得更加簡潔、更不易出錯(不必為下標的起始值和終止值而操心)。
[注] foreach 迴圈語句的迴圈變數將會遍歷陣列中的每個元素, 而不需要使用下標值。陣列初始化以及匿名陣列
在 Java中,提供了一種建立陣列物件並同時賦予初始值的簡化書寫形式。下面是一 例子:
int[] smallPrimes = { 2, 3, 5, 7, 11, 13 };
請注意, 在使用這種語句時,不需要呼叫 new。 甚至還可以初始化一個匿名的陣列:
new int[] { 17, 19, 23, 29, 31, 37};
這種表示法將建立一個新陣列並利用括號中提供的值進行初始化,陣列的大小就是初始值的 個數。使用這種語法形式可以在不建立新變數的情況下重新初始化一個數組。例如:
smallPrimes = new int[] { 17, 19, 23, 29, 31, 37};
[注] 在 Java 中, 允許陣列長度為 0。在編寫一個結果為陣列的方法時, 如果碰巧結果 為空, 則這種語法形式就顯得非常有用。此時可以建立一個長度為 0 的陣列: new elementType[0] 注意, 陣列長度為 0 與 null 不同。
陣列拷貝
在 Java中,允許將一個數組變數拷貝給 另一個數組變數。這時, 兩個變數將引用同 一個數組:
int[] luckyNumbers = smallPrimes; luckyNumbers[5] = 12; // now smallPrimes[5] is also 12
如果希望將 一個數組的所有值拷貝到一個新的陣列中去, 就要使用 Arrays 類的 copyOf方法:
int[] copiedLuckyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);
第 2 個引數是新陣列的長度。這個方法通常用來增加陣列的大小:
luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);
如果陣列元素是數值型,那麼多餘的元素將被賦值為 0 ; 如果陣列元素是布林型,則將賦值 為 false。相反,如果長度小於原始陣列的長度,則只拷貝最前面的資料元素。
Java 陣列與 C++ 陣列在堆疊上有很大不同, 但基本上與分配在堆(heap) 上 的陣列指標一樣。也就是說,
int[] a = new int[100]; // Java
不同於
int a[100]; // C++
而等同於
int* a = new int[100]; // C++
Java 中的 [ ]運算子被預定義為檢查陣列邊界,而且沒有指標運算, 即不能通過 a 加 1 得到陣列的下一個元素。
命令列引數
每一個 Java應用程式都有一個帶 String arg[]引數的 main方法。這個引數表明 main方法將接收一個字串陣列, 也就是命令列引數 3 例如, 看一看下面這個程式:
public class Message { public static void main(String[] args) { if (args.length = 0 || args[0].equals("-h")) System.out.print("Hello,"); else if (args[0].equa1s("-g")) System.out.print("Goodbye,"); // print the other command-line arguments for (int i = 1 ; i < args.length; i++) System.out.print(" " + args[i]); System.out.println("!"); } }
如果使用下面這種形式執行這個程式:
args[0] :"-g"
args[1]:"cruel"
args[2]: "world"
這個程式將顯示下列資訊:
Goodbye, cruel world!