Java programing Java programing

Java 程式設計上課教材

Java 程式設計上課教材 > 課程內容 > 第 9 堂課 - 字串的基礎應用

第 9 堂課 - 字串的基礎應用

上次更新日期 2019/01/04

我們從變數的宣告當中,知道有個 String 的變數類型,不過,這種 String 所定義出來變數是唯讀的,不可隨意更動的資料。 如果要讓字串資料變成一個可以『被玩弄的物件』時,就得要使用字串的許多建構子或者是方式來進行處理。 未來我們開始處理資料庫的課程時,就會使用到很多字串的應用,包括文字的替換、文字的搜尋等任務!因此,熟悉字串的應用, 對未來是很有幫助的。

  • 9.1: 字串的產生與字串建構子
  • 9.2: 字串變數內容的直接變更 StringBuffer 類別
  • 9.3: 二維陣列的應用
  • 9.4: 課後練習

9.1: 字串的產生與字串建構子

在開始文字的處理之前,這裡先就 "字串 (String)" 與 "字元 (character, char)" 做個簡單的分別。

  • String 與 char 的宣告與認識 char

所謂的字串 (String) 指的是 0 個到多個的字元組成的一列文字,這種文字可以使用許多的 java 字串處理函數來進行搜尋、取代、增加與整理。 至於字元 (character, char) 則是會有一個字元存在,這個字元通常是英文 (ASCII),可以透過『 (int)character 』的方式來取得字元的 ASCII 10 進位代碼。

此外,這兩種文字類型的宣告時,給予的值設定也不太一樣,字串會用雙引號 (") 而字元通常使用單引號 (') 來處理!此外, 這兩種宣告,也不能是相同的。通常,宣告的方式如下:

String str = "some word";	// 使用的是雙引號來處理 "字串內容"
char   chr = 'A';		// 使用的是單引號來處理 '一個字元'

上面這個宣告範例還挺重要的!務必記得處理的方式。我們使用過比較多的字串了,先等等再處理。我們雖然在 unit4_5_3.java 裡面稍微談過 char 這個宣告的方式, 不過大家應該還不是很熟悉,因此這裡再次的處理一下。假設有一堆字元陣列,如何透過迴圈的方式,將這些陣列的內容轉成數值? 又如何將數值陣列轉成字元呢?

  1. 分析問題:使用前一章談到的陣列方式,先宣告兩個陣列內容,大致如下:
    char chr[] = { 'I', 'n', 'e', 'e', 'd', 'j', 'a', 'v', 'a' };
    int  var[] = { 73, 32, 76, 79, 86, 69, 32, 74, 97, 118, 97, 33 };
    
    請透過迴圈的方式,將兩個陣列的內容互相轉換成為數值與字元。
  2. 解決步驟:
    1. 宣告類別名稱為 unit09_1_1 (就是檔名)
    2. 開啟方法為 main 的程式
    3. 將上述兩個宣告放到程式碼當中
    4. 透過 for 迴圈,並經過 chr.length 的方式取得迴圈個數後,使用 (int)chr 及 (char)var 的方式,將結果印出
    5. 關閉 main
    6. 關閉類別
  3. 程式設計:主要是透過 for 迴圈,要記得陣列個數取得的方式就是 array.length 喔!
    System.out.println ("先顯示字元轉數值");
    for (int i=0; i < chr.length; i++) {
    	System.out.print ((int)chr[i] + ", ");
    }
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_1_1
    先顯示字元轉數值
    73, 110, 101, 101, 100, 106, 97, 118, 97,
    
    現在顯示數值轉字元
    I LOVE Java!
    

透過這個方法,你就可以知道怎麼進行字元的轉換了!不過,唯一要注意的是,字元轉成 ASCII 的數值時,或者是 ASCII 數值轉成字元時, 還是得要去參考 ASCII 的對應碼!

  • 字元轉字串 (charactor to String)

我們知道 java 程式當中,可以透過『 System.out.print ("字串內容") 』的方式來進行字串標準物件的處理,不過,通常這樣的處理都是將資料印出來而已, 最多再使用『 String str = "一些測試文字" 』的方式,來將經常使用的內容做成變數後,讓字串變數在程式碼當中持續的被使用而已。 而如果你想要將某些字串或者是字元陣列進行字串的擷取與整合時,也能夠透過 String() 建構子的方式來處理,常見的處理方式有:

char[] check = {'我','愛','J','a','v','a','真','心','不','騙'};	// 建立一個 10 個內容的字元陣列
String str1 = new String();		// 建立空字串
String str2 = new String(check);	// 由字元陣列完整的變成字串
String str3 = new String(check, 2, 4);	// 由字元陣列的 2 號位置連續取 4 個字元出來(由0開始編號)
String str4 = new String("就是字串");	// 由字串內容取出字串物件
String str5 = new String(str2);		// 由字串變數取出字串物件

另外,從剛剛的介紹,你也知道了 String 是個『字串,就是一段文字的資料』的意思,而 char 則是『一個字元,例如 abcd 是 4 個 char ,每個字元都是一個 char 的意思』。 因此,在設計程式碼的時候,如上所示,我們才需要用到 char[] 陣列來處理每一個文字。而上頭的程式碼當中, 會得到:

check[0] = '我';
check[1] = '愛';
check[2] = 'J';
...
check[9] = '騙';

現在,讓我們將 String() 物件列印出來瞧瞧看,同時,看看字串與字串建構子產生的物件是否相同?

  1. 分析問題:很單純的要將上述的程式碼帶入之後,印出 str1 ~ str5 的內容後,同時比較字串內容是否相同。
  2. 解決步驟:
    1. 宣告類別名稱為 unit09_1_2 (就是檔名)
    2. 開啟方法為 main 的程式
    3. 宣告 check 字元 (char) 陣列,同時給予初始化
    4. 宣告 str1 ~ str5 的字串,從 String() 建構子去讀取資料
    5. 由 System.out.println 印出 5 個字串內容
    6. 由 ( str2 == str5 ) 的結果,以 System.out.println 印出來兩者是否相同
    7. 關閉 main
    8. 關閉類別
  3. 程式設計:只以最後一個比較的程式碼比較特別,我們可以這樣做:
    System.out.println ("比較 str2 與 str5 是否一致的結果? " + ( str2 == str5 );
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_1_2
    str1 =
    str2 = 我愛Java真心不騙
    str3 = Java
    str4 = 就是字串
    str5 = 我愛Java真心不騙
    比較 str2 與 str5 是否一致的結果? false	// 這一行的結果最重要!
    

上面的範例中, str1, str4, str5 應該不會造成困擾,但是, str2 與 str3 怎麼來的呢?基本上, check 就是一個字元陣列,如上面的程式碼說明, check 全部的陣列內容組合起來,就是『我愛Java真心不騙』,因此,str2 就會得到這樣的結果。那麼 str3 呢?他的意義是這樣的:

我 愛 J a v a 真 心 不 騙 	// 實際字元陣列內容
0  1  2 3 4 5 7  7  8  9  	// 字元陣列的編號,從 0 開始編號
      1 2 3 4             	// str3 要從第 2 個編號位置,連續取出 4 個陣列。

如上所示,因此,最終 str3 = new String(check, 2, 4) 就會得到 Java 這個字串的內容了!但是等等,為什麼 str2 與 str5 的內容會不一樣!? 螢幕上面顯示的結果確實是一樣的喔!

  • 每個字串物件都是獨特而不相同的,判斷內容是否相同要用其他函數方法

等等,既然 str5 是從 str2 的字串去完全相同的生成,那麼為什麼最後一行顯示的竟然是 str2 與 str5 不同呢? 這是因為『所有的字串物件都是不同的個體,因此使用 (str2 == str5) 的邏輯判斷,其結果是不會相同的!』!這很重要啊!咦!那怎麼處理字串的內容判斷? 這需要使用到 equals() 這個方法的處理。處理的方法也很簡單,使用下列的方式即可:

# 底下兩個都是邏輯判斷,因此只會回傳 true 或 false 喔!
( str2 == str5 )	// 一定回傳錯誤 (false),因為字串都是不一樣的個體
str2.equals(str5)	// 如果 str2 與 str5 的文字內容相同,就會回傳 true ,否則回傳 false

不論是不是中文,都可以使用上述的方法去比對。但是,如果你使用的是英文語系的國家,想要避免大小寫英文字母的干擾時, 就得要使用底下的方法了:

str2.equalsIgnoreCase(str5)	// 忽略 str2 與 str5 字串中大小寫的差異。大小寫視為相同的意思。
將 unit09_1_2.java 另存新檔成為 unit09_1_3.java,修改最後一段程式碼,讓顯示的結果會像這樣:
C:\Users\dic\java>java unit09_1_3
str1 =
str2 = 我愛Java真心不騙
str3 = Java
str4 = 就是字串
str5 = 我愛Java真心不騙
比較 str2 與 str5 的文字內容是否一致的結果? true
亦即,最後一行會根據文字內容給予判定是否內容一次,而非分析全部的字串物件!
  • 字串的分析相關函數

一般的字串是可以被搜尋與分析的,常見的搜尋與分析的函數有底下這些:

String  str = "some words in here";	// 假設有個字串變數,指定成為這樣
int     str.length();		// 取得 str 的字串總長度 (總字元數)
char    str.charAt(i);		// 取得 str 的第 i 個位置的字元
int     str.indexOf("關鍵字");	// 取得在 str 內出現 "關鍵字" 的位置, 若不存在,則給負值
int     str.indexOf("關鍵字",j);// 從 str 內第 j 個位置向後開始找 "關鍵字" 的位置
boolean str.equals(str2);	// 比較 str 與 str2 是否相同 (true|false)
int     str.compareTo(str2);	// 比較 str 與 str2 字串的差異,0 代表沒有差異。
String  str.substring(i,j);	// 從 str 取出第 i 到第 j 個字元成為新字串
String  str.substring(i);	// 從 str 取出第 i 到最後一個字元成為新字串
boolean str.contains("關鍵字");	// 從 str 裡面判斷有沒有關鍵字存在,若有則回傳 true。
char[]  str.getChars(srcBegin, srcEnd, char dest[], destBegin) // 將字串讀出變成字元陣列

假設你要分析使用者輸入的資料,該資料裡面的規定需要:

  • 判斷階段:
    1. 必須要有 "java" 這個關鍵字,否則就直接離開程式的運作 (System.exit(0))
    2. 字串的總長度需要多於 20 個字元 (≥ 20),否則離開程式的運作。
  • 字串處理階段:
    1. 列出字串內容以及字串的總長度。
    2. 列出 java 關鍵字所在的位置 (索引) 號碼。
    3. 找到 java 關鍵字的位置之後,向後延伸 3 個位置的字元,將他列出來 (例如找到 3 號位置,則列出 6 號位置的字元)。 理論上,應該會列出 a 這個英文才對喔!
    4. 從 java 關鍵字所在的位置,後面的資料全部變成另一個子字串變數,並且列出此子字串的內容與字元總數。
    5. 建立一個新的字串變數,內容為 "This is new string " + 前一個子字串內容,處理完畢後印出來此字串
    6. 將最後的字串以新的字元陣列載入,並從 19 到 22 編號的字元列出來。

開始來撰寫一下這個字串的處理程式:

  1. 分析問題:要處理上述的功能,判斷階段需要使用 if 來處理,字串處理階段則用到一堆字串處理函數就是了。
  2. 解決步驟:
    1. 先匯入 java.util.Scanner 的套件,因為需要讓使用者輸入字串!
    2. 宣告類別名稱為 unit09_1_4 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 先告知使用者需要輸入一串含有 java 關鍵字的字串,且需要超過 20 個字元以上的文字
    5. 取得使用者的輸入。
    6. 開始判斷階段 (1)有沒有包含 java 關鍵字 (2)有沒有超過 20 個字元,若任何一個不成立,就跳出程式。
    7. 開始分析與處理字串相關資訊:
      1. 列出字串,同時透過 .length() 來列出字串長度
      2. 找到 java 的關鍵字號碼所在,並且列出來
      3. 透過 .charAt(i) 的方式,找出上述索引位置後 3 個位置的字元
      4. 透過 .substring() 函數取得子字串,同時列出此字串的內容與長度
      5. 透過 newstr = "字串" + oldstr 的方式,建立新的字串變數,然後印出此字串
      6. 先從上述的字串取得長度後,根據長度給予新的字元陣列變數的個數,例如 char newstr[] = new char[There] ; 之後直接印出全部的內容,之後,再由 19 到 22 號,印出來相關的字元內容即可。
    8. 關閉 main
    9. 關閉類別
  3. 程式設計:都是透過上述的函數,去處理的!你大概需要注意的就是上述函數的型別而已!不過最後一點比較特別,需要搭配前一個字串的內容。
    // 第一點,可以用 str1.contains("java") 來處理!但是用 if 時,需要找不到的情境才離開!因此
    if ( ! str1.contains("java") ) {		// 就是驚嘆號!可以反向處理!
    ...
    }
    
    // 最後一點的處理方案如下:
    char str4[] = new char[str3.length()];
    str3.getChars(0,str3.length(),str4,0);
    System.out.println (str4);
    for ( k=19;k<=22;k++ ){
    	System.out.print(str4[k]);
    }
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_1_4
    請輸入一串文字,需要包含有 java 關鍵字,而且需要超過 20 個字元
    這是 one string
    找不到 java 關鍵字,我離開囉!
    
    C:\Users\dic\java>java unit09_1_4
    請輸入一串文字,需要包含有 java 關鍵字,而且需要超過 20 個字元
    This is java
    輸入的字元數少於 20 個,我離開囉!
    
    C:\Users\dic\java>java unit09_1_4
    請輸入一串文字,需要包含有 java 關鍵字,而且需要超過 20 個字元
    這是 one java string check words. hehe.
    原本字串: 這是 one java string check words. hehe.
    字串長度: 37
    java 所在的位置: 7
    第 10 個位置的字元是: a
    新的字串: java string check words. hehe.
    字串長度: 30
    第三個字串: This is new string java string check words. hehe.
    第三字串長: 49
    This is new string java string check words. hehe.
    java
    

這個都是屬於字串的處理方式!基本上,都是建立新的字串~原有的字串內容是沒辦法直接在內部修改的喔!此外,String() 的方法,是將字元陣列讀成字串, getChars 則是將字串塞進字元陣列去,這個功能剛剛好是相反喔!要稍微注意一下才好。

  • 字串內容的分析

某些時刻,我們可能需要針對使用者輸入的資料進行分析,以避免使用者輸入錯誤的資訊了。舉例來說,如果需要使用者輸入 16 進位的整數數值時, 你就得要知道 16 進位指的是 0, 1...9, a, b, c, d, e, f 這幾個數值與英文。假設只能輸入小寫英文,那你怎麼知道使用者輸入的是否正確呢? 其實,可以透過底下的步驟來整理:

  1. 將字串拆解成為字元陣列
  2. 將字元使用 (int)chr 的方式轉成數值
  3. 比較此數值是否在 ASCII 對應的範圍內

第 c 步驟就可以知道使用者輸入的資料有沒有在我們所指定的範圍內了!多說無益,來做個簡單的範例。假設我們就是要讓使用者輸入一個 16 進位的正整數數值, 現在讓我們來處理使用者輸入的資料是否正確!

  1. 分析問題:使用者輸入的是 16 進位的資料,因此不能使用 .nextInt() 了!需要直接使用字串 .nextLine() 的方式讀入,再以上述的分析方法處理資料。
  2. 解決步驟:
    1. 匯入 java.util.Scanner 套件
    2. 宣告類別名稱為 unit09_1_5 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 建立 input 輸入物件
    5. 取得使用者的輸入資料成為一個字串變數 (input.nextLine())
    6. 透過分析字串變數的長度,建立字元陣列,宣告字元陣列
    7. 透過 .getChars() 函數,將字串轉成字元陣列
    8. 透過 for 迴圈,開始分析每個字元的數值,需要注意數值的範圍是:數字 (48~57),a~f(97~102) 之間。
      • 如果有任何一個不正確的字元存在,將字元秀出來,然後結束程式 (System.exit)
    9. 分析結束後,最終說明此字串是正確的!
    10. 關閉 main
    11. 關閉類別
  3. 程式設計:最重要的就是字元陣列的分析,該段程式碼會有點像這樣:
    // 開始判斷每個字元是否都在數值 48~57 之間,以及小寫 a~f (97~102) 之間?
    int chknu = 0;
    for ( int i=0; i < leng; i++ ) {
    	chknu = (int)chk[i];
    	if ( ( chknu >= 48 && chknu <= 57 ) || ( chknu >=97 && chknu <= 102 ) ) {
    		// 這是正確的!沒問題!
    	} else {
    		System.out.println ("這個字元 " + chk[i] + " 有問題,所以我停止了");
    		System.exit (1);
    	}
    }
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_1_5
    請輸入一串 16 進位的數值,我將判斷你輸入的是否正確!
    737497923
    這個字串 737497923 是正確的 16 進位數值
    
    C:\Users\dic\java>java unit09_1_5
    請輸入一串 16 進位的數值,我將判斷你輸入的是否正確!
    ajojo234r23
    這個字元 j 有問題,所以我停止了
    
    C:\Users\dic\java>java unit09_1_5
    請輸入一串 16 進位的數值,我將判斷你輸入的是否正確!
    1379abcd309d
    這個字串 1379abcd309d 是正確的 16 進位數值
    

再來思考一下,既然是 16 進位,那能不能將此 16 進位數值轉成實際的 10 進位數值呢?其實也不難啊!例如底下的數值, 每個位置分別對應了 16 進位的數值,而數值指數可以使用 Math.pow 來進行處理,這樣就可以計算 10 進位的數據了!

原始數據:  1   a   3   5
進位數值:163  162  161 160

計算結果:1*163 + 10*162 + 3*161+ 5*160

分析一下,你可以發現指數的數值,就是整個數值長度 (4) 減 1 ,持續削減下去的結果!至於 a~f 呢?由於數值是從 97 (a) 開始, 因此,如果是在 97~102 之間,就直接用字元的 ASCII 的數值減去 87 即可!

請將 unit09_1_5.java 另存新檔成為 unit09_1_6.java,然後將 16 進位資料轉成 10 進位的數值並且同時輸出! 比較需要注意的,只是最終的數值轉換而已。最終的執行結果會有點像這樣:
C:\Users\dic\java>java unit09_1_6
請輸入一串 16 進位的數值,我將判斷你輸入的是否正確!
12479ad
這個字串 12479ad 是正確的 16 進位數值,我開始計算 10 進位轉換
16 進位數值: 12479ad
10 進位數值: 19167661

9.2: 字串變數內容的直接變更 StringBuffer 類別

String 的變數內容基本上是不能修改的,但是你可能會看到有個 replace("old","new") 的資訊,要注意,這個資訊其實並沒有修改到變數的內容! 只是輸出的時候,調整輸出的資訊而已,並非修改了字串變數本身喔!除非你反覆的將新值帶入給字串變數這樣。

  • 透過 replace("old","new") 單純在螢幕上輸出被改過的訊息

現在,讓我們來玩一下這個 replace 的功能。假設,你要將疑問句變成肯定句,例如出現『嗎?』就改成『!』這樣。該如何處理?

  1. 分析問題:很單純的將使用者的輸入資料做個輸出的變化而已。
  2. 解決步驟:
    1. 先匯入 java.util.Scanner 的套件,因為需要讓使用者輸入字串!
    2. 宣告類別名稱為 unit09_2_1 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 告知使用者輸入一個疑問句的內容
    5. 取得使用者的輸入。
    6. 將疑問句常用的『嗎?』取消之後輸出
    7. 印出原本的字串內容。
    8. 關閉 main
    9. 關閉類別
  3. 程式設計:比較重要的就是 replace 的使用而已。
    System.out.println (str1.replace("嗎?","") );
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_2_1
    請輸入一個疑問句,結尾請用『嗎?』
    我會賺大錢嗎?
    肯定句: 我會賺大錢
    疑問句: 我會賺大錢嗎?
    

除了這個直接印出時做個小手段之外,還有另外一種,就是直接帶入新的字串!意思是,將原本字串變數的內容整個取代掉的意思。 處理的方案就跟 i=i+1 這樣的情況類似啦!現在,讓我們來玩一個假人工智慧,當出現『嘛』或『嗎』的時候,就將他刪除, 當出現問號 (全形與半形),就將他變成驚嘆號,然後將『會不會』變成『會』,『能不能』變成『能』,然後出現『你會』變成『我會』, 出現『你能』變成『我能』這樣。

  1. 分析問題:反覆一直重新設定新的字串變數,反覆透過 replace 來處理即可。
  2. 解決步驟:
    1. 先匯入 java.util.Scanner 的套件,因為需要讓使用者輸入字串!
    2. 宣告類別名稱為 unit09_2_2 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 告知使用者這是一個 AI 程式,可以發問題給系統回答 (陪聊天)
    5. 進行一個 while 迴圈,然後進行如下的動作:
      1. 取得使用者的輸入
      2. 判斷,如果輸入為 "0" 字串時,就直接 break 迴圈
      3. 取代『嘛』、取代『嗎』
      4. 取代全形與半形的 ? 變成驚嘆號
      5. 取代『會不會』成為『會』,取代『能不能』成為『能』
      6. 取代『你會』成為『我會』,取代『你能』成為『我能』
      7. 輸出新的字串
    6. 關閉 main
    7. 關閉類別
  3. 程式設計:比較重要的就是 replace 的使用而已。
    if ( str1.equals("0") ) break;
    str1 = str1.replace("會不會","會");
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_2_2
    這是一個可以陪你聊天的 AI 程式啊!,底下空白處就可以打字了!
    你說: 你好
    回答: 你好
    你說: 你會不會 java ?
    回答: 我會 java !
    你說: 你能不能生小孩?
    回答: 我能生小孩!
    你說: 你能不能去 yahoo 讀新聞?
    回答: 我能去 yahoo 讀新聞!
    你說: 0
    

在上面的這個範例當中,如果你的 if 裡面是寫成如下格式的話,那你就會發現,這個程式碼永遠停不了了...這就是字串物件的意義! 如果是字元,是能夠互相比較的,但是字串,就一定要使用 str.equals(str2) 的比較方式了喔!切記切記!

字串比對的錯誤範例: ( str1 == "0" )
字串比對的正確範例: ( str1.equals("0") )
字元比對的範例: ( chr == 'A' )
字元比對的範例: ( (int)chr > 48 && (int)chr < 57 )
  • 從字串裡面直接修改,透過 StringBuffer 功能

想像一下,如果你的字串變數資料量非常的龐大,那如果還使用替代的方式來處理,例如上一個例題的 replace 的動作,那麼你的字串就會一直重複取代! 在處理上可能比較沒有彈性,因為如果資料量是例如整篇文章,那處理起來就很傷腦筋。而 String 這個物件又是唯讀的, 最多只能重新給予,不能部份修改而已。

如果想要直接修改字串內容,就得要使用 StringBuffer (字串緩衝區) 的物件來處置,宣告的方法是這樣:

StringBuffer strbf = new StringBuffer();

裡面的 strbf 就是字串緩衝區的變數名稱了!那麼這個 StringBuffer 物件的內容中,文字可以怎麼被處理呢?基本上,常見的處理函數有:

String  strbf.reverse()			// 反向排列字串
String  strbf.setCharAt(index.'N') 	// 在 index 的位置上,修改成 N 字元
String  strbf.append(strbf+"otherword")	// 字串進行累加的動作
String  strbf.insert(index,"word")	// 在 index 位置上,加入 word 字串
String  strbf.deleteCarAt(i)		// 殺掉第 i 個位置的字元
String  strbf.toString()		// 最終要將 StringBuffer 物件變成字串後,才能夠輸出!

我們來做個簡單的範本,假設有個對談的 AI 假機器人,他會分析你輸入的字串有沒有存在『我』這個字,然後:

  1. 如果沒有『我』這個關鍵字,就結束程式,否則繼續進行分析
  2. 列出『我』這個字在第幾號位置上 (可能需要 +1 喔!因為從 0 編號)
  3. 假設使用者輸入為 myinput 時,使用 strbf 的類型,增加資料內容為: "『myinput』這句話說得很有道理!"
  4. 將 strbf 內的『我』變成『你』,之後將字串輸出
  5. 將 strbf 內的『你』那個字元刪除,然後將字串輸出
  6. 將 strbf 內的剛剛刪除的那個位置上,增加『在下』,然後將字串輸出

很簡單的範例,主要是在讓我們知道怎麼處理字串緩衝區的文字處理功能而已!

  1. 分析問題:需要有使用者的輸入資料 (myinput) 以及後來要處理的字串緩衝資料 (strbf),然後依序帶入上述的函式庫處理。
  2. 解決步驟:
    1. 匯入 java.util.Scanner 套件
    2. 宣告類別名稱為 unit09_2_3 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 依據上述的動作,分別進行各個流程。
    5. 關閉 main
    6. 關閉類別
  3. 程式設計:最重要的其實就是資料的轉換了!例如取代位置應該是這樣寫的:
    // c. 建立 StringBuffer 物件,並且累加資料
    StringBuffer strbf = new StringBuffer();
    strbf = strbf.append("『"+str+"』這句話說得有道理!");
    
    // d. 將『我』變成『你』之後輸出
    int i = strbf.indexOf("我");
    strbf.setCharAt(i,'你');
    System.out.println (strbf.toString());
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_2_3
    請用『我』這個字,寫一句你的夢想
    我希望天天賺大錢
    『我』在第 1 個位置上
    『你希望天天賺大錢』這句話說得有道理!
    『希望天天賺大錢』這句話說得有道理!
    『在下希望天天賺大錢』這句話說得有道理!
    

未來用到比較大的字串資料處理時,才需要這方面的技術喔!

9.3: 二維陣列的應用

前一章談到基礎的陣列處理方式,這一章一開頭的字元處理 (char chk[] = new char[nu]) 也是透過陣列來進行處理的!很多資料我們都可以透過陣列來強化!

  • 陣列的合併

如果有兩個班級,這兩個班級的學生成績已經是登記好了,那如何讓兩班的成績整合在一起呢?可以透過第三個新陣列來完成的! 例如底下的例題,可以來玩一玩!

  1. 分析問題:有兩個班級的學生成績如下:
    int class1[] = {85, 79, 63, 24, 55, 67, 98, 73, 65, 77, 78 };
    int class2[] = {65, 33, 78, 26, 63, 47, 53, 62, 90};
    
    現在,我們要整合這兩班的學生成績,排序之後列出成績。
  2. 解決步驟:
    1. 宣告類別名稱為 unit09_3_1 (就是檔名)
    2. 開啟方法為 main 的程式
    3. 先宣告上述的兩個陣列資料
    4. 透過 class1.length 以及 class2.length 取得兩個陣列的長度,來宣告 int classall[] 這個新陣列
    5. 先從 0 ~ class1.length 來產生 classall 前半部資料
    6. 再由 class1.length ~ class2.length 來產生後半部資料
    7. 以插入排序法針對 classall 來進行排序
    8. 最終印出 classall。
    9. 關閉 main
    10. 關閉類別
  3. 程式設計:最重要的部份其實就是陣列,以及陣列的複製了:
    int classall[] = new int[class1.length+class2.length];
    
    for ( int i=0; i<class1.length; i++ ) {
    	classall[i] = class1[i];
    }
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_3_1
    第 1 個學生成績: 24
    第 2 個學生成績: 26
    第 3 個學生成績: 33
    第 4 個學生成績: 47
    第 5 個學生成績: 53
    第 6 個學生成績: 55
    第 7 個學生成績: 62
    第 8 個學生成績: 63
    第 9 個學生成績: 63
    第 10 個學生成績: 65
    第 11 個學生成績: 65
    第 12 個學生成績: 67
    第 13 個學生成績: 73
    第 14 個學生成績: 77
    第 15 個學生成績: 78
    第 16 個學生成績: 78
    第 17 個學生成績: 79
    第 18 個學生成績: 85
    第 19 個學生成績: 90
    第 20 個學生成績: 98
    
  • 陣列的複製

某些時刻我們可能需要進行陣列的複製,以應付後續的任務。如何複製陣列呢?可以根據上面的作法來複製一個新的陣列。此外, 你也可以透過 java 的內建函數來建立陣列~而且這個新陣列可以從舊的陣列裡面的任何一個索引開始抽取出需要的連續部份來進行陣列的舉動。 複製的方法是:

int class1[] = {85, 79, 63, 24, 55, 67, 98, 73, 65, 77, 78 };
int class2[] = {65, 33, 78, 26, 63, 47, 53, 62, 90};
int classall[] = new int[class1.length+class2.length];
System.arraycopy( class1, 0, classall, 0, class1.length );
System.arraycopy( class2, 0, classall, class1.length, class2.length );
// System.arraycopy (來源陣列, 來源陣列索引位置, 新陣列, 新陣列開始位置, 複製的長度 );
請將 unit09_3_1.java 另存新檔成為 unit09_3_2.java,然後修改陣列的複製部份的資料,重新編譯後並執行。 執行的結果會跟 unit09_3_1 相同!

使用 System.arraycopy 看起來會是比較簡單有效率的作法!

  • 二維陣列的使用 - 抓取最大、最小與索引值的方法

就如同前一章節的 excel 表格一般,Excel 表格就是二維陣列,橫的列有 A, B, C..,直的行有 1, 2, 3..,因此,我們就會有 array[A][1] 這樣的格式去找出該格點的數值。 同樣的,在 java 裡面是允許二維陣列的,宣告的方法有點像這樣:

int myclass[][] = new int[2][20];

上面的情況指的是共有兩橫列,並且每一列裡面有 10 個欄位的意思。畫成表格,會有點像底下這樣:

陣列012345678910
class[0][]8579632455679873657778
class[1][]653378266347536290

不過,明明第一列跟第二列的欄位長度不並相同啊~如果你想要額外指定不同長度的陣列時,可以這樣做:

int myclass[][] = new int[2][] 	// 先宣告二維陣列,但只規範橫列為 2 ,欄位先不宣告
    myclass[0]  = new int[10]	// 因為已經宣告過,所以前面不需要 int 了!後面則是宣告欄位為 10
    myclass[1]  = new int[8]	// 同理,宣告欄位為 8 !

接下來在將資料帶入陣列中即可。那如何知道陣列有幾列?有幾欄呢?基本上,列與欄需要分開來處理的,依序是這樣:

myclass.length		// 會得到 2 ,這是『列數』
myclass[0].length	// 會得到 10 ,這是第 0 列的欄數
myclass[1].length	// 會得到  8 ,這是第 1 列的欄數

你可以對比一下,當需要 myclass[1][5] 時,會得到多少?答案是『 47 』那個欄位~這樣理解二維陣列了嘛!? 讓我們來處理一下這個資訊~

  1. 分析問題:假設已經有底下的陣列宣告了
    int myclass[][] = { {85, 79, 63, 24, 55, 67, 98, 73, 65, 77, 78 }, 
    	{65, 33, 78, 26, 63, 47, 53, 62, 90} };
    
    請列出最佳成績與最差成績的索引
  2. 解決步驟:
    1. 宣告類別名稱為 unit09_3_3 (就是檔名)
    2. 開啟方法為 main 的程式
    3. 將上述的宣告加入到本程式當中
    4. 透過 myclass.length 以及 myclass[N].length 的方式,取得不同的欄位長度
    5. 透過上述的方式取得陣列資料後,開始進行迴圈
      • 如果比最低值還低,就紀錄該欄位資訊的索引值
      • 如果比最高值還高,就紀錄該欄位資訊的索引值
    6. 最終列出最高成績、索引值以及最低成績、索引值。
    7. 關閉 main
    8. 關閉類別
  3. 程式設計:如何透過巢狀迴圈處理二維陣列,是這種題目裡面需要理解的!
    for ( i=0; i<myclass.length; i++ ) {
    	for ( j=0; j<myclass[i].length; j++ ){
    		...
    	}
    }
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_3_3
    透過二維陣列,取得最大最小值
    最高成績: 98,在第 0 列與第 6 欄
    最差成績: 24,在第 0 列與第 3 欄
    
  • 透過二維陣列進行資料分析 - 售貨員銷售紀錄

一般來說,透過這種多維陣列的處理方式,常常會用類似 excel 的表格功能,意即是通常在統一計算某些特殊的資料, 例如會計資料、學生的分數資料等等。舉例來說,底下有個範例:

銷售員產品A(12元)產品B(16元)產品C(10元)產品D(14元)產品E(17元)
13234544533
26454706523
34340456860

這很常在表格的探討裡面發現吧!現在你想要根據上表達成幾個任務:

  1. 每一個銷售員所銷售的產品總金額
  2. 業績最好的銷售員
  3. 業績最好的產品

其實,作法也是很簡單,將整個陣列宣告完畢之後,以陣列的長度搭配迴圈的計算,就可以取得正確的解答了。 我們可以將上述的三個需求全部以個別的迴圈來完成即可。

  1. 分析問題:將陣列內的物品,乘上對應的金額,就可以分別取得售貨員的總額,以及各項物品的總額了。
  2. 解決步驟:
    1. 宣告類別名稱為 unit09_3_4 (就是檔名)
    2. 開啟方法為 main 的程式
    3. 宣告一個名為 price 的陣列,列出 5 項產品的價格,可以是這樣的:
      int price[] = {12, 16, 10, 14, 17};
      
    4. 宣告類似名為 res 的二維陣列,第一維顯示每一個售貨員 (有 3 個),第二維則是每項產品的銷售額 (有 5 種), 簡單的說,可以這樣直接宣告的:
      int res[][]={ {32,34,54,45,33}, {64,54,70,65,23}, {43,40,45,68,60} };
      
    5. 宣告最佳售貨員的編號、最佳售貨員的銷售額等整數變數
    6. 宣告一些暫存變數,包括 i, j, k, sum 等,其中可能某些變數需要給予初始值
    7. 以售貨員個數為主,進行迴圈的設計:
      1. 以每種貨物為主,進行迴圈的設計,可以根據底下的方式來計算售貨員總額喔!
        sum = sum + price[j]*res[i][j];
        
      2. 使用 if 的功能,判斷此售貨員的售貨總額是否為最大,若是,則變更最大值的售貨員編號與總額
    8. 列出最佳售貨員的代碼、最佳售貨員的銷售總額。
    9. 以產品的數量為主來設計迴圈的進行 (例如 price.length 的意思)
      1. 將每個售貨員同一種產品的銷售額加總,就是每種物品的銷售額了
      2. 判斷產品的銷售額是否為最大,若是,則更改最大值的產品編號。
    10. 列出銷售最好的產品編號與總額
    11. 關閉 main
    12. 關閉類別
  3. 程式設計:在判斷的使用上,一個是以第一維為外層迴圈,一個則是以第二維為外層迴圈, 需要注意我們需要列出的是哪種項目才行!
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_3_4
    第 1 個銷售員總銷售額: 2659
    第 2 個銷售員總銷售額: 3633
    第 3 個銷售員總銷售額: 3578
    最佳售貨員代碼是: 2銷售成績共: 3633 元
    第 1 種產品的銷售額: 1668
    第 2 種產品的銷售額: 2048
    第 3 種產品的銷售額: 1690
    第 4 種產品的銷售額: 2492
    第 5 種產品的銷售額: 1972
    最好的銷售產品是: 4,產品總額: 2492 元
    

這就是最常見的類似二維陣列的計算功能!記得喔!未來在資料庫裡面也會經常用到這方面的技巧喔!

  • 透過二維陣列設計座標 - 亂數迷宮的設計

此外,如果用在未來設計一些小遊戲的情況下,這種二維的陣列,就可以假設成為座標軸囉!例如,假設使用這種情況來設計一個迷宮, 這個迷宮可能沒有出口...沒關係,就用簡單的想法先來設計一下!當某個座標軸是牆面,就印出『 # 』或全形的『█』符號, 如果是通道,就印出『空白按鈕』或『全形空白』的符號,這樣就可以設計一個怪異的迷宮了!

  1. 分析問題:一般所謂的迷宮,最外層的圍牆一定會存在,然後,內部的圍牆則是隨機產生的!只會有或沒有這樣。 因此,如果要產生一個 20x20 的迷宮,只要是最外層 ( x=0, x=19, y=0, y=19 的情況下),就必須是圍牆,其他的, 可以透過亂數,如果是 0 就印出空白,如果是 1 就印出 # 當作圍牆。
  2. 解決步驟:
    1. 宣告類別名稱為 unit09_3_5 (就是檔名)
    2. 開啟方法為 main 的程式
    3. 先告名為 cord[][] 的二維陣列,且 X 與 Y 個別均為 20 個;
    4. 以 y 為外部迴圈,以 x 為內部迴圈,在迴圈當中處理底下任務:
      • 計算 0 或 1 的亂數 (只有兩個數值)
      • 若為 0 則 cord 在此座標設定為 0
      • 若為 1 則 cord 在此座標設定為 1
      • 但是,如果 x 是 0 或 19,及 y 是 0 或 19 時這四個條件下,就是圍牆,所以 cord 一定是 1
    5. 以 y 為外部迴圈,以 x 為內部迴圈,在迴圈當中處理底下任務:
      • 如果 cord 是 1 ,就印出 # 或『█』
      • 如果 cord 是 0 ,就印出空白或全形空白
      • 在 x 與 y 的迴圈的中間,就印出 "" 來讓迴圈斷行輸出
    6. 關閉 main
    7. 關閉類別
  3. 程式設計:就非常簡單,算出來是 0 或不是 0 然後填入陣列中而已!
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit09_3_5
    ████████████████████
    █  █  ███     ███  █
    ████  █ █ █ █ █ ██ █
    ██ █      ███ █ ██ █
    █  ███   ███   █ ███
    █  ██ █   █  █ █ ███
    ████    █ ███    ███
    █ ██ █ ██   ███ █ ██
    ██ ██  █   ██ ███  █
    ██ █    █ ██████████
    █   █ ██ █ █████ ███
    █   █ █ █     ████ █
    ██    █ ███ ███   ██
    █       █ █ ██  █ ██
    ███  █ █          ██
    ██ █ ███     ███ █ █
    ███ ███  ██ ██   █ █
    █████ █ █  █ █  █  █
    █ █  █   █ ████    █
    ████████████████████
    

當然,這不是正統的迷宮,只是亂數丟出一堆圍牆的資訊而已~不過,如果你可以在第一個迴圈計算當中加入迷宮的特殊計算的話, 那就能夠直接將你的迷宮設計妥當囉!

9.4: 課後練習

先查閱 1.3 小節的介紹,了解雲端系統的登入、繳交資料檔名等等的設計,然後再繼續底下的習題。 最終要上傳的檔案有:

  • unit09_sort.java (純文字檔)
  • unit09_date.java (純文字檔)
  1. 有時候我們需要將使用者輸入的字元進行一些順序排列,通常排列的方法就是依據 ASCII 碼的順序來安排的。 現在,請你設計一隻名為 unit09_sort.java 的程式碼,這個程式碼會進行如下的工作:
    1. 先讓使用者輸入一串文字;
    2. 將這串文字依據每個字元的 ASCII 從小到大重新排列
    3. 最後將這串新的字串輸出。
    最後執行的成果會有點像這樣:
    C:\Users\dic\java>java unit09_sort
    請輸入一串文字,不過,盡量不要出現空白字元:
    你的輸入: jlasjfasojf979asfmo1rjnmvlmasl;f'a
    我排序後輸出的結果是這樣:
    1799;aaaaaffffjjjjlllmmmnoorssssv
    
  2. 有時候我們需要分析使用者輸入的資料格式是否正確,然後取出資料來分析。 現在,假設我們需要使用者依據 YYYY/MM/DD 的格式來輸入西元的 年/月/日 的格式,請寫一隻名為 unit09_date.java 的程式碼來完成底下的動作:
    1. 連結符號必須是 / 這個符號
    2. 西元年必須是 2000 到 2030 之間
    3. 月份需要 1~12
    4. 日期需要 1~31
    最後執行的成果會有點像這樣:
    C:\Users\dic\java>java unit09_date
    請輸入最近兩年日期格式 (YYYY/MM/DD): 141/134/13
    日期格式錯誤!分隔符號不是 / 喔!
    
    C:\Users\dic\java>java unit09_date
    請輸入最近兩年日期格式 (YYYY/MM/DD): 1349l1/13412/13
    日期格式錯誤!超過 10 個字元
    
    C:\Users\dic\java>java unit09_date
    請輸入最近兩年日期格式 (YYYY/MM/DD): 1998/11/12
    日期格式錯誤!年份不在 2000 與 2030 之間
    
    C:\Users\dic\java>java unit09_date
    請輸入最近兩年日期格式 (YYYY/MM/DD): 2013/13/21
    日期格式錯誤!月份不在 1 與 12 之間
    
    C:\Users\dic\java>java unit09_date
    請輸入最近兩年日期格式 (YYYY/MM/DD): 2019/01/04
    日期格式是正確的