# Java進階技巧- 陣列、函式、字串 ## 1.陣列 ### 1.基本介紹及記憶體位置 1. ★陣列是連續的記憶體,陣列只有參考位置(最頂層1維、2維取最高的一個)在stack裡面,其他都在heap中,基本資料型態的話就會直接在stack裡面,不會進到heap裡。 2. 陣列存取資料,都會是相同性質的資料,如果是不同系列的資料,其實就用不同變數宣告即可。 3. 基本資料型態存的stack記憶體位置是值,(heap)不會存資料, 參考資料型態的stack記憶體是存位置,heap才會存記憶體位置或值(看幾維陣列,最後一層才是值(如果是參考資料型態的陣列,最後一層還是位置),其他都是位置)。 多維陣列,也就是一直以一維陣列拆解,最後一層在看是值或是地址。 4. 基本資料型態陣列,陣列內值預設值為零; 參考資料型態陣列,陣列內值預設值為null 5. 如果直接是參考資料型態陣列的變數名稱,印出會得到記憶體的位置 6. int a[] = new int[3]; int b[] = a; //如果是 int b[] = a[] 或 a[0] 是錯的 因為int b[ ] = a整個陣列,也就是說b[ ] 所有值跟位置都跟a[ ] 一樣,因為記憶體位置指向相同。 a[0] = 4; System.out.println(b[0]); b[0] = 4 #### 手抄筆記-陣列記憶體圖示: 記憶體圖片, 1.陣列、參考資料型態、多重陣列 2.程式碼記憶體 3.程式碼記憶體全部 ![](https://i.imgur.com/dmIweDM.jpg) ![](https://i.imgur.com/ozsjhDe.jpg) ![](https://i.imgur.com/yIlunnC.jpg) ![](https://i.imgur.com/iVepFrl.jpg) ### 2.多維陣列資訊 1. 兩層迴圈寫法(如果二維陣列長度不同時(第二個[2][3],三如果長度是不同時) ```java int[][] test = {{1,2,3},{4,5}}; for (int i = 0; i < test.length; i++){ for (int n = 0; n < test[i].length; n++){ System.out.println(test[i][n]); } } ``` 多維陣列直接賦值方法及示意圖 ![](https://i.imgur.com/KwszopN.png) ### 3.陣列交換&共用 1. 交換時少一層,二維陣列交換,暫存只需要定義一維陣列;一維陣列交換只需要定義int 即可。 2. 要將兩個陣列具有相同數值有兩個方法: 1. 共用陣列( arr1 = arr2;):將arr1的參考位置指向arr2,電腦中的記憶體空間不變(heap),但stack 還是有陣列arr1的記憶體,只是兩個都指向同個位置。 2. 陣列的methon:"clone()" (ex: arr1 = arr2.clone() ):創建與原陣列等大的空間,並將原陣列中的資料複製過去。複製是直接將arr2複製,然後arr1指向新複製的陣列,也就是記憶體空間也會改變。 - 示意圖 ![](https://i.imgur.com/upjDZT8.jpg) ![](https://i.imgur.com/04vTANX.jpg) 3. 陣列如果被取代,假如原始的記憶體沒有被任何一個讀取(即沒有連結時), 原始的記憶體就會被GC(垃圾回收) 清理掉,不會浪費記憶體空間 4. 陣列交換方式: ```java public static void main(String[] args) { int arr[][] = new int[4][2]; // 座號 成績 for (int i = 0; i < 4; i++) { arr[i][0] = i + 1; arr[i][1] = (int) (Math.random() * 101); } for (int i = 0; i < 4; i += 2) { //利用值的方式 for (int n = 0; n < 2; n++) { int tmp = arr[i][n]; arr[i][n] = arr[i + 1][n]; arr[i + 1][n] = tmp; } //利用位置的方式 // int[] tmp = arr[i]; // arr[i] = arr[i+1]; // arr[i+1] = tmp; } for (int i = 0; i < 4; i++) { System.out.println("座號:" + arr[i][0] + "成績:" + arr[i][1]); } } } ``` ### 4.陣列自動放大(可無限輸入資料) 將陣列長度擴大,內建函式有兩種方式: (可看第一周投影片 5-4 記憶體管理) 1. System.arraycopy(來源陣列,起始索引值,目的陣列,起始索引值,複製長度); 第一種方法,要先創立新陣列當作目的陣列,先一行宣告,並且在此時就要給予長度。 2. Arrays.copyOf(要被複製的陣列,新陣列的長度); 第二種方法,不需要先新創立,會自動放過去。 以下為手動製作方法: ```java public class Test { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int arr[] = new int[2]; int count = 0; do { int tmp = sc.nextInt(); if (tmp == -1) { break; } if (arr.length == count) { int tmpArr[] = new int[arr.length * 2]; for (int i = 0; i < arr.length; i++) { tmpArr[i] = arr[i]; } arr = tmpArr; } arr[count++] = tmp; } while (true); for (int i = 0; i < count; i++) { System.out.print(arr[i] + " "); } } } ``` ### 5.陣列排序(0820課堂) 1.氣泡排序法 最慢的方法:從第一位開始,一直跟後面比,比較大就交換位置(因此不一定每次都是第一個被移到最後面,而是當次最大的數值會被移到最後一個),總共會執行n-1次,並執行 n-1 - i次(因為後面的數值不用再比較,一定是最大)。 ```java package test; import java.util.Scanner; public class Test { public static void main(String[] args) { int arr[] = {7, 4, 6, 2, 1, 5}; printArr(arr); bubbleShot(arr); printArr(arr); } //氣泡排序法,可以獨立出來一個function public static void bubbleShot(int[] arr){ //第一層,控制次數,需要做n-1次,因為第一位不用比較 for (int i = 0; i < arr.length - 1; i++) { //第二層,控制每一次要比對的次數,因為每次都是跟後面(n+1)比,因此要-1不然到最後一位時 //沒有資料可以比,又因為每次結束後,最後一個數字一定是最大值,因此要減掉已執行過的次數, //因為就算比也不會比較大。 for (int n = 0; n < arr.length - 1 - i; n++) { if (arr[n] > arr[n + 1]) { swap(arr, n, n + 1); // 一樣可以用其他function } } } } public static void swap(int[] arr, int i1, int i2) { int tmp = arr[i1]; arr[i1] = arr[i2]; arr[i2] = tmp; } public static void printArr(int[] arr) { for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); } } ``` ### 6.陣列搜尋(0820課堂) 1. 線性循序搜尋法:就是平常我們使用的,從第一個比到最後一個 2. 二分(元)搜尋法,最多的次數就是(工程領域的log是以2為底,也是是2的幾次方) logN + 1 次。(Math.log (double)),但其實不用用到,解法如下,可參考(0820課堂實作) ```java 正確做法: 用兩個變數left與right分別紀錄搜尋的最左與最右索引 中間數索引 middle=(left+right)/2 如果數字應落在左半邊,新的right值: right = middle - 1 如果數字應落在右半邊,新的left值: left = middle + 1 left應當等於或比right小,否則就代表鍵值不存在 <- 此時代表找不到。 如果數值跟middle相等,也就代表找到值了。 //老師版本的二元搜尋法,不用用log,直接用左右兩邊的值去確認位置 public static int binarySearch(int num[], int count, int search){ int left = 0; int right = count -1; int middle; while(left <= right){ // <= 在left = right 時才會進迴圈 middle = (left + right) / 2; if (search == num[middle]){ return middle; } if (search > num[middle]){ left = middle + 1; // 放棄左半邊,會加1的原因,是因為middle已經確認過不是了,所以要掠過它 }else{ right = middle - 1; // 放棄右半邊,會減一的原因同上 } } return -1; } // java 內建的二元搜尋法: // 所屬類別:java.util.Arrays // 語法:public static int binarySearch(int[] a,int key) // 功能:二分搜尋。 // 引數:key為尋找目標。a[ ]為已排序的工作陣列。 // 回傳值:若key位於a陣列中,則回傳索引值。若不位於a陣列中,則回傳負值。 我的作法: 直接用<= 不另做 +1 -1 的判斷 public static int twoSearch(int num[], int count, int search) { int min = 0; int max = count; for (int i = 0; i <= (int) (Math.log(count)) + 1; i++) { //用log找最多幾次,因為log有時會有小數點,所以用<= 就算沒用掉也會直接跳出 int index = (min + max) / 2; if (search == num[index]) { return index; } if (search < num[index]) { max = index; continue; } min = index; } return -1; } ``` ## 2.函式 ( Function) (範例:0820課堂) ### 1.基本資訊及架構 1. 寫在main函式外,但要包含在主類別之中。 2. 每個java 一定在:main funtion 主程式開始及結束,其他皆只是函式或方法。 main funtion = class Main{ public static void main (String[] args) { →此為主程式,方法在這個之上或之下都可以,要在class Main 裡 3. public 是存取修飾詞,static 修飾詞 (靜態函式的意思),(暫時再寫的時候都先加上 public static 都先加。) 4. void 沒有回傳值,常用在不需要回傳資料,EX: 可以直接輸出時,不需要回傳資料 5. void 的位置,如果是要有回傳值,就將void 改成要的資料型態(ex: int,double,String) 基本資料跟參考資料型態都可。 6. main 為名稱,像變數名稱一樣都可以隨意叫,但盡量遵守命名原則,小寫開頭,轉折處大寫EX: dateArr。 7. String[] args 則是放入要從外面帶入的參數(如果沒有要代入的話,就留小括號即可,裡面不用放,就算是void還是可以代入外面的參數,只是無法用return傳回數值而已),要有形態,可以有兩個以上的參數,用逗點分隔,可以不同型態,但都一定要有型態跟參數(詳細範例可以看後面);後面的大括號內放的是function的行為(←平常在寫得)。 Tips.小括號裡面其實就跟平常在宣告變數一樣,只是差在都用逗點區隔即可 EX: int a ,外面的 小括號內是 1 ,也就是在進入方法時,初始化變數宣告 int a = 1的意思。 8. 定義function的的時候,最好只做到那個方法要用到的功能就好,不要超過上限,盡量讓if的判斷都在外面,增加彈性。 9. java函式範例圖: ![](https://i.imgur.com/7hMHp8I.jpg) ### 2.補充資訊 1. 定義function的的時候,最好只做到那個方法要用到的功能就好,不要超過上限,盡量讓if的判斷都在外面,增加彈性。 2. 有回傳值時重點要注意function傳回值得型態,是否跟主程式的吻合,EX:主程式是宣告int變數承接回傳的值,但function回傳的是double,就會發生錯誤, 3. 只要不是void的,都有回傳值,就要寫return,但void有時候可以沒回傳但影響到主程式的值。 4. return後面也可以不帶參數,不帶參數就是返回空,其實主要目的就是用於想中斷函數執行,返回調用函數處。 5. return和break的主要區別在於:return是跳出method,回到喊它的那個method,break是跳出所在的迴圈 6. function 如果要提前結束只能用return ,但要有回傳值才可以,如果是void一樣可以用return結束函式,只是不會有回傳值,等於直接關掉function回到上一層。 7. Java的函式隸屬於某一個類別,除非傳入的參數不同,否則在同一個類別當中不允許宣告兩個相同名稱的函式。 8. 同個calss function可以互相使用,但是在建立變數時自然會占用到記憶體,因此在效能與彈性及流暢度中,自己要抓平衡 9. ★java 只有: Call by Value 傳值呼叫 是資料的值傳入方法裡面 (記憶體的圖在下方); Call by Reference 傳參呼叫: 是資料的位置傳入方法裡面; ( C++有,專門語法) Call by Address 傳址呼叫 ←只有台灣在講 ( 其實就是將陣列指到同個位置,有些人誤講成傳址呼叫,但java是把這個位置當成值傳回 ←在別的語法也就是傳參呼叫) 10. Function 本身可以直接直接在主程式中寫,不需搭配任何變數,就算不是void也行: ```java public static void main(String[] args) { test(); test(); } public static void test(){ System.out.print("!"); } public static void main(String[] args) { test(1); //此處,test(1); 回傳了1 但是沒有任何變數去承接他 int b = test(1); } public static int test(int a){ System.out.print(a); return 1; } ``` ### 3.funtion的範例及function執行順序: funtion的開始,會先宣告參數 因此會先執行 System.out.println(random(1,6)); 因此會先執行小括號內的random(1,6) ```java int r = random(1, 6); System.out.println(r); /*可以改寫為: System.out.println(random(1, 6)); 執行順序,function在被呼叫時,第一件事情就是宣告變數 (將參數帶入變數中 EX: int a = 3) , 此時random(1, 6) 也就是參數位置; function會先宣告一個變數(ex;a 也就是 a = random(1, 6); 因此會先執行 也就是說:function 的參數執行順序,由左至右,月內層function越先執行; */ } // random (裡面就跟平常在宣告變數一樣,只是差在都用逗點區隔即可 ) public static int random(int min, int max){ int r = (int)(Math.random() * ( max - min + 1) + min); return r; // 可以改寫成:return = (int)(Math.random() * (max - min + 1) + min); } ``` - Function 範例1:因為有時候會是以記憶體位置傳回,因此雖然是void宣告,仍有可能影響到主程式的數值(含記憶體圖片) ```java public static void main(String[] args) { int[] a = new ine[1]; a[0] = 5; test(a); System.out.println(a[0]); } // 主程式的a 代表的是一維陣列,所以test(a) 也就是將這個一維陣列傳入function test小括號內 // 因為一維陣列是參考資料型態,也就是說傳的值是記憶體的位置,而非數值,因此會變成test裡面的 // a[0] 指向的記憶體位置跟主程式的一維陣列a 相同,所以修改時,就會改變主程式的a陣列值。 public static void test(int[] a){ a[0] = 1; } //以下為傳值回去,就影響不到a[0]的數字,因為是傳 "5" 這個數值回a 而void並沒有return,因此 // a = 1 只是在 test 的 a = 1,與主程式無關。 public static void main(String[] args) { int[] a = new int[1]; a[0] = 5; test(a[0]); System.out.println(a[0]); } public static void test(int a){ a = 1; } ``` ![](https://i.imgur.com/FkDLBT7.jpg) ## 3.進階函式 - 函式多載(overload) 1. ★面試必考,常與覆寫( override ) 做比較;函式多載(overload): 函式定義可允許同名但不同署名的形式,這就是所謂多載(overload)的機制。 只要參數的數量,或型態不一樣(包含順序),就可以用同個方法名稱命名,不會重複。 EX:有一個String陣列跟一個Int陣列,要同時做arrDouble,不需要將arrDouble取名為arrDoubleInt、 arrDoubleStr 只需要創建兩個方法名為arrDouble後面的參數及前面傳回的資料型態改變即可。 署名:是名稱包含到小括號(函式名稱、參數資料型態、參數的個數與順序) (回傳值型態、函示名稱、參數宣告)。 命名是function名稱而已 - 示意圖 ![](https://i.imgur.com/fAoy2CW.png) ![](https://i.imgur.com/FCS0QRL.png) ## 4.遞迴函式(recursive) 1. 通常用在處理複雜資料結構的運作時。 2. 遞迴函式關鍵兩要素: base case 、 副函式會呼叫自己 3. 透過函式本身再次呼叫自己,直到 base case(最基本可解決的情況,類似迴圈的停止點)時才回傳值,如果沒設base case的話,就像while迴圈沒有break 會無限循環,只是函式的無限呼叫會導致記憶體過載。 4. 遞迴函式在設計程式上,耗費記憶的資源量大,因為每一次遞迴,就等於會再呼叫自己一次,假如遞迴20次,就等於同時存在20個,直到碰到basecase時才會開始回傳並關閉function 5. 迴圈 VS 遞迴 1. 理論上任何重複結構都可透過迴圈或遞迴的方式表現。 2. 兩種寫法都有無限重複的可能 3. 程式設計師需要程式撰寫的複雜度與執行效能間做考量 4. 進行、結束方式: 迴圈:透過設定重複的條件 ex: for (次數)、while、do while (true 或條件) 決定是否進行,或是提前使用break跳出。 遞迴:透過重複呼叫自己 ,除非碰到設定的base case值才跳出 5. 優缺點分析: 迴圈:因為要考慮重複的規律性,因此寫法會變得複雜,但是卻比較省記憶體。 遞迴:好處是寫起程式比較簡單,但相對地也比較耗記憶體。 6. 練習時的建議:每次練習皆需要用迴圈的方法重寫過一次,因為: 使用遞迴寫法有助提高程式的易讀性,但從效能的觀點來看,並非是演繹流程的最好寫法。 同樣問題嘗試用迴圈再撰寫一遍,以便未來當有效能考量的必要時,能採最有效的方式來實作。 ### 1.範例及記憶體示意圖 ![](https://i.imgur.com/5Bn71lt.jpg) ![](https://i.imgur.com/aDaWBMe.jpg) 費式數列示意圖 ( ![](https://i.imgur.com/GEd4aRP.png) ![](https://i.imgur.com/Mnquu84.png) ### 2.範例code ```java // public static int f(int x) { // int[] num = new int[2]; // num[0] = 0; // num[1] = 1; // while (x >= num.length) { // int tmp[] = new int[num.length * 2]; // for (int i = 0; i < num.length; i++) { // tmp[i] = num[i]; // } // num = tmp; // } // for (int i = 2; i <= x; i++) { // num[i] = num[i-1] + num[i-2]; // } // return num[x]; // } //如果是需要求x很大的話 建議用陣列, // public static int f(int x) { // int[] num = new int[x + 1]; // for (int i = 0; i <= x; i++) { // if (i <= 1) { // num[i] = i; // break; // } // num[i] = num[i - 1] + num[i - 2]; // } // return num[x]; // } //如果是需要求x較小的話 建議用值計算,較省記憶體, public static int f(int n) { if (n == 0 || n == 1) { return n; } int a1 = 0; int a2 = 1; for (int i = 2; i <= n; i++) { int tmp = a2; a2 = a1 + a2; a1 = tmp; } return a2; } // public static int f(int x) { // if (x == 0 || x == 1) { // return x; // } // return f(x - 1) + f(x - 2); // } ``` ```java package test; import java.util.Scanner; import java.lang.Math; public class Test { public static void main(String[] args) { System.out.println(question1(3)); System.out.println(question2(3)); System.out.println(question3(3)); System.out.println(question4(3)); System.out.println(question5(3)); System.out.println(question6(3, 3)); System.out.println(question8(16, 12)); } // 1. 1+2+...+n OK // 2. 1-2+3-4+....+n // 3. 1+4+9+16+....+n*n // 4. n! ( 階層函式) // 5. fib (費式數列) // 6. base^exp (次方) // 7. base^exp ( O(logn) ) - 沒寫,看不懂題目 // 8. gcd (最大公因數) public static int question1(int n) { if (n == 1) { return 1; } return question1(n - 1) + n; } public static int question2(int n) { if (n <= 0) { return n; } if (n % 2 == 1) { return question2(n - 1) + n; } else { return question2(n - 1) - n; } } public static int question3(int n) { if (n == 1) { return 1; } return question3(n - 1) + n * n; } public static int question4(int n) { if (n == 1) { return 1; } return question4(n - 1) * n; } public static int question5(int n) { if (n == 0 || n == 1) { return n; } return question5(n - 1) + question5(n - 2); } public static int question6(int n, int times) { if (times == 0) { return 1; } if (times == 1) { return n; } return question6(n, (times - 1)) * n; } public static int question8(int a, int b) { if (a % b != 0) { return question8(b, a % b); } else { return b; } } } ``` ## 5.字串 ### 1.基本介紹 1. 字串的本質是字元(char)型態的陣列。 2. 在 Java 中將字串視為 String 類別的一個實例,使用 String 類別來建構,也就是將其視為物件存在於程式之中,可以,這讓字串處理在 Java 中變得簡單。(不需要import) 3. 字串的輸入是雙引號" " , 讀取是用nextLine()。 4. 可以使用" + " 運算子來做子元串接(會自動重新定義(Override)字串)。 5. 字串建構:除了直接 = “ “ 外,也可以用字元陣列建構 EX: char[] name = {‘1’, ‘5’, ‘9’}; // 創建一個字元陣列 String str = new String(name); // 創建一個字串,並將name字元陣列的值存入字串中,因此字元陣列name就算後面修正也不影響,因為不是將str的位置指向name。 String[] nameStr = {“jachy”, “joy”}; //創建一個String陣列 並初始化賦值 String nameStr[0] = jachy [1] = joy; 6. 格式化用法,為何function可以這樣無限制加入,是因為宣告參數時,如果是用參數資料型態...參數名稱,就可以無限加入,這是一種Object的方法。 7. 編譯時期的String 跟執行時間的String 會是在不同的記憶體空間;所以如果在編譯時期會在同個heap中,如果是在執行時期,會=new兩個heap記憶體空間,因此兩個不會相等(heap 裡面會叫做字串池)。 8. 字串所有的字,在編譯時期產生的,只要是重複的就會是同個位置,不同的話會在創一格新的,字串如果相加,如果跟後面的數字一樣,會自動跑到那個位置。 9. 字串池會在程式結束時才會釋放掉,而nextLine() 創出來的則在沒有人指向他時,會被GC掉。 10. 字串內容是不可變的,當字串已在記憶體中被建立出來,該記憶體中的字串內容就不允許變更,會在heap裡面,直到沒人指向他,程式結束或被GC。 ### 2.字串使用方法(詳細可見7-2 字串說明、) 1. 將字串轉換為數值型態,可轉換成對應的數值型態: (數值型態全名ex: Byte Integer Float) .parse (跟前面型態一樣,除了整數改成Int)(字串) 2. 如果指定的字串無法剖析為指定的資料型態數值,則會發生 NumberFormatException 例外(亦即如果字串長度過大,ex: 回傳值是214877858,如果前面型態是用byte short去轉換,就會發生錯誤,如果字串更大,就會用long存取。) #### 3.常用方法 說明: 1. int length() 取得字串的字元長度 2. equals() 判斷原字串中的字元是否相等於指定字串中的字元:使用 A.equals(B) 也就是像整數 if ( A == B)的意思 3. toLowerCase() 轉換字串中的英文字元為小寫 4. toUpperCase() 轉換字串中的英文字元為大寫 5. length、concat ; split() ;substring() ;contains; replace; compareTo; eaquls; try很常用到 6. contains(String) 7. 範例&示意圖 ```java public static void main(String[] args) { String date = "1999/9/9"; String[] strs = date.split("/"); //正則表示式 , 因為split方法內用for迴圈跑,所以String陣列會自己跑0,1,2 for (int i = 0; i < strs.length; i++) { System.out.println(strs[i]); } } ![](https://i.imgur.com/QeyCzG6.jpg) ### 3.取得字串中的字元方法 1. char charAt(int index) 傳回指定索引處的**字元** 1. int indexOf(int ch) 傳回指定**字元**第一個找到的索引位置 1. int indexOf(String **str**) 傳回指定**字串**第一個找到的索引位置 1. int lastIndexOf(int ch) 傳回指定**字元**最後一個找到的索引位置 1. String substring(int beginIndex) 取出指定索引處至字串尾端的子字串 1. String substring(int beginIndex, int endIndex) 取出指定索引範圍子字串 1. char[] toCharArray() 將字串轉換為字元陣列 範例圖 ![](https://i.imgur.com/AbjL9nA.jpg) ### 4.endsWith() 方法可以判斷字串是不是以指定的文字作為結束,您可以使用這個方法來過濾檔案名稱。 - 範例(使用 endsWith() 來過濾出副檔名為 jpg 的檔案) ![](https://i.imgur.com/1d5RLQm.png) ### 5.[方法大全](http://tw.gitbook.net/java/java_strings.html) (java 極客書) ### 6.[7-3 String類別-已加註筆記](https://drive.google.com/file/d/1dYvTA3fXo_MFR3dknrcBOF9p1gafuUTy/view?usp=sharing) ### 7.字串記憶體位置圖 ![](https://i.imgur.com/5XHT6V7.jpg) ###### tags: `CMoney7th-戰鬥營學習筆記`