預計學習細項 : - [ ] Spring boot - [ ] Exception處理 - [ ] Test檔設計 - [ ] dto檔 - [ ] Tomcat與Servlet - [ ] Collection Framework - [ ] Serializable - [ ] Gradle - [ ] Thread / Runnable - [ ] JavaFX ## 目錄 - [資料型態 - Day 1~5](##Day1) - [Class - Day 6~10](##Day5) - [繼承與多型 - Day 11~15](##Day11) - [抽象與介面 - Day 16~20](##Day16) - [List概念與實作 - Day 21~24](##Day21) - [HashMap - Day 25](##Day25) - [泛型Generics - Day 26~28](##Day26) - [Inner Class - Day 29](##Day29) - [Garbage Collection - Day 30](##Day30) - [Annotation - Day 31~32](##Day31) - [Spring Boot - Day 33~??](##Day33) ## 學習雜談 - Day20 : > 老實說,我從來沒有想過可以這麼認真學一樣東西,雖然對大家來說可能很簡單,但對3分鐘熱度的我來說,真的是一大挑戰,不管最後是否有辦法精通Java,我都很滿意了! - Day33 : > 突然發現,最累人的不是學習,是寫筆記🤣,不過也正是如此我才可以把學習內容真正刻在腦中吧ww 不然通常是學了又忘,忘了又學。 - Day40 : > 逐漸感受到每天學習的痛苦了ww,撐下去啊!! **阿對了!如果你是用手機版看的話,版面會亂掉ww,請見諒** ## Day1 #### 學習重點 : Compile - Terminal的編譯方法 ⭐ - `javac 檔名.java` -> 編譯出class檔 - `java 檔名` -> 執行main函式內的程式 - 基本class架構 ```java= public class Compile{ public static void main(String[] args){ System.out.println("Hello World!"); System.out.println("YEAH"); } } ``` - 變數使用規則 ⭐⭐⭐ - 當印整數時,default是int,因此若輸入的 **整數過大** 需要使用到Long時,需要在數字後面加上L。 ```java= public class Variable { public static void main(String[] args){ System.out.println(3); System.out.println(300000000000L); } } ``` - 當印浮點數時,有雙精度Double與單精度Float,Float只會 **取到第7位** ,因此加上F後會自動四捨五入到小數點後第7位 ```java= public class Variable { public static void main(String[] args){ System.out.println(3.14159268F); //輸出3.1415927 } } ``` ## Day2 #### 學習重點 : DataType - 資料型態轉換 ⭐⭐⭐ - 資料型態 **數字range** 比較 : double > float > long > int > short > byte ```java= // 小範圍 -> 大範圍 byte a1 = 3; short a2 = a1; int a3 = a2; long a4 = 12345; //雖然指派時12345是int,但會自動轉型成宣告(long)的型態 float a5 = a4; //這也行的通 // 大範圍 -> 小範圍 int b1 = 3; byte b2 = b1; // 錯誤! double b3 = 3.5; float b4 = b3; //錯誤! float b5 = 10.5; //預設10.5是double -> 因此會錯誤! ``` - **強制轉換** : 有可能失真 ```java= int c1 = 3; byte c2 = (byte) c1; int c3 = 128; byte c4 = (byte) c3; //由於128超過byte (-127~127) 因此會失真 // 原理是將大範圍轉成二進位,去掉高位元,直到成為小範圍的二進位長度 double c5 = 3.1415926536; float c6 = (float) c5; //失真! ``` - **字串轉數字** : parse用法 ```java= String s1 = "123"; int d1 = Integer.parseInt(s1); String s2 = "10000000000"; long d2 = Long.parseLong(s2); String s3 = "3.14"; float d3 = Float.parseFloat(s3); String s4 = "3.1415926"; double d4 = Double.parseDouble(s4); ``` - **數字轉字串** : valueOf用法 ```java= String s1 = String.valueOf(123); String s2 = String.valueOf(555555L); String s3 = String.valueOf(3.14F); String s4 = String.valueOf(3.1415926); ``` ## Day3 #### 學習重點 : Operator - 運算子介紹 ⭐⭐ - 有些東西很熟就不放了,放一些有趣的~ ```java= public class Operator{ public static void main(String[] args){ int x = 5/2; // 左右皆為int型態 -> int商數(2) double y = 5%3.5; // 3.5為double型態 -> double餘數(1.5) System.out.println(x); // 印出2 System.out.println(y); // 印出1.5 boolean z = "Hello" == "Hello"; // String也可以比較 System.out.println(z); // 印出true int p = 1; p++; // 後置遞增(Postincrement) 先回傳後加1 ++p; //前置遞增(Preincrement) 先加1後回傳 } } ``` - 判斷式 ⭐⭐ - switch : 以switch中的**變數為基準與case比較**,像if (x == 5){} 這樣的感覺。 - **break很重要**,若沒有break,程式會以case成功的那行開始,後面的case都會忽略條件直接執行,直到遇到break或者switch底部。 ```java= int x = 5; swtich (x){ case 1: System.out.println("Yes 1"); break; case 2: System.out.println("Yes 2"); break; case 3: System.out.println("Yes 3"); break; case 4: System.out.println("Yes 4"); break; case 5: System.out.println("Yes 5"); break; // 跟else很像 default: Systen.out.println("Not in the list!"); break; } ``` - Standard Input (標準輸入) ⭐⭐ - Scanner用法 : 注意陷阱,close之後,程式就會停止輸入功能,因此建議整體程式只有一個Scanner物件,並在程式最後在關閉即可。 ```java= import java.util.Scanner; public class Comparison { public static void main(String[] args){ // new一個物件,指向Scanner記憶體位置 Scanner s = new Scanner(System.in); // 取得整數輸入 int x = s.nextInt(); System.out.println(x); // 取得字串輸入 String y = s.next(); System.out.println(y); // 關閉輸入功能 s.close(); } } ``` ## Day4 #### 學習重點 : Loop - 迴圈continue & break ⭐⭐ ```java= for (int i=1; i<5; i++){ if (i%2==0){ continue; // continue回直接回到for的判斷式當中,而不進行當前判斷式的後續程式 } System.out.println(i); } int j = 0; while (true){ System.out.println(j++); //先印出j後加1 if (j>5){ break; // 跳出迴圈 } } ``` #### Zerojudge : for + switch新手訓練 - 題目 : 利用a值判斷b&c做加減乘除,前面先輸入運算多少組數對。 ```java= import java.util.Scanner; public class Traning_loop { public static void main(String[] args){ Scanner s = new Scanner(System.in); int count = s.nextInt(); for (int i=0; i<count; i++){ int a = s.nextInt(); long b = s.nextInt(); long c = s.nextInt(); switch (a) { case 1: System.out.printf("%d\n", b+c); break; case 2: System.out.printf("%d\n", b-c); break; case 3: System.out.printf("%d\n", b*c); break; case 4: System.out.printf("%d\n", b/c); break; default: break; } } s.close(); } } ``` - 新知識 : %d可以代表整數變數的位置,用printf來做格式化輸出,其他**還有%s、%c、%f**...。 - 感想 : 測資一直沒過,還以為要用long型態,結果只是忘記格式化輸出ww ## Day5 #### 學習重點 : Array - 陣列 ⭐⭐⭐⭐ - 初始化 : 宣告array的資料型態,並給予長度,利用**new語法** - 初始化並沒有給予值時,自動填入0。 ```java= int[] x = new int[4]; // 沒有給予值狀況 double[] y = new double[5]; int[] z = new int[]{1,2,3} // 給予值時,中括號不要寫長度 ``` - 取得長度 **.length** ```java= int[] a = new int[3]; System.out.println(a.length); ``` #### Zerojudge : Array矩陣翻轉 - 題目 : 給予一個矩陣,求其翻轉後的形式 ```java= import java.util.Scanner; public class Traning_array { public static void main(String[] args){ Scanner s = new Scanner(System.in); for (int round=0; round<4; round++){ int row = s.nextInt(); int col = s.nextInt(); int[][] origin = new int[row][col]; int[][] trans = new int[col][row]; for (int i=0; i<row; i++){ for (int j=0; j<col; j++){ origin[i][j] = s.nextInt(); } } for (int j=0; j<col; j++){ for (int i=0; i<row; i++){ trans[j][i] = origin[i][j]; } } for (int a=0; a<col; a++){ for (int b=0; b<row; b++){ System.out.printf("%d ", trans[a][b]); } System.out.println(); } } s.close(); } } ``` - 解題思路 : 簡單來說我先用一個origin存原本的矩陣,然後把row跟col倒反,接者再用 **倒反的迴圈** 跑origin,再存到trans裡面之後再印出來。 - 感想 : 程式是可以正常運作了,但是總感覺能再精進,而且還被測資搞🤣,後來看到是總共有**四組矩陣**,所以我就寫for in 4跑四次才通過測試。 ## Day6 #### 學習重點 : Class的attribute - 編譯 ⭐⭐⭐ - 程式在編譯時,找尋的是.java檔案當中的class,可以有很多class ```java= // 假設在Test.java當中有兩個class class Test1{ } class Test2{ } ``` - 若使用javac Test.java -> 會編譯出Test1.class 跟 Test2.class - 執行 - 當編譯完後,利用 `java Test1` 執行時,程式會去Test1尋找 **main方法** 作為 **入口** 開始執行,因此若要讓class能夠執行,必須在其中加入main。 - static : 靜態語句 ⭐⭐⭐⭐⭐ - 當我加入static後,static成員會在 **載入時(執行前)** 與程式碼一起被放進記憶體空間當中,而不像一般成員要等到執行時,才會去跟記憶體要空間。 - 簡單來說 : static就是**未雨綢繆**,先放起來等著被呼叫。 ```java= class Test1{ static int a = 0; public static void main(String[] args){ System.out.println(a); // 存取本身類別的成員 Test2.text = "HAEY"; // 修改其他類別的static成員 Ssytem.out.println(Text2.text); // 存取其他類別內的static成員 } } class Test2{ static String text = "YEAH"; } ``` ## Day7 #### 學習重點 : Class的void、return value - void基本形式 ⭐⭐⭐⭐ - 這邊再補充一下,static加入後,可以讓成員本身變成 **class的屬性**,因此不需要instance就可以直接呼叫。 ```java= class Test{ public static void main(String[] args){ Test.print("Hello World!"); // 直接呼叫 } static void print(String msg){ System.out.println(msg); } } ``` - return ⭐⭐⭐ - 當我定義一個函式是 `static int add(){}` 類似這種形式的時候,就會需要回傳值,因為方法的型態不是void(無),而是int(整數)。 ```java= class Test{ static int add(int a, int b); return a+b; // 回傳a+b的值(int) } } ``` - 特別的是 : void雖然不須回傳就能運作,但也可以加入return作為 **中斷程式** 的一種方式。 ```java= class Test{ static void print(String msg){ if (msg == ""){ return; // 若參數的msg為空字串,就直接中斷 }else{ System.out.println(msg); // 不然就印出msg } } } ``` ## Day8 #### 學習重點 : Package & public用法、class分類 - class分類 ⭐⭐⭐ - 當有許多class在同一檔案中做不同事情,可以將其拆分成許多.java檔案 - 分類後,**class與檔名須相同**,才能於主程式調用 - 封包package概念 ⭐⭐⭐ - 如果檔案的階層長這樣 -> > 主程式.java math > > Add.java > > Multiply.java - 那麼在 `Add.java` 以及 `Multiply.java` 中就要加入 `package math;` 這樣的宣告 ```java= package math; ``` - 這樣表示 add 跟 multiply 是**屬於** math 這個 **封包** 當中 - 如果封包當中還有封包,則需要利用 `package math.geometry;` 像這樣子的宣告。 - public關鍵字 ⭐⭐⭐⭐ - 若我單純寫一般形式的函式,這樣是無法被 **封包外的檔案** 調用 ```java= class Add{ static int add(int a, int b){ return a+b; } } ``` - 加入public -> 使其能夠被封包外部檔案取用 ```java= public class Add{ public static int add(int a, int b){ return a+b; } } ``` - import關鍵字 ⭐⭐ - import可以方便我們在調用封包程式時,不用重複打封包名稱 - 利用剛剛的math封包,如果要在主程式調用,原本要這樣 -> ```java= public static void main(String[] args){ System.out.println(math.Add.add(1,2)); } ``` - 修改後變成 -> ```java= import math.Add; public static void main(String[] args){ System.out.println(Add.add(1,2)); } ``` - 看起來雖然沒什麼變🤣,但如果封包有很多層可能就會變成 `app.arithmetic.basic......`,這樣重複打超級麻煩! - 若一個封包有多個 `.java` 檔案,可以使用 `import math.*`,直接全部引入。 ## Day9 #### 學習重點 : (non-static) 物件、Constructor - 物件(object) v.s 類別(class) ⭐⭐⭐⭐⭐⭐ - 簡單來說,類別是 **藍圖**,物件是 **實體**,藍圖只有一張,但實體**可以有很多個**。 - 而藍圖中有固定的設定,像是手機藍圖 - 長寬高是固定的,但**顏色**有很多種,這樣我們可以設定 **長寬高為類別屬性(static)**,而 **顏色可以設定為物件屬性(non-static)** - new關鍵字 ⭐⭐⭐⭐⭐ - 藍圖到製作成實體的過程稱為 **建構**。 - 而new這個字就是 **建構**,因此我們要將藍圖做成物件,必須透過new - new出一個物件後,要找地方存放,那當然就是像int、double一樣,**類別本身也是一種資料型態**,因此可以宣告該類別的變數。 ```java= Cellphone c = new Cellphone(); // 類別宣告 變數名稱 = 建構 藍圖(); ``` - this關鍵字 ⭐⭐⭐ - 在指稱物件時,可以利用this來作為物件的代名詞 ```java= public class Player{ public String name; public void setName(String name){ this.name = name; } } ``` - 也就是當我new一個新的物件出來後,在使用setName這個方法時,我會取得 **該物件** 的name名稱,替換成 **傳進來的name**。 - 雖然一般來說會把參數的變數跟成員變數的名稱故意錯開,但this的出現可以保證電腦認得 **左邊是物件的name而右邊是參數的name**。 - Constructor ⭐⭐⭐ - 它本質上是一種方法,在 **建立物件** 的時候會自動呼叫。 - 若沒有定義,Java會自動建立**deafult constructor(無參數無內容的建構式)** - 名稱必須跟class相同 - 可以有參數,若有參數建構式,則不會自動建立deafult constructor ```java= public class Cellphone{ // 藍圖屬性(static) -> 賦值 public static double length = 11.5; public static double width = 5.5; public static double height = 1.2; // 物件屬性(non-static) -> 可以先有default或者在建構式當中再default public String color; // 初始建構式 > 自動呼叫 public Cellphone(){} // 建構式(有參數版本) public Cellphone(String color){ this.color = color; } // 類別的方法 public static void getSize(){ System.out.println("Length: " + length); System.out.println("Width: " + width); System.out.println("Height: " + height); } // 物件的方法 public void getColor(){ System.out.println(this.color); } } ``` ## Day10 #### 學習重點 : Access Modifiers - Modifiers ⭐⭐⭐⭐⭐⭐ - protected Modifier - 子類權限 : 用於 **繼承** 中,只能供子類繼承以及封包內使用。 - private Modifier - 私人權限 : 只能在 **類別內** 被調用 - default Modifier - 預設權限 : 只能給 **封包內** 的的類別調用 - 一般來說,通常會將 **同類型** 的類別放在同封包中,在封包當中創造一個 **對外接口 (使用public宣告) 檔案**,其餘可設定成default來提供對外接口檔案做調用。 - public Modifier - 公開權限 : 可以被 **封包外** 的類別調用 - **建構式** 通常會設成public,才能在外部建構物件。 ```java= class Test{ // 私人 private int x = 5; // 預設 int y = 6; // 公開 public z = 7; } ``` - Getter / Setter ⭐⭐⭐⭐ - 利用getter & setter來代替單純 `object.variable` 這種寫法 - 這樣才能達到class **封裝** 的效果 (管理哪些成員能夠被外部取得or更改) ```java= class Test{ private int x = 5; private int y = 6; // getter public int getX(){ return this.x; // x可利用getX取得 } public int getY(){ return this.y; // y也一樣 } // setter public void setX(int x){ if (x>0){ this.x = x; // 當x>0時(條件),x可被更改 } } } ``` ## Day11 #### 學習重點 : Inheritance - 繼承意義 ⭐⭐⭐ - 繼承通常被用在 **事物有相同性質** 時的場景,比如說,手機跟平板都有 **螢幕、產品型號...等等**,我們可以將這些相同性質設成一個類別-> Properties,再由Phone、Tablet去 **繼承**,這樣就不用在Phone跟Tablet寫重複的基本性質宣告了。 - extends關鍵字 ⭐⭐⭐⭐⭐ - 藉由剛剛的Properties以及Phone跟Tablet,我們說Properties是父類,而Phone跟Tablet是子類,子類繼承父類 -> ```java= public class Properties{ // 父類定義 public int size = 0; public String model = "WHAT"; } ``` ```java= public class Phone extends Properties{ // 子類 extends 父類 public int number; // 子類獨有性質 public void getSize(){ System.out.println(this.size); // 調用父類性質 } } ``` #### Zerojudge : 空間切割 - 這題是給定切割次數 -> 找出可切出幾個空間 - 這題利用 f(0) = 1、f(1) = 2、f(2) = 4、f(3) = 8 再利用牛頓差值法找出均差來。 ```java= import java.util.Scanner; public class Space{ public static void main(String[] args){ Scanner s = new Scanner(System.in); while(s.hasNextInt()){ int n = s.nextInt(); int result = (n*n*n+5*n+6)/6; System.out.println(result); } s.close(); } } ``` ## Day12 #### 學習重點 : 繼承中的建構式、super調用 - super 關鍵字 ⭐⭐⭐⭐ - 在繼承中,子類物件形成時會**先呼叫父類的建構式** (很合理嘛!要先有父親才有兒子),此時Java會在子類建構式中 **自動插入** `super()`(無參數) 來做為 **呼叫父類建構式** 的橋樑。 - 當父類的建構式朋友們中有**帶參數的建構式時**,我們就得 **自己寫super(參數)**,在括號中加入父類建構式需要的參數。 ```java= // 父類 public class Father { public String name; public Father(String name){ this.name = name; } public void print(){ System.out.println(this.name); } } ``` - 在子類建構式中,記得要 **帶有父類要求的name**,後面甚麼int num之類的才是子類自己本身的! ```java= // 子類 public class Son extends Father { public int number; public Son(String name, int num){ super(name); // 利用super作為呼叫父類參數建構式 this.number = num; } } ``` ```java= // 主程式 public class Inheritance { public static void main(String[] args){ Son s = new Son("Bob", 15); s.print(); } } ``` - 結語 : 總結來說,super讓子類在建構時,能夠遵守父類前,子類後的特性啦~ ## Day13 #### 學習重點 : 繼承中的物件轉換 -> Up/Down Casting - Private成員可以被繼承嗎? : ⭐⭐⭐⭐ - 我今天在學轉換時有個疑問 : 子類繼承時會包含父類的private成員嗎? - 答案是 : **會的**,儘管無法調用,但繼承這個動作本質上就是把父類的東西copy給子類,就像我爸給我一個寶箱,但不給我鑰匙,我雖然無法打開,但內容物還是我的 - 子類轉父類 ⭐⭐ - 本質上,子類擁有父類所有東西,因此在轉換時,不需過多於語法 ```java= class Inheritance{ public static void main(String[] args){ Son s = new Son("Bob", 5); Father f = s; // 直接把Son型態變數s 指派給 Father型態f // 或者直接寫 Father f = new Son("Bob", 5); } } ``` - 父類轉子類 ⭐⭐⭐⭐⭐ - 需要 **明確標示** 子類名稱 (因為一個父類可能有很多個子類) - 情境一 : **原本** 就是**父類**的物件直接轉子類物件 ❌ ```java= Father f = new Father("Bob"); Son s = (Son) f; // 失敗! ``` - 情境二 : **原本** 就是**子類**的物件,只是先轉成父類物件後,再轉回子類物件 ✅ - 注意! 當子類轉成父類後,子類獨有的東西都還在,只是到父類後不能存取。 ```java= Father f = new Son("Bob", 5); // 原本是Son物件 -> 轉成Father Son s = (Son) f; // 成功! 這邊把Father f 轉回 Son s ``` - instanceof 關鍵字 ⭐⭐⭐⭐ - 它用於判斷物件是否屬於某類別或者其子類,會傳布林值 ```java= Father f = new Son("Bob", 5); // 轉型成父類 System.out.println(f instanceof Son); // 回傳true! // 雖然f是Father型態,但它本身還是帶有Son獨有成員(只是不能被存取而已) System.out.println(f instanceof Father); // 回傳true! ``` ## Day14 #### 學習重點 : Protected Modifiers、Overriding - protected 關鍵字 ⭐⭐⭐⭐ - protected在Class繼承封裝當中扮演很重要的角色。 - 回顧Day11的Properties,單靠其屬性及建構式做出來的物件 **並沒有意義** - **需要** Phone跟Tablet等 **實際類別** 去繼承並做出物件,此時Properties就可使用protected僅開放給子類調用。 - Overriding子類覆寫 ⭐⭐⭐⭐⭐ - Overriding出現在子類跟父類有 **相同method且參數一樣** 時。 - 可見性 : 當父類的 `A method` 是 **private** 時,因子類不可見 -> 喪失覆寫權。 - 加入 `@Override` 在方法上面,像保險絲,編譯時會幫你找有沒有覆寫上的錯誤。 ```java= // 父類 public class Father{ protected String name; // 使用protected僅供子類調用 protected Father(String name){ // 使用protected僅供子類"建構" this.name = name; } public void sayHi(){ System.out.println("HI" + this.name); } } ``` ```java= // 子類 public class Son extends Father{ private int num; public Son(String name, int num){ super(name); this.num = num; } @Override // 標註該void為Overriding public void sayHi(){ System.out.println("HI " + this.name, + this.num); } } ``` ```java= // 主程式 public class Test{ public static void main(String[] args){ Father f = new Son("Bob ", 5); // 儘管轉型成父類(本質上還是子類),因此還是執行子類的覆寫方法 f.sayHi(); // out -> Hi Bob 5 } } ``` ## Day15 #### 學習重點 : Polymorphism多型 (Overriding具象化) - 多型的概念 : ⭐⭐⭐⭐⭐⭐⭐ - 在Overriding當中,我們知道子類可以對父類的同種方法進行覆寫,但這有甚麼意義呢? - 再次回顧Day11的Properties,如果今天Properties中有個 `按關機鍵事件`,在Phone當中可以 **覆寫** 按下去會進入省電畫面,而在Tablet當中會 **覆寫** 進入睡眠畫面。 - 這時利用 **不同子類型**,有不同樣的動作就稱為 **多型**。 - 在多型當中,通常會設定method **有父類物件參數**,但傳入子類物件,其運用到的就是 **覆寫+向上轉型**。 ```java= public class Properties{ public int size; public String model; protected Properties(int size, String model){ this.size = size; this.model = model; } public void close(){ System.out.println("已關閉螢幕."); } } ``` ```java= public class Phone extends Properties{ private String number; // Phone專有屬性 public Phone(int size, String model, String number){ super(size, model); this.number = number; } @Override public void close(){ System.out.println("進入省電模式!"); // Phone覆寫 } // getter public String getNumber(){ return this.number; } } ``` ```java= public class Tablet extends Properties{ public Tablet(int size, String model){ super(size, model); } @Override public void close(){ System.out.println("進入睡眠模式!"); // Tablet覆寫 } } ``` ```java= public class Test{ public static void main(String[] args){ Phone phone = new Phone(5, "A","0800092000"); Tablet tablet = new Tablet(15, "B"); Test.print(phone); Test.print(tablet); } public static void print(Properties p){ // 運用多型 p.close(); } } ``` - 我對多型的疑問 : ⭐⭐⭐⭐ - Q : 若我在子類添加一個 `private` A方法,在父類添加一個 `public/protected` A方法,在多型試驗當中,會不會因為轉型到父類後,而無法調用子類所覆寫的A呢? (注意! 父類若是private則不能被覆寫) - A : 答案是不會!當我呼叫A方法時,其機制就是去尋找這個方法原本所在的類別,就如同我 ==Day13中的private可以被繼承嗎?== 裡面想同的概念,編譯器會去尋找A方法的所在的 **初始記憶體位置(在子類內部,可以調用),不因轉型而改變!** ## Day16 #### 學習重點 : Abstract抽象 - 抽象化的概念 : ⭐⭐ - 簡單來說就是父類搞抽象、子類去實踐!爸爸的夢想小孩來實現! - 有時候父類別本身不具物件意義,需要被實踐才完整,此時就可以在父類別加上 `public abstract class`。 - 抽象化的**強制力** : ⭐⭐⭐⭐⭐ - 如果method **必須** 被override才能運作的話,就在父類別的void前面加上abstract,讓子類別 **必須** 寫一個override函式。 - 這提供程式開發者能夠確實覆寫類別的抽象方法 **(不能被忽略)** ```java= // 利用之前的Properties來做示範 public abstract class Properties{ // 抽象化 -> 本身不能建物件 public int size; public String model; protected Properties(int size, String model){ this.size = size; this.model = model; } public abstract void close(); // 抽象化 -> 不能有{}主體 (因為我們期待它是在子類被實現而不是父類) } ``` - Q : 非抽象類別可以有抽象方法嗎?如果是抽象類別可以有一般方法嗎? ⭐⭐⭐⭐⭐ - A : 不可以,可以。 - 前者不可以的原因是因為當非抽象方法被建構出來後,編譯器發現 : ㄟ?怎麼有個 **方法是抽象的,阿我要怎麼調用...** -> 很明顯有錯吧,所以行不通。 - 後者可以的原因是,抽象類別被子類繼承後,不見得每個method都要被覆寫!**因此有些method還是可以交給父類統一做就好**,所以抽象類別有一般方法是可以的! ## Day17 #### 學習重點 : Interface介面型態 - Interface : 子類的實作集合 ⭐⭐⭐⭐⭐ - Interface提供我們在 **不同類別、不同繼承結構** 中實作相同的method。 - Abstract則是作用在 **不同類別、同一繼承結構下** 的實作。 - 舉例來說,我繼承自「人」、小黑繼承自「狗」,我們明顯在 **不同繼承結構** 當中 (先不要說甚麼 : 阿都是哺乳類的w,我只是舉例🫠),但我們有共通動作「吃」,因此吃這個動作就可以放入Interface作為人類跟狗狗類別去實作。 - Interface 介面寫法 ⭐⭐⭐ - 它其實跟一般類別很像,只是其 **內部method不需要主體** (跟Abstract很像吧) - 不過Interface中也是可以有主體的(加上default或static),這裡的default不是modifiers,而是 **實作** ```java= public interface Eat{ void eat(); // 這裡不是default權限,而是public default void print(){ // 實作 System.out.println("Yummy"); } static void say(){ // 實作 System.out.println("WHAT"); } } ``` - implement 關鍵字 ⭐⭐⭐⭐⭐ - implement就是實作的意思,它被放在類別名稱後面 ```java= public class Me extends Human implements Eat { @Override // 必須得使用public,因為interface中都是public public void eat(){ // Override System.out.println("我是人,我吃了食物"); } } ``` - Interface casting ⭐⭐⭐⭐⭐⭐ - 由於Interface跟一般類別很像,因此 **它也有多型的寫法**,當我跟小黑都 **確實** 實作了Eat介面(亦即 **全部都有覆寫** Eat中的method),那麼就可以利用多型向上casting成Eat型態 (跟父類的感覺很像吧!)。 ```java= public class Test{ public static void main(String[] args{ Me m = new Me(); Test.getEatStatus(m); // 屬於Human子類、Eat介面型態 } private static void getEatStatus(Eat e){ e.eat(); } } ``` - 總結 : - 介面提供 **不同繼承結構** 中的類別可以實作同樣的動作,因此可以說 -> **繼承提供屬性連結,介面提供方法連結**。 - 介面讓編譯器只需要認得介面動作,不必要知道子類到底是誰即可運作。 ## Day18 #### 學習重點 : final用法 - 概念 : ⭐⭐⭐⭐⭐⭐ - final又可以說 **唯一性,不能被更動**,很類似於c++中的const,很酷的是它final可以用在class身上。 - **final class** : - class加入final亦即它 **不能被繼承**,這對於開發者來說很有幫助,畢竟有些類別是單獨運作的,不希望該class被拿去亂繼承。 ```java= // 加入final使其無法被繼承 public final class Test{ public static void main(String[] args){ ...略; } } ``` - **final method** - method加入final後,即不能在子類中Override該method,以保持父類class的獨特性。 ```java= public class Father{ public final void print(){ System.out.println("不要覆寫我w"); } } ``` - **final variable** - variable加入final,最關鍵的就是限制該變數只能被 **指派一次**-> 亦即賦值初始化後即不能再更動!常用在constant中。 - 不過final物件變數本身還是 **能夠更改其型態中非final的成員**。 ```java= public class Test{ public static final int PI = 3.1415; final Son s = new Son(); s = new Son(); // 錯誤! 不能再指派 s.x = 5 // 但是可以更改Son形態中的非final變數 } ``` ## Day19 #### 學習重點 : Lambda - Interface前導 ⭐⭐⭐⭐⭐⭐ - 我們知道在介面型態中,只需要宣告而不需要實作,若今天Interface中 **只有一個抽象方法** 我們即可利用lambda形式來節省「**創class、implements、overriding**」等過程。 - 我們稱只含一個抽象方法的Interface為 **functional Interface**,同樣可加上Annotation來幫助編譯器理解。 ```java= //------Interface檔案------- @FunctionalInterface public interface InterfaceLambda{ void print(); // 抽象方法 default sayHi(){ // 預先實作方法不影響其成為functional interface System.out.println("嗨"); } } ``` - Lambda基本形式 ⭐⭐⭐⭐⭐⭐⭐⭐ - 原本創物件寫法 ```java= //--------實作檔案----------- public class Lambda implements InterfaceLambda{ @Override public void print(){ System.out.println("I love Lambda!"); } } //---------主檔案------------ public class Test{ static void lambdaTest(InterfaceLambda l){ l.print(); } public static void main(String[] args){ Lambda l = new Lambda(); lambdaTest(l); } } ``` - 利用 `(參數) -> {主體}` 形式 ```java= //---------主檔案------------ public class Test{ static void lambdaTest(InterfaceLambda l){ l.print(); } public static void main(String[] args){ // 寫法一 InterfaceLambda l = () -> System.out.println("I love lambda!"); // 直接實作 lambdaTest(l); // 寫法二(最簡短) lambdaTest( () -> System.out.println("I love lambda!") // 直接實作 ); } } ``` - Lambda參數形式 ⭐⭐⭐⭐⭐⭐⭐⭐ - 當今天Interface中的抽象方法帶有參數,或者是非void方法需要這樣寫。 ```java= //------Interface檔案------- @FunctionalInterface public interface InterfaceLambda{ String print(String s); // 參數且回傳非void抽象方法 } ``` ```java= //---------主檔案------------ public class Test{ static void lambdaTest(InterfaceLambda l, String s){ l.print(s); } public static void(String[] args){ lambdaTest( (s) -> { System.out.print("HI" + s); return s;} , "I love lambda!") } } ``` - 總結 : 關於參數形式還有問題,但我需要休息一下,一天肝完有點難ww,明天繼續! ## Day20 #### 學習重點 : lambda簡化 - 單一簡化 ⭐⭐⭐⭐ - 參數 & return形式簡寫 - 單參數 -> **小括號省略**、單行return -> **大括號 & return關鍵字省略** ```java= //------Interface檔案------- @FunctionalInterface public interface InterfaceLambda{ String str(String s); // 帶有一參數 } ``` ```java= //---------主檔案------------ public class Test{ static void lambdaTest(InterfaceLambda l, String s){ System.out.println(l.str(s)); } public static void(String[] args){ lambdaTest( s -> "HI " + s, "I love lambda!" ); // (s) 省略為 s // {return "HI " + s;} 省略為 "HI " + s } } ``` ## Day21 #### 學習重點 : ArrayList (Collection Framework) - Array複習 ⭐⭐ - Array是一個 **固定容器**,裝著相同型態的物品,而我們宣告它的方法就是利用 `型態[]`表示容器,在初始化的時候,就必須對其賦值,因此大小不可改變。 ```java= String[] names1 = new String[5]; // 固定容量為5 String[] names2 = { // 固定容量為3 "Alice", "Bob", "Carol" }; ``` - ArrayList概念 ⭐⭐⭐⭐⭐⭐ - ArrayList表示的是一個 **動態陣列**,不需在初始化的時候給予容量大小,而是根據使用者新增or刪除 **動態調整容量**。 - 無法裝 **primitive type**(原始型別 : int、double...),可以裝Objects、**wrapper types**(Integer、Character、Boolean)。 ```java= public class Test{ public static void main(String[] args){ // 型態宣告於前面,後面可省略不寫 ArrayList<String> names = new ArrayList<>( Arrays.asList("Alice", "Bob", "Carol") ); // asList方法給予陣列 System.out.println(names.get(0)); // Alice names.set(0, "Ariel"); // 替換陣列元素 System.out.println(names.get(0)); // Ariel names.add("Daniel"); // 加入元素在尾巴 System.out.println(names.get(3)); // Daniel // 也可以直接選擇物件刪除,不一定要是index names.remove("Daniel"); } } ``` - toString差異 ⭐⭐⭐⭐⭐⭐ - 在Object類別當中,有一個toString用法,當我們在 `System.out.println(物件)` 時,編譯器會使用 `.toString()` 的method將其印出來。 - Array - 由於Array物件 **並沒有覆寫** Object.toString()方法,因此編譯器會直接執行父類別Object的toString,導致印出來的是物件的記憶體位置。 - ArrayList - ArrayList **有確實覆寫** Object.toString(),因此可以印出完美的陣列長相,ArrayList同時給予很多方便的method,如上面所展示,這都是 **Collection Framework** 的功勞(雖然我還沒學ww)。 - 實際應用層面的差異 ⭐⭐⭐⭐⭐⭐ - 儘管ArrayList提供很多method,但是由於其 **內部儲存的是object**,因此會比一般Array存prmitive type還要慢上很多,因此在開發ML or 矩陣運算 or 定量資料時,還是會選擇使用Array。 ## Day22 #### 學習重點 : LinkedList & 淺淺的資料結構 - LinkedList與ArrayList的差別(part 1) ⭐⭐⭐⭐⭐⭐⭐⭐⭐ - 兩者雖然都是List介面下的實作,架構相同,但內容卻大相逕庭,兩者對資料處理的方式有很大的不同。 - 這邊稍微跳槽到資料結構的部分來看看LinkedList,淺淺的碰一下就好🤣,畢竟主軸在學Java #### LinkedList的指向,參考 [GeeksforGeeks : Types of Linked List](https://www.geeksforgeeks.org/dsa/types-of-linked-list/) - 單指向(Singly LinkedList, SLL) ![image](https://hackmd.io/_uploads/HJYML_CSbl.png) - 所謂單指向就是指每個node(節點)存有下個Data的address pointer - 雙指向(Doubly LinkedList, DLL) ![image](https://hackmd.io/_uploads/Hy2PwO0H-g.png) - 雙指向亦即每個node都存有previous以及next的Data address pointer #### 與單指向(SLL)比較 : 查找數據 - 儘管記憶體必須 **比SLL多出一些空間來存prev**,但查找數據時,**可比SLL少一半時間**(因為可根據index位置來決定要從Tail開始還是從Head) #### 與單指向(SLL)比較 : 刪除數據 - 若在單指向中要刪除node G,我們必須知道G前面是誰,因此要從頭開始跑,跑到F之後再把F的next pointer接到H身上,這樣超級麻煩。 - 但在雙指向中,node G本身有存prev跟next位置,可以直接利用 ```java= G.prev.next = G.next G.next.prev = G.prev // 這樣替換即可刪除G數據 ``` - 迴圈指向(Circularly LinkedList, CLL) ![image](https://hackmd.io/_uploads/BySG3_0BZx.png) - 迴圈指向中tail的next指向 **並不是null** 而是head的位置,因此可以達到循環。 - 像是聽歌時,歌單是一個List,當我開啟循環播放後,最後一首歌所指向的就是第一首歌。 ```java= // 原本(in SLL or DLL) tail.next -> null // 開啟循環播放(in CLL) tail.next = head // 指派head位置給tail.next ``` - Java中的LinkedList類型 - 在Java中,通常採用Doubly LinkedList,也蠻好理解的? 明天來嘗試實作看看這些類型的LinkedList好了,順便跟ArrayList比較看看。 ## Day23 #### 學習重點 : LinkedList-DLL實作 + remove功能實現(整合Class觀念) - 原本我只是想單純把Geeks那邊的code拿過來看一看抄一抄,但抄著抄著就來勁了🤣,直接加了個remove功能,順便整合之前學的Class建構跟一些關鍵字用法,腦袋打結了不知道多少次,但最後解開超開心,[完整程式碼 in github](https://github.com/learning-official/JavaLearning/tree/main/LinkedListExercise)。 #### 製作LinkedList架構思路 : 🔥🔥🔥🔥🔥🔥🔥 - DLL.Node型態 - data : **int**、prev : **Node**、next : **Node**) - 做一個Node class ```java= private static class Node { Node next, prev; int data; Node(int data){ this.next = null; this.prev = null; this.data = data; } } ``` - LinkedList物件 : - 數組輸入 - 依序new出Node物件並放進其data成員中,形成一組Node陣列 - 將.next以及.prev串起來 - Node[0] 存入 this.head、Node[length-1] 存入 this.tail ```java= LinkedListT(int[] array){ if (array.length == 0) { System.out.println("陣列是空的!"); return; } Node[] nodeG = new Node[array.length]; this.length = array.length; for (int i = 0; i<array.length; i++){ nodeG[i] = new Node(array[i]); } for (int i = 0; i<array.length-1; i++){ nodeG[i+1].prev = nodeG[i]; nodeG[i].next = nodeG[i+1]; } // 讓head作為nodeG[0]的別名,當建構完成後,nodeG消失,head留著 // 並存有next的位置 -> Reachability,不會被GC回收 this.head = nodeG[0]; this.tail = nodeG[array.length - 1]; } ``` - remove功能 : - 利用基本的 `node.next.prev = node.prev` 以及 `node.prev.next = node.next` 串接 - 使node.next 以及prev = null 進行斷鍵,讓node不被指也不指別人 -> **Unreachable** -> 被刪除(GC) ```java= public void remove(int index){ if (this.length == 0) return; Node node = this.head; // 讓node跟head指向同一塊記憶體位置,node是head的reference // node本身是要被刪除的那個,因此整體都是由node在跑 if (index <0 || index >= length){ System.out.println("超出陣列範圍!"); return; } for (int i = 0; i < index; i++) node = node.next; // 顧好Head位置 if (node.prev == null){ this.head = node.next; if (this.head != null) this.head.prev = null; }else node.prev .next = node.next; // 顧好Tail位置 if (node.next == null){ this.tail = node.prev; if (this.tail != null) this.tail.next = null; } else node.next.prev = node.prev; // 最後要讓node本身跟指向斷開 node.next = null; node.prev = null; this.length -= 1; } ``` - 老實說,我突然想到可以用多建構子的方式,可以針對不同型態做儲存,不過太麻煩了,算了ww,明天再來跟ArrayList做比較🔥 ## Day24 #### 學習重點 : ArrayList與LinkedList的詳細比較 - 關於Access的差別 ⭐⭐⭐⭐⭐⭐ #### LinkedList : - 由前兩天所學的,可以猜到它是 **Sequential access**,也就是**搜尋時**要從head or tail開始搜尋,**時間複雜度O(n)**。 #### ArrayList : - 利用 **Random access**,相較於LinkedList每個node位置是分散的,ArrayList的**數據位置是相連的**,因此在搜尋時,只需利用 `陣列起始位置 + (index * 型態的byte)` 即可存取,**時間複雜度O(1)**。 - 關於Insert & Remove的差別 ⭐⭐⭐⭐⭐⭐ #### LinkedList : - 在新增或刪除只需要針對該index的node.next跟prev動手腳就好,斷開再接起來就這麼簡單,**理論上 : 時間複雜度O(1)**... 不過在實際處理中,若不知道該節點位置,就必須利用**for迴圈**跑到該index進行動作,因此 **時間複雜度又變成O(n)** #### ArrayList : - Array在新增跟刪除資料就整個超搞剛,要先copy整坨資料再一個一個放進 **新的Array** 當中,再把要刪除或新增的資料根據index隨著copy過程放進去,因此**時間複雜度O(n)**。 - 關於實際執行的反差 ⭐⭐⭐⭐⭐⭐⭐⭐⭐ - 雖然理論上LinkedList有其厲害之處,但是在實際計算上由於它的**記憶體不連貫性** 導致在增刪當中除了要for迴圈找index(**O(n)**)之外還要讓在不同位置跳來跳去,讓整個讀寫速度慢的有剩。 - 不過如果是 **已知node位置 or 只對head動手腳** 的情況 **O(1)**,那麼還是用LinkedList比較好。 - 而在ArrayList當中,由於它**記憶體是連貫的**,CPU剛好對整塊記憶體的copy很有效率,也**符合cache的邏輯**,因此儘管同樣是O(n)但實際還是會比LinkedList快。 #### 總結來說,ArrayList還是主要動態陣列用法。 ## Day25 #### 學習重點 : HashMap、Hash原理 - HashMap介紹 ⭐⭐⭐⭐⭐⭐ - 實作Map介面,與Python中的Dictionary相同,都是儲存key、value。 - 與List兩個實作相同,**只能存Wrapper Classes**,不能存primitive type ```java= import java.util.HashMap; public class Test{ public static void main(String[] args){ HashMap<String, Integer> dict = new HashMap<>(); // 新增資料 : {Alice=100} dict.put("Alice", 100); // 刪除資料 : {} dict.remove("Alice"); // 替換資料,若key不存在,就不動作 : {} dict.replace("Bob", 80); // 若該key不存在就新增,反之則不動作 : {Carol = 70} dict.putIfabsent("Carol", 70); dict.get("Alice"); // 用key查value : null(因為找不到) dict.containsKey("Apple") // false dict.containsValue(100) // false } } ``` - HashMap存資料的原理 ⭐⭐⭐⭐⭐⭐⭐⭐⭐ - 首先,Map本身是以 **key作為index** 加上 **以array儲存value** 建立起來的。 - 因為是用Array,因此在搜尋時,**時間複雜度為O(1)**。 --- - 假設我們要求使用者建立帳號,會建立User物件,其中有username以及password,我們以 **username作為key**,User物件作為value,對username做hashing,而實際hashing跟存取方式請看下方 -> #### HashFunction : 👉key轉化為index - 其存在的意義就是讓單純的key被hash成一個 **難以被反推** 的數字(**Hash code/key**),並以它做為Array的index。 #### Bucket : 👉裝User的地方 - 由於Hash code通常較大,若將所有hash code作為index存取,整個陣列會超級龐大。 - 因此會利用mod,`"hash(key)" mod "length"`,縮減成0~length-1的大小。 - 而每個index又稱Bucket,因為可能有多個key經hashing跟mod後,index還是一樣,此時即為Collision。 #### Collision : 👉一堆User住同樣的Bucket - 當多個hash code經模運算後得出相同的index就稱為Collision。 - 若發生衝突,在搜尋時就變成多個key對上同一User,此時有兩種解決方案 : **Open addressing、Chaining**。 #### Open Addressing : 👉處理多User住同Bucket的問題 - 簡單來說,就是發生衝突時就往下找直到找到空位(其他bucket) 放進去,這個動作叫做**Linear probing(線性探測)**。 - 但這衍伸一個問題 - 當過多hash code被分配到同一Bucket,由於線性探測作用,會造成附近的Bucket都塞滿,此時稱為**Clustering(很擁擠)**。 - 此時可以用Quadratic或者Double probing來產生較亂的probing順序以減少Clustering發生。 #### Load Factor : 👉負載因子 - 用以說明 `目前資料量 / 該array的長度`,Load Factor越接近1,Clustering機率越高,此時就得resize array(擴容) & rehash data,因此我們得盡可能將Load Factor控制在75%以內,**以保持O(1)**。 #### Chaining - Closed Addressing : 👉傳統上處理多User住同Bucket的辦法 - 傳統上,發生Collision時,會利用LinkedList來存與Bucket衝突的所有User,並在User類別中存入該node的nextIndex。 - 因為是用LinkedList,所以如果Hash Function設計不好導致Collision,而進入到LinkedList這個地步,**時間複雜度就回到O(n)**。 ## Day26 #### 學習重點 : Generics泛型 - 1 - 泛型是甚麼? ⭐⭐⭐ - 在List跟Map當中都可以看到 `< >` 這個東西的存在,我們知道它是在宣告陣列 & Map的 **型別**,也難怪叫做泛型,因此泛型可以說是在我們在型別使用上的 **變數** - 當我們需要**對多個型別做同樣的事情**,一般來說要寫N個同樣的檔案,只是型別不一樣,這時就可以利用泛型來整合成一個檔案。 - 由於泛型的實作是**透過Object來做的**,因此在使用泛型時,其實是以 "它是extends自Object" 來實作的,因此必須是封裝型態,所以才**不能使用int、double**等原始資料型態,而是用Integer、Double...,這樣才能拿來宣告 **物件**。 - 泛型怎麼用? ⭐⭐⭐⭐ - 在泛型當中,有三種用法 `Generic Class / Interface / Method` - 今天卡忙,僅討論Class的用法,我們會在Class名稱後方加入 `< >`,中間就是放入 **型別變數名稱**,因此可以自己取名,但是一般都用T (Type)。 ```java= public class Test <T> { // 底下就可以利用T,以是一個型態的用法下去用。 private T Athing; public Test(T Athing){ this.Athing = Athing; } public void print(){ System.out.println(Athing); } } ``` - 測試時就用跟List & Map相同的方式下去做 ```java= public static void main(String[] args){ Test<Integer> testInt = new Test<>(55); testInt.print(); // 印出55 } ``` ## Day27 #### 學習重點 : Generics泛型 - 2 - Generics class的繼承 ⭐⭐⭐⭐⭐⭐ - 由於T本身也是代表一個類別,因此我們也可以利用extends來 **限縮**,T的範圍 由**大範圍Object -> 小範圍Properties** [點我看Properties在幹嘛](#Day15) ```java= //-------------泛型檔------------- public class Generics <T extends Properties>{ T Athing; public Test(T Athing){ this.Athing = Athing; } public void close(){ Athing.close(); } } //-------------主程式------------- public class Test{ public static void main(String[] args){ Generics<Phone> phone = new Generics<>( new Phone(5, "A1", "0800092000") ); phone.close(); // 使用phone物件帶著Phone型態 System.out.println(phone.getNumber()); // 調用獨有形態,這是多型做不到的 } } ``` - 當然,有繼承就有實作,因此在Generics當中也可以使用Implements,不過在<>當中一樣是用extends來當作 **實作**,因此我們會把 **繼承** 放在最前面,實作放在後面,以 `&` 做分隔 ```java= public class Generics <T extends Properties & aInterface> ``` - 泛型跟多型到底差在哪? ⭐⭐⭐⭐⭐⭐⭐⭐⭐ - 簡單來說,泛型主要注重 **對內型別的精確度**,因此注重**型別本身** 的 **獨有method**。 - 而多型注重 **對外一致性**,因此使用的是 **子類** 的 **共同method** (也就是父類的protected method)。 - Generics method ⭐⭐⭐⭐⭐⭐⭐⭐ - 它有點多型的影子,但就如同上面所說,它還是注重在型別本身。 ```java= public static <T, K> void print(T Athing, K Bthing){ Athing.print(); Bthing.print(); } ``` - Wildcard關鍵字 : `?` - 使用List來理解,當我們想print出List時,需要利用 `?` 來作為 **未知型別** 的符號。 - 同時我們也可以對wildcard做 **限縮** 範圍的動作。 ```java= public static void main(String[] args){ List<Integer> intList = new ArrayList<>(); intList.add(5); printA(intList); List<Phone> phoneList = new ArrayList<>(); phoneList.add(new Phone(5, "A1", "000")); printB(phoneList); } // 原本長這樣 private static void print(List<Object> o){ System.out.println(o); } // 雖然Integer是繼承自Object // 但如果是List<Object> & List<Integer>就沒有繼承關係了! // 因此要用 ? 來作為未知型別 private static void printA(List<?> o){ System.out.println(o); } // wildcard繼承型別 private static void printB(List<? extends Properties> p){ System.out.println(p); } ``` ## Day28 #### 學習重點 : Generics泛型 - 3、Varargs用法、Type Erasure - LinkedList泛型實作 ⭐⭐⭐⭐ - 我把前幾天一直在做的LinkedList加上了泛型用法,這樣就更還原原版LinkedList了! [看我的LinkedList實作](##Day23) - 在其中我還學到了利用varargs(可變參數)的用法! #### 原本我是這樣做 ```java= public class LinkedListT<T>{ private static class Node<T>{ Node<T> next, prev; T data; Node(T data){ this.next = this.prev = null; this.data = data; } } public LinkedListT(T[] array){ // ...省略 for (T data : array){ // ...省略 } // ...省略 } } ``` - 這樣在主程式中只能這樣宣告 ```java= public static void main(String[] args){ Integer[] intList = {1,2,3,4}; LinkedListT<Integer> test = new LinkedListT<>(intList); } ``` #### 加上varargs之後 ```java= public LinkedListT(T... array){ // ...省略 } ``` ```java= public static void main(String[] args){ LinkedListT<Integer> test = new LinkedListT<>(1,2,3,4); } ``` - Varargs到底是甚麼? ⭐⭐⭐⭐ - varargs = **Var**iable **Arg**uments,允許多個 **相同型態** 的參數以陣列形式被打包作為單一參數 - 這樣我們就不用同一種類型要寫一堆同樣的method了! ```java= // 原本要這樣寫 int add(int a, int b){ return a+b; } int add(int a, int b, int c){ return a+b+c; } int add(int a, int b, int c, int d{ return a+b+c+d; } // 利用varargs可以這樣寫 int add(int... a){ int sum = 0; for (int i : a){ sum += i; } return sum; } ``` - **Type Erasure** ⭐⭐⭐⭐⭐⭐⭐⭐⭐ - 由於舊版java沒有泛型的功能,為了讓後期的程式能在舊版JVM當中執行,因此編譯後的class檔就不會有泛型出現,而是一堆Object。 - **Compile-time**時,泛型會檢查型態是否符合(Type checking),若我在<>中放入String,但是參數卻寫入Integer,此時就會報錯。 - **Runtime前(Compile後)**,所謂的Integer、String等等都會被Type Erasure的機制而變成Object,若編譯沒有報錯,成功轉成Object後,java就會在每個利用泛型T的地方自動casting。 - **Runtime**時,我們就不會看到任何泛型的影子了。 ## Day29 #### 學習重點 : Inner Class - Inner class用在哪? ⭐⭐⭐⭐⭐ - 在前幾天我做Node節點時,就突然想到為甚麼有Inner Class這種東西,(雖然我寫得很理所當然🤣 - 簡單來說,Inner class就像機器的零組件,獨立出來沒什麼意義,但又是機器的一部分,因此我們會把這種零組件的class寫進去機器class當中。 - Inner class怎麼寫? ⭐⭐⭐⭐⭐⭐⭐ - 首先有分`non-static` & `static`,而non-static又有分member、local、anonymous三種。 #### **non-static** : member inner class ```java= public class OuterClass { public void printOuter(){ System.out.println("HI this is outer class"); } // inner class public class InnerClass{ public void pirntInner(){ System.out.println("HI this is inner class"); } } } ``` - 在外部建立 **non-static** inner class物件時,必須**先**建立outer class物件,再利用 **outer物件** new出 **inner物件**。 ```java= public class InnerTest { public static void main(String[] args){ OuterClass outer = new OuterClass(); // 超神奇的寫法吧 outer.new建構出inner物件 OuterClass.InnerClass inner = outer.new InnerClass(); inner.pirntInner(); } } ``` - 不過一般來說,**non-static**的inner class通常會設成private,因為只是要給內部成員使用,就像Node class一樣。 #### **non-static** : Local inner class - 雖然說這幾乎不會用到,但是還是寫一下好了w ```java= public class OuterClass{ public void sayHi(){ System.out.println("HI"); class LocalInnerClass{ String name = "小八"; public void sayName(){ System.out.println(name); } } LocalInnerClass what = new LocalInnerClass(); waht.sayName(); } } ``` - 簡單來說就是在method當中建立class,這個class生命週期只會在runtime的時候出現而已。 #### **non-static** : anonymous inner class - 先說,它就像extneds class或implements interface一樣。 - 這個還蠻有趣的,它就像是 **單次使用** subClass,我可以建立一個A類別的物件但同時覆寫A類別的方法,有點神奇,讓我們看code! ```java= public static void main(String[] args){ OuterClass outerAnony = new OuterClass(){ @Override public void sayHi(){ System.out.println("Hi"); } } outerAnony.sayHi(); // 印出Hi } ``` - 這就是**單次覆寫**,outerAnony型態上雖是OuterClass,但 `sayHi` method卻有覆寫。 - 更簡短也可以這樣寫 -> ```java= new OuterClass(){ @Override public void sayHi(){ System.out.println("Hi"); } }.sayHi(); ``` #### 關於inner class調用local variable - 若在method當中宣告的區域變數要被 `local inner class` 調用,需將該變數設final形式。 - 原因是當我們在local class當中 **return了物件** 且有關local variable的東西,當method結束,變數就消失了,但物件卻留著,就會導致物件找不到變數,發生錯誤,因此 **Java會自動copy** variable到inner class中。 - 但當我們在method當中又改了該變數,Java會很問號??要不要再copy一次,因此乾脆 **統一設定成final**。 #### static : member inner class - 基本上就是在class前面加上static即可 ```java= public class OuterClass { // ...省略 // static inner class public static class InnerClass{ // ...省略 } } ``` ```java= public class InnerTest { public static void main(String[] args){ // 這邊就改成利用Outer.Inner這樣做 OuterClass.InnerClass inner = new OuterClass.InnerClass(); inner.pirntInner(); } } ``` - 為何不用Lambda就好? ⭐⭐⭐⭐⭐⭐⭐ - 仔細看Lambda跟Anoymous會發現真的好像喔!不過還是有部分差異 : #### lambda - 用在**functional interface**中 - **Only** 覆寫一個method - 優點在於語法簡潔且專一性高 #### Anonymous - 用於**class**以及interface中 - 可以覆寫**一堆method** - 優點在於可以實作**複雜的interface**(多個抽象method) ## Day30 #### 學習重點 : Garbage Collection (GC) - 甚麼是Garbage Collection? ⭐⭐⭐⭐⭐ - 簡單來說,它像是程式清潔員,把一些沒在用的實體阿物件阿全部從記憶中釋放,以保持記憶體是乾淨的空間。 - 它本質上就是一種演算法,利用標記(Mark)、清除(Sweap)在做事。 - 一般來說GC運作時會**stop-the-world pause**,也就是暫停程式,因此好的GC就是讓STW的時長越短越好。 - 它實際怎麼運作的? ⭐⭐⭐⭐⭐⭐⭐ - 當我們在new物件時,都會去跟記憶體要空間,久而久之記憶體就理所當然的被塞爆了嘛! - 基於許多物件生命週期都不長的原則,Java採用Generational GC,分出**Young generation**、**Old generation**,還有一個不歸GC管的 **Metaspace**。 #### Young Generation : - 此區域是**最頻繁**被GC偵測的部分,因為 **物件的出生點** 都在這! 當GC掃過整個Young Gen的區塊後,沒有reference的物件,或者說沒有被指向的物件,都會被清理掉(Sweap),而有reference的物件則會被標記起來(Mark) - 當物件被偵測過幾次都保有reference,此時GC就會將其**打包送往Old Gen**,讓Young Gen變得更乾淨。 #### Old Generation : - 這個區域不常被偵測,因為這裡存放的都是從Young Gen存活下來的物件,**始終有reference**,它的出現讓Young Gen不用每次都要掃整個Heap,可以專心看那些新的物件,省下許多時間。 #### Metaspace : - 它本身是存放固定靜態的東西,像是Class架構、method名稱等不可能變動的東西。 - 在Java7之前,**它叫做PermGen**,跟著Generation一起在Heap堆中玩樂w,但在Java8之後被踢出Heap區,因為基本上Metaspace的東西**都是固定的**,沒有必要跟Young/Old Gen這種動態區塊放在一起。 - 它存儲於Native memory區塊,因此能存多少,就看我們電腦RAM有多大(不過通常會給它一個MaxSize,不然等等電腦爆掉ww) - GC參數設定 ⭐⭐⭐⭐ - 由於Heap是動態的,因此在執行之前就必須給定參數如 -> Xms初始記憶體、Xmx最大記憶體、Young/Old gen的比例(Ratio)、GC的種類 -XX:+UseG1GC。 - 當然Metaspace也需要給定MaxSize以防止它占用太多Native memory。 - Heap如果設的太大,GC清理頻率⬇️,清理時長⬆️,設的太小就是相反。 - GC的種類 ⭐⭐⭐ - 自Java9後,預設都是採用 **G1GC**,因為它能夠切分Heap區塊,有效清理垃圾。 - 另外還有Parallel GC、ZGC、CMS GC等,不過等有興趣再來看吧! ## Day31 #### 學習重點 : Annotation - Annotation是甚麼? ⭐⭐⭐⭐ - 在前幾周的學習中,`@Override`、`@FunctionalInterface` 這種東西常常出現,它們都是一種Annotation。 - 跟一般註解不一樣的是,它們可以對class、method、variable產生作用。 - 基本架構 : ⭐⭐⭐⭐⭐⭐ - 跟interface很像,只是多加一個 `@` 而已,所以本質上操作跟interface差不多。 ```java= @Retention(RetentionPolicy.RUNTIME) // 作用時機 @Target(ElementType.TYPE) // 作用對象 public @interface Annoatation{ } ``` - 作用時機與作用對象 : ⭐⭐⭐⭐⭐ - annotation可以設定其作用的時機,也同時決定了它的生命週期 #### 作用時機 : 分為三種 `Runtime`、`Class`、`Source` - 利用Retention來設定。 - **Runtime** 就是在執行時還保持作用。 - **Class** 就是編譯時供編譯器作用,運行時被丟棄。 - **Source** 在原程式碼中作用,編譯後被丟棄,不被.class保留,像 `@Override`。 #### 作用對象 主要有三種 `Class`、`Method`、`Field` - 利用ElementType來設定,就單純設定作用對象,應該很好理解w - 怎麼找annotation ⭐⭐⭐⭐⭐⭐⭐⭐ - 基本上在做annotation時,我們會想要針對有annotation的class、method、variable做事情,就像 `@Override` 一樣是針對覆寫method去check。 - 這邊利用class來舉例 : - 要確認class是否有加註annotation,就是在判斷式當中利用 `物件.getClass().isAnnotationPresent(annotation.class)` 來取得boolean回傳並做後續動作。 ```java= public static void main(String[] args){ AnnotationTest cat = new AnnotationTest("Alice"); if (cat.getClass() .isAnnotationPresent(ClassAnnotation.class) ){ System.out.println("This is an annotation class!"); } } ``` - method與invoke ⭐⭐⭐⭐⭐⭐⭐ - 我們可以針對有annotation的method經判斷來呼叫method。 ```java= try{ // 利用try catch確保存取的是非私有method // 利用Method型別先跑過整個class中的method for (Method method : cat.getClass().getDeclaredMethods()){ if (method. isAnnotationPresent(MethodAnnotation.class)){ // 放入物件並以該物件呼叫 method.invoke(cat); } } } catch (IllegalAccessException e){ System.out.println(e.getCause()); } catch (InvocationTargetException e){ System.out.println(e.getCause()); } ``` - Field特殊找法 ⭐⭐⭐⭐⭐ - 基本上對有Annotation的變數找法跟method一樣,但是在最後會多一個找型別的動作 -> ```java= try{ for (Field field : cat.getClass().getDeclaredFields()){ // 由於field存取的是value // 因此利用該value是否instanceof某個type // 並存到str中進行動作 Object obj = field.get(cat); if (obj instanceof String str){ System.out.println(str); } } } catch (IllegalAccessException e){ System.out.println(e); } ``` - Annotation參數設置 ⭐⭐⭐⭐⭐⭐⭐⭐ - 我們可以在 **annotation設置參數**,但我們知道在**interface當中不能設變數** - 因此把它弄成method -> 需要被實作(指派、賦值),這種method不會有參數,因此可以把它當作一種變數的變形? - 當然型態都是primitive或者String等。 - 當然也能設預設值,利用[default實作 -> 注意不是modifiers](##Day17),跟interface一樣。 ```java= public @interface Test{ int times() default 5; } ``` - 要加上參數就像 `@annotation(times = 3)` 這樣。 - 而要取得參數值就是利用(以Method舉例) -> ```java= for (Method method : cat.getClass().getDeclaredMethods()){ if (method. isAnnotationPresent(MethodAnnotation.class)){ // 以interface物件來存method上annotation的實作 MethodAnnotation ma = method. getAnnotation(MethodAnnotation.class); // 利用time取得來觸發method for (int i = 0; i<ma.time(); i++){ method.invoke(cat); } } } ``` ## Day32 #### 學習重點 : Annotation實作、reflection反射 - 反射是甚麼? ⭐⭐⭐⭐⭐ - 昨天在看Annotation的時候,一直很不理解反射是甚麼神奇的東西,今天決定好好來研究下。 - 簡單來說,反射就是在 **Runtime** 時還能 **取得Class資訊** 的一個動作。 - 當我們寫 `物件.getClass().get...` 其實就是以物件所屬類別視角來做一些動作。 ```java= AnnotationTest at = new AnnotationTest(); // 以物件.getClass() = class視角來看本身 Method[] methods = at.getClass().getDelaredMethods(); for (int i=0; i<methods.length; i++){ System.out.print(methods[i].getName() + " "); } ``` - Annotation實作 ⭐⭐⭐⭐⭐⭐⭐ - 由於我實在搞不懂annotation真正的用處在哪,所以請AI給我個例子,我再抓下來實作 ( 可能真的得等我自己開發東西的時候才會很有感吧w - 這邊實作一個annotation用來 **確保變數不是null** 的功能!  - 想法是這樣的 : - 1️⃣ 建立fieldCheck註解 : 意義 - 使必須被賦值的variable都**不能為null**。 - 2️⃣ 於fieldCheck中加入**message** : 偵測時可在terminal給予error。 - 3️⃣ 利用for-loop在checkNull中找到**有annotation**的變數。 - 4️⃣ 確認變數是否是null,若否則pass,若是則噴出message。 - **checkNull** 長這樣(好亂ww) : ```java= public static void checkNull(Object obj) throws IllegalAccessException{ for (Field field : obj.getClass().getDeclaredFields()){ if (field.isAnnotationPresent(FieldCheck.class)){ field.setAccessible(true); Object value = field.get(obj); if (value == null){ System.out.println("error : " + field.getAnnotation(FieldCheck.class).message()); }else{ System.out.println("pass : " + field.getName()); } } } } ``` - Override復刻 - Runtime ⭐⭐⭐⭐⭐⭐⭐ - 由於Override本身是在**Source時期偵測**,要用到Processor,以我目前的能力應該不太可能w - 所以我改成**在Runtime檢查**,實際code太多就不放上來了,我丟到[github](https://github.com/learning-official/JavaLearning/tree/main/Annotation)! ## Day33 #### 學習重點 : Spring Boot - 環境建置 - Maven簡介 ⭐⭐⭐⭐⭐ - **Maven是?** : 它是一種自動化專案管理工具,讓各式各樣的檔案之間能順暢溝通。 - 它靠著 `pom.xml` 這個核心檔案來描述版本、依賴關係、封包路徑等...。 - 指令設置 : 利用maven的特性,用指令來輕鬆建置專案,常見的有 : ```maven= mvn compile mvn -cp 目標資料夾 目標類別檔 ``` - Maven環境設置 ⭐⭐⭐⭐⭐⭐⭐ - `mvn` 指令生效 - 像終端機上的指令 `javac`、`pip`、`git` 一樣,我們要讓系統認得mvn這個指令,就要到電腦的 **編輯系統環境變數** 加入maven的bin檔(指令核心)位置,這樣在terminal輸入mvn時,系統就會 **自動導航** 到指定的位置去執行指令。 - `pom.xml` 核心設置 - 基本上maven就是靠著pom中的設定去自動化建置、生成檔案 ```html= <!-- 首先先宣告這個檔案是用Maven的namespace下去跑的 --> <project xmlns="https://maven.apache.org/POM/4.0.0"> <!-- pom.xml的格式版本 --> <modelVersion>4.0.0</modelVersion> <!-- 封包路徑 --> <groupId>test</groupId> <artifactId>web</artifactId> <!-- 專案版本,自己寫w --> <version>1.0.0</version> <properties> <!-- 編譯時使用的編碼 : 常見UTF-8 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- 編譯時使用的java編譯器版本(javac -version查看) --> <maven.compiler.source>21</maven.compiler.source> <!-- 程式最低可以執行的環境版本(java -version查看) --> <maven.compiler.target>21</maven.compiler.target> </properties> </project> ``` - 專案架構 ⭐⭐⭐⭐⭐⭐⭐⭐ - 引用 [iT邦幫忙 Day20 Maven簡介](https://ithelp.ithome.com.tw/articles/10303335) ``` 專案 : |-- pom.xml (Maven 設定檔) |-- src | |-- main (開發主目錄) | | |-- java (原始碼) | | | -- test (groupId) | | | -- web (artifactId) | | | -- Main.java (程式檔) | | |-- resources (各種靜態資源/設定檔) | |-- test (測試主目錄) | -- java | -- test | -- web | -- MainTest.java |-- target (打包主目錄) | -- classes | -- Main.class ``` - `src / main / java` 基本上是Maven的固定架構,不能隨便更改。 - 程式封包、指令呼叫 ⭐⭐⭐⭐ - 開頭必須給予封包路徑 ```java= package test.web; ``` - 當使用 `mvn compile` 編譯檔案時,程式會被編譯成.class並丟到target的classes資料夾中。 - 當我們要執行class時,需要以 `mvn -cp ./target/classes` 為起點 -> 去找 `test.web.Main`,這樣分開寫的原因是要 **明確表示封包路徑完整呈現出** `test.web` 而不是在設定起點時就放在classes後面。 - 最後指令長這樣 : ```maven= mvn -cp ./target/classes test.web.Main ``` ## Day34 #### 學習重點 : Spring Boot - 基本程式框架 - `pom.xml` 連結Spring Boot ⭐⭐⭐⭐⭐⭐⭐ - 昨天我只有針對pom.xml做基本專案設置,並沒有引入Spring Boot的架構,今天就來完善它吧! - 在 `pom.xml` 中,要加入幾個東西 : `parent`、`dependency`、`build` - `parent` 會取得 Spring Boot 的pom檔,內部包含複雜的依賴管理跟插件配置。 `dependency` 選擇依賴 Spring Boot 的網頁工具。 `build` 就是幫忙打包的部分啦~一樣是使用 Spring Boot 的工具。 ```html= <!-- 繼承Spring Boot的pom檔 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-stater-parent</artifactId> </parent> <!-- 依賴的web套件都在這引入 --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <!-- 以maven打包成Fat jar(整合所有使用到的套件,包含Spring Boot)--> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> ``` - 基本Spring Boot網頁程式框架 ⭐⭐⭐⭐⭐⭐ - 這邊引入了包含Spring的web伺服器、類別讀取、環境設定等 ```java= // 環境設定、類別讀取、依賴性檢查等 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; // 讀取網頁類別並寫入到網頁 import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.GetMapping @SpringBootApplication @RestController public class Main{ public static void main(String[] args){ SpringApplication.run(Main.class, args); } @GetMapping("/") // 寫入根目錄,也就是首頁 public String home_page(){ return "Hello Spring boot!"; } } ``` - 網頁呈現 ⭐⭐⭐ - 首先是利用 `mvn compile` 編譯後,再利用我們在pom寫的dependency (Spring Boot plugin) `mvn spring-boot:run` - 伺服器上線至本機後,使用`localhost:8080` or `127.0.0.1:8080` 找到自己publish上去的網頁。 ## Day35 #### 學習重點 : Spring Boot - 靜態檔處理、路由與路徑 - 靜態檔是甚麼? ⭐⭐⭐⭐⭐ - 當不同人對網頁做出請求時,伺服器所做的動作 **都相同,都會導引至相同檔案**,且這些請求 **不須經過Java運算**,而是靠著當初啟動時所設定的方式就稱為靜態。 - 在網頁設計當中,靜態檔就包含了美術、介面設計、按鈕效果等,因此常見的 **靜態HTML、CSS、JavaScript** 都是靜態檔的一員! - 如何處理以及取得靜態檔? ⭐⭐⭐⭐⭐⭐ - 首先是靜態檔的位置 : 按照我在Day33 ~~從IT邦幫忙偷來w~~ 的結構圖中可以看到靜態檔被放在resources目錄當中,但我們還得新增一個 **static子目錄**,並把檔案塞進static當中,才能夠被讀取到! - 在啟動網頁後,我們需要在 `localhost:8080/` 後面加上靜態檔位置,感謝Spring Boot的幫忙,因此我們 **只需要打上static後的檔案位置** 即可! - 結果圖(~~其實我只是想放小八~~) : ![image](https://hackmd.io/_uploads/B1byc8lwWe.png) - 路由是甚麼? ⭐⭐⭐⭐ - 簡單來說,像是A網址到B網址的決定、跳轉,以及要怎麼回應B網址的內容都是靠路由的運作。 - 在Spring Boot當中,我們可以 **引入路徑參數**,並寫一個 **路由函式** 來 **回應** 該網址的內容。 ```java= import //...省略 // 重點在這 import org.springframework.web.bind.annotation.PathVariable; @RestController @SpringBootApplication public class Main{ public static void main(String[] args){ SpringApplication.run(Main.class, args) } // 填入路徑參數name建立路由函式,並以@PathVariable註解,達成動態 @GetMapping("/test/{name}") public String set(@PathVariable String name){ return "Hello " + name; } } ``` ## Day36 #### 學習重點 : Spring Boot - RequestParam參數查詢、JSON格式 - 網址符號簡介 ⭐⭐⭐⭐⭐⭐ - 這個東西算是回答了我一直以來困惑很久的問題 : 網址到底是怎麼寫的?尤其是一些甚麼 `?`、`=`、`&` 等等的符號,而在今天,我終於看懂了! - 在參數查詢當中,我們會用到 `路徑 ? 參數1=value & 參數2=value`,以 `?` 分隔路徑與參數,使Mapping透過路徑對應到函式後,將參數放進函式中。 ```java= import //...省略 // 引入參數查詢annotation import org.springframework.web.bind.annotation.RequestParam; @SpringBootApplication @RestController public class Main{ public static void main(String[] args){ SpringApplication.run(Main.class, args); } @GetMapping("/echo") public String echo( // 對echo路徑新增兩個參數,n1以及n2 @RequestParam int n1, @RequestParam int n2 ){ return "Sum is " + (n1 + n2); } } ``` - 輸入 `http://localhost:8080/echo?n1=1&n2=2` -> ![image](https://hackmd.io/_uploads/B1vMS5gwZl.png) - 不同格式回傳 ⭐⭐⭐⭐⭐⭐ - Spring Boot可以接受許多形式的回傳型態,如Array、Map、List等,甚至是自己定義的類別(要有getter),它都會以 **Json資料格式** 放至前端,以下是實際操作 : ```java= import //...省略 @RestController @SpringBootApplication public class Main{ public static void main(String[] args){ SpringApplication.run(Main.class, args); } @GetMapping("/test") public String[] nameList(){ String[] nameL = new String[]{"Alice", "Bob", "Carol"}; return nameL; } @GetMapping("/test/map") public Map<String, Integer> map(){ Map<String, Integer> mp = Map.of("x", 1, "y", 4, "z", 3); return mp; } // 自訂義類別(有getter) @GetMapping("/test/point") public Point point(){ return new Point(3,4); } } ``` ```java= // Point類別 package test.web; public class Point { private int x; private int y; public Point(int x, int y){ this.x = x; this.y = y; } public int getX(){ return this.x; } public int getY(){ return this.y; } } ``` ## Day37 #### 學習重點 : Spring Boot - Mapping差異、(IoC、DI、Bean) 三兄弟 - **GetMapping** 以及 **ReqeustMapping** 差異 ⭐⭐⭐⭐ - 在上網查請求網址寫法時,常常會看到Get跟Request的出現,我就很好奇這兩位同學差在哪? - 簡單來說,**Get是Request的簡化版**,它只處理 **method上的請求業務**,而Request可以對Class、method作用,由於Request比較廣泛,因此參數需要放得比較多。 #### RequestMapping用法 - 當作用在Class上時,意即該類別下的method都會從參數網址為起點開始,直接看code比較清楚!  ```java= import //..省略 import org.springframework.web.bind.annotation.RequestMapping; @SpringApplication @RestController // 使Main類別都是從/test出發 @RequestMapping("/test") public class Main{ public static void main(String[] args){ SpringApplication.run(Main.class, args); } // 網址為 localhost:8080/test/ @GetMapping("/") public String home(){ return "Hello test!"; } } ``` - 當作用在method上時,除了要一樣要加入目錄之外,還要選擇該網址的 **動作類型**,像是 `GET`、`POST`、`PUT` 等...。 但如果我寫 `@RequestMapping(value="/yeah", method=RequestMethod.GET)` 那其實就跟GetMapping長一模一樣了。 #### 總結 整體來說,GetMapping要比RequestMapping來的**更精確**,但當我們有許多class要做**網址分類時**,利用RequestMapping來寫會更好,還有當我們需求**不只GET時**,也要用RequestMapping來選擇Method! --- - IoC (Inversion of Control 控制反轉)⭐⭐⭐⭐⭐⭐⭐⭐ - 先下結論,它就是把 **控制權交給中心統籌管理**、**提高維護性、降低依賴性**。 #### 仔細思考 - 假設B、C實作了O介面,且A類別中需要使用O介面的功能,我們會在A類別中建立B物件(假設使用B物件實作的功能),但當我想換成C時,必須砍掉所有A物件,這樣 **不好維護程式**,且 **A對B、C的依賴性or相關性過高**。 - 此時我們會在A類別中單純宣告O介面的變數,**但不指派**,而是將B與C交給中心管理,當我們要使用B實作的功能時,只需要將O變數指向中心的B,而C也是如此。 - DI(Dependency Injection 依賴注入) ⭐⭐⭐⭐⭐⭐⭐⭐ - 在討論IoC時其實就有用到DI的概念,當我們自A類別宣告介面變數,並在建構時,==指派== 一個實作介面的物件,就是 ==注入==,而依賴就是 **A類別依賴O介面的意思**。 - 我覺得這篇文章講的很好,「[淺入淺出 Dependency Injection](https://medium.com/wenchin-rolls-around/%E6%B7%BA%E5%85%A5%E6%B7%BA%E5%87%BA-dependency-injection-ea672ba033ca)」想像插頭是O介面,插座是A類別,可以有很多插頭實作O介面,但可能一個是電腦插頭,另一個是電風扇插頭,功能不同,當建立A物件時,就等於插頭與插座接上了(注入)! - 讓我們用code來實際操作看看 : ```java= //-------------插頭介面檔------------------- public interface ElectricalPlug{ // 插頭介面 void Connect(); } //---------------插座檔-------------------- public class Socket{ //使用插頭的「插座」類別 private ElectricalPlug ep; // 在初始化時,"注入" 實作插頭介面的類別 public Socket(ElectricalPlug ep){ this.ep = ep; } public void SendPower(){ this.ep.Connect(); } } //---------------------------------------- //實作插頭介面 public class AEP implements ElectricalPlug{ @Override void Connect(){ System.out.println("這是A插頭"); } } ``` - Bean (~~豆豆先生~~)⭐⭐⭐⭐⭐⭐⭐⭐ - 其實Bean就是實作O介面的類別**物件**啦~這些Beans都會被存放於Spring中心,透過Spring來管理這些物件的生命週期。 - 我們不需要自行new物件做管理,而是丟給Spring IoC中心管理(做為一個Bean存起來),而在需要用的時候,利用DI概念就可以啦~ ## Day38 #### 學習重點 : Spring Boot - Bean創建與呼叫 - Bean的創建 ⭐⭐⭐⭐ - 在昨天學習中,我們知道,Bean就是是 **實作介面的類別** 存在Spring IoC中。 - 我們在建立Bean時會用 `@Component` 來註解類別,而注入時會利用 `@Autowired` ```java= // A插頭實作類別 @Component // 利用Component註解該類別為Bean,因此會交由Spring管理 public class AEP implements ElectricalPlug{ @Override public void Connect(){ System.out.println("這是A插頭"); } } ``` ```java= // 插座類別 @RestController // 多功能註解(包含component) public class Socket{ // 利用Autowired去Spring中心找有實作ep的類別(A插頭) // 並注入進去 @Autowired private ElectricalPlug ep; @GetMapping("/power") public String sendpower(){ this.ep.Connect(); return "Power on"; } } ``` - 當Main檔案執行時,在 `localhost:8080/power` 就會看到以下畫面 : ![image](https://hackmd.io/_uploads/HJpQTFmvbg.png) #### 使用Autowired要注意的事情 ⭐⭐⭐⭐ - 我們必須確保某類別使用Autowired時,該類別**本身也得是一個Bean**.. 為甚麼呢? - 原因在於 : 當該類別也是Bean時,**Spring才能管理這個類別**,在建構該類別時,才能順便檢查類別有沒有需要做甚麼(像是Autowired等),也就是說,我們必須讓Spring管理插頭以及 **插座**,這樣才能保證這個插座會接哪些插頭。 #### RestController也可以宣告Bean ⭐⭐⭐ - 其實RestController本身是繼承自Controller,而Controller **又繼承自Component**,因此RestController就是一個 **多功能註解**,不只能宣告Bean,還能讓Spring去解讀其他東西,因此在Socket中,就不單純只寫Component,而是寫RestController(因為還有Mapping要被解讀)。 - 多Beans的使用 (**@Qualifier**)⭐⭐⭐⭐⭐ - 如果有多個類別實作了插頭介面,而我們又使用Autowired的話,就會出錯,因為當Spring去找Bean時就會遇到 **要選哪個Bean?** 的難題,這時候就是Qualifier上場的時候啦~ - 假設我有三個實作 `AEP`、`BEP`、`Celectrical` ```java= @RestController public class Socket{ @Autowired @Qualifier("BEP") private ElectricalPlug ep; // ...省略 } ``` - 透過Qualifier就可以選擇要取得的實作啦~ #### Bean的名稱🚨 - 這邊要注意的是,當Bean的名稱是像BEP這種 **前兩個字母是大寫**,那麼在Qualifier中寫BEP沒問題,但如果像Celectrical的話,在Qualifier就要寫 `celectrical`,第一個大寫要轉成小寫。 ##### 為甚麼這麼麻煩? - 其實這跟Java的傳統有關,一般來說類別首字母都是大寫,在建構物件變數時,首字母反而會寫成小寫。 - 由於Bean本身是物件,所以就理所當然地把Qualifier首字母限制成小寫~(如果類別前兩個字母是大寫,那Spring就會認為它是縮寫詞,而不過度干預!) ## Day39 #### 學習重點 : Spring Boot - PostConstruct 與 Value取得設定檔 - Bean的初始化 ⭐⭐⭐⭐⭐⭐ - 若類別在實作介面時,本身也有成員變數,我們就需要利用初始化來賦值,由於 `@Component` 的關係,管理權在Spring身上,因此我們要利用 `@PostConstruct` 來告訴Spring要初始化。 - **初始化的重要性🚨** : 如果是一般的初始化,其實可以直接在宣告時賦值,但若涉及到複雜的初始化,像是 **檢查是否有注入成功**,這些不能賦值但又很重要,因此需要靠初始化來完成。 ```java= @Component public class AEP implements ElectricalPlug{ private int durability; @PostConstuct public void init(){ durability = 100; } @Override public void Connect(){ if (this.durability>0){ this.durability--; System.out.println( "已連接A插頭,目前耐久度 : " + this.durability ); }else{ System.out.println("A插頭已損壞"); } } } ``` - 這邊給予插頭一個耐久度,**使用100次就會壞掉**,那麼就需要對耐久度做初始化(雖然可以直接賦值啦ww 但想要展示一下PostConstruct怎麼用) #### PostConstruct的一些須知🚨🚨 - 首先就是一定是標註在 `public void` 的函式上。 - 標註的函式**不能**有參數。 - 盡量一個類別只放一個PostConstruct即可。 #### 初始化檢查是否有注入 - (PostConstruct真正派上用場之地) ```java= @RestController public class Socket{ @Autowired @Qualifier("AEP") private ElectricalPlug ep; @PostConstruct public void init(){ if (ep == null){ System.out.println("沒有插頭耶~"); }else{ System.out.println("好耶,有插頭!"); } } } ``` #### Spring存Bean的順序(超重要!!) - 1️⃣ 執行建構子 - 2️⃣ 注入變數 - 3️⃣ 執行PostConstruct - 這也難怪可以在PostConstruct做一些檢查(因為都建構完了嘛~) - Value 與 Spring Boot設定檔 ⭐⭐⭐⭐⭐⭐ - 我們可以利用 `@Value` 來讀取 Spring Boot 的設定檔中設定的值,這應該也算一種初始化? #### application.properties設定檔 - [Day33](##Day33) 有放結構,忘了再去看w,反正這個設定檔會是放在resources中,這個檔案都是**拿來存值**的,也難怪它被放在靜態資料夾。 - 它的語法是Spring自己的格式,使用 `Key=Value` 來作為宣告(注意不能有空白like key = value),且不需要型別,以下是範例 : ```python= durability=100 # 註解跟python一樣,使用「#」 my.name=小八 # 可以用「.」當作「的」,將name作為my的子屬性 ``` - 當然Spring除了可以讀properties檔,也可以讀常見的yml檔,我相信你很熟悉它,就不講了~ #### Value取值 - 在Bean中就可以取出設定檔中設定的值並👉注入👈在變數上,注意型別要對的上,而使用的方法如下 : ```java= @Component public class AEP implements ElectricalPlug{ @Value("${durability}") // 名稱不一定要跟變數一樣 private int durability; // ...省略 } ``` - 除了在Bean中使用,也可以在有 `@Configuration` 的類別中使用,不過目前還不知道它是甚麼東東,先記在心裡就好w - 而Value也可以設置預設值,像這樣 `"${durability:50}"`,用**冒號**代表賦值,當Sprint在設定檔中 **找不到durability**,就會使用預設值。 ## Day40 #### 學習重點 : Spring Boot - AOP(Aspect-Oriented-Programming)  - AOP - 切面導向程式設計? ⭐⭐⭐⭐⭐⭐⭐⭐ - 從字面上看根本看不懂🫠,但我找到了一張圖不錯。 取自 [[AOP系列] 簡單介紹AOP的概念](https://ithelp.ithome.com.tw/articles/10229664) ![image](https://hackmd.io/_uploads/HyY7jMDv-l.png) ----> **(圖一)** ![image](https://hackmd.io/_uploads/H1DEozPwWe.png) ----> **(圖二)** - 從圖一可以知道,若n個方法除了本身的功能,還要做同樣的邏輯(權限check、資料check、錯誤log),等於要寫n次邏輯。 - 但當我們將方法共同邏輯轉90度,與方法本身的功能垂直後,變成像是 **切開** 來一樣,**這時就是AOP的用處了** -> 輕鬆管理相同邏輯、減少運算時間。 - 引入Spring AOP ⭐⭐ - 在 `pom.xml` 中加入aop的dependency : ```html= <depenency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-stater-aop</artifactId> </depenency> ``` - Spring AOP怎麼運用? ⭐⭐⭐⭐⭐⭐ - 在Spring中,`@Aspect` 是宣告類別為切面的註解,而切面本身也必須是Bean,**若**其不在Spring IoC中,內部的檢查機制都不會被讀取,也自然不能達到管理方法的功能了。 - 由於Aspect本身是Bean,因此其管理的方法所在的類別也必須是Bean。 - 切面類別寫法 : ```java= import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect // 創建切面 @Component // 使切面為Bean public class AspectTime{ // 在切入點方法之前執行 @Before("execution(* test.web.ElectricalPlug.Connect(..))") public void beforeConnect() { System.out.println("準備連接插頭..."); } } ``` - Pointcut(切入點)📌 - 在Before參數中,我們要設置 **切入點**,讓Spring知道用作於哪個方法之前。 - 而參數有幾個要素 `execution(修飾詞 回傳型態 封包名.類別名.方法名(參數))`。 - 修飾詞**可省略**,`*` 代表**隨便**,`隨便` 可以作用在路徑中以及回傳型態,而方法參數用(..)來代表。 - 若不想這麼麻煩,也可以自定義annotation,並以「帶有該註解的方法」來作為切入點。 - 其餘用法還有 `@After` : 作用於方法之後,跟Before一樣作法。`@Around` : 作用於方法前後,稍複雜,就不多細探討了~ ## Day41 #### 學習重點 : Spring Boot - Spring MVC(Model-View-Controller) - 甚麼是Spring MVC? ⭐⭐⭐⭐⭐⭐⭐⭐ - 簡單來說,它就是連結前端(View)與後端(Model、Controller)的橋梁,其實在前幾天我就做過MVC了。 - 當我們在網址欄(**前端**)輸入 `http://localhost:8080/power` 這樣的動作時,實際上就是透過MVC告訴插頭要接電(**後端**),由此可知,MVC就是 `Mapping` 、`Controller` 等來實踐的! - Http架構 ⭐⭐⭐⭐ - Http協議是來規範前後端溝通的格式,其必包含`Request`、`Response`,這樣才能成為**有效的溝通** #### Request內容 - **Http method** : 包含GET、POST、PUT等,在 [Mapping比較](##Day37) 中我有寫過! - Url : 就是那串 `http://localhost:8080/power`。 - Header : 基本上就是一些通用資訊(使用者瀏覽器資訊等),可以利用 `@RequestHeader` 來取得資訊。 - Body : 用在POST、PUT等身上,以參數請求回傳給後端,通常會是一些隱私的資訊。 Header跟Body可以比喻成包裹,**寄件人、收件人、地址**都是Header,**內容物**是Body。 #### Response內容 - Http status code : 這個很常見,像是404Error一樣,前面的數字就是狀態碼 : ![image](https://hackmd.io/_uploads/SJ8wKDOPZx.png) - Header : 與Request header一樣都是放通用資訊。 - **Body** : 後端要傳給前端的數據會放在這,當請求通過時,數據會整合前端顯示給使用者。 ## Day42 #### 學習重點 : Spring Boot - Url格式、POST - Url的結構 ⭐⭐⭐⭐⭐⭐ - 在Url中,分為幾個部分 -> `協議://域名:埠號/路徑?查詢#片段ID`。 - **協議** : 一般常用的就是http協議 - **域名** : 就是主機的IP位置(一般來說不會直接寫IP啦w) - **埠號** : 選填,它作為主機的起點與終點,可以分流不同資訊,不同協定會指定不同連接埠,這樣當電腦遇到埠號時,就知道要以什麼樣的方式處理它。 想像**域名是一家公司**,當送貨員要送貨時,根據貨上的樓層(**埠號**)搭到該樓交給人員(**使用者**)。 - --- - **路徑** : 這裡就是Spring的天下啦~ 根據使用者輸入的路徑,Spring就會去找相對應的Mapping,像是前幾天寫過的RequestMapping以及GetMapping! - **查詢** : 像是RequestParam這樣的查詢參數,用於動態導向。 - **片段** : 這邊只專屬於前端,因此並不會與後段有聯繫,像是點擊我的筆記Day1的話,網址就是`https://hackmd.io/@learning-official/Java_learning#Day1`,後面的Day1**不會傳到後端**,而是單純讓使用者電腦**自動滑到Day1的頁面**而已。 - POST概念 ⭐⭐⭐⭐ - 昨天統整的Http架構中,Body就是給POST包裝後傳遞到後端的東西,由於它是隱蔽的,不能放在Header由GET傳遞,因此POST在MVC當中是很重要的東西,一些私人的參數都要靠它來傳遞。 - **@RequestBody**、網站測試 ⭐⭐⭐⭐⭐⭐⭐ - 透過前幾天寫的[Point類別](##Day36),我們可以以X、Y值為隱密資訊透過POST方法傳遞,其中需要用到 `RequestBody` 來標註Point物件 : ```java= // ...省略 import org.springframework.web.bind.annotation.RequestHeader; @SpringBootApplication @RestController @RequestMapping("/test") public class Main{ // ...省略 @RequestMapping("/post") public String post(@RequestBody Point point){ System.out.println(point.getX()); System.out.println(point.getY()); return "請求成功"; } } ``` ![image](https://hackmd.io/_uploads/BkbvBCFvZe.png) ![image](https://hackmd.io/_uploads/H1NuHAKv-x.png) ## Day43 #### 學習重點 : Spring Boot - RequestHeader、API概念 - RequestHeader用法 ⭐⭐⭐⭐ - 昨天認識了RequestBody的用法,那麼今天來學Header吧!儘管Header似乎不會拿來傳重要參數,但也可以模擬一下,學習如何使用RequestHeader註解! - Header本身可以用在 **各種Http method**,不像Body只能用在POST & PUT。 ```java= import org.springframework.web.bind.annotation.RequestHeader; @SpringBootApplication @RestController public class Main{ public static void main(String[] args){ SpringApplication.run(Main.class, args); } @RequestMapping("/header") public String header_test(@RequestHeader String info){ System.out.println(info); return "請求成功"; } } ``` - API概念 ⭐⭐⭐⭐⭐⭐ - 老實說,這個東西從我以前開始就是一個似懂非懂的東西,想說藉著Java學習,來了解API真正的意義! #### API (Application Programming Interface) - 字面上來看,叫做**應用程式介面**,直白的講,就是應用程式之間的溝通! - 想像API是服務生,而我們看著menu(應用程式)勾選完料理後,由服務生(API)送到廚房(應用程式)製作美食!因此可以說API是串接使用者與後台數據處理的重要成員! #### API實際應用 - 當要比較飯店價格時,就會找~~trivago~~,它其實就是把各大飯店的API接到自己的網站上,當我們查詢空房、價位時(**Request**),它就會將我們的要求利用飯店API取得資訊(**Response**)。 #### 總結 - API就是一個**窗口**,連接了使用者與伺服端,使用者**不須**知道伺服器端怎麼處理的,只需要得到一個結果即可,很大程度的減少使用者的困惑程度ww ## Day44 #### 學習重點 : Spring Boot - RESTful API - Representational State Transfer (具象狀態傳輸)⭐⭐⭐⭐ - REST本身是一種架構,使傳輸過程更有效率,它規定了資料應該以什麼樣的格式傳送、儲存...。 - 因此當我們要寫一個REST協定的傳輸、儲存功能時,就會說RESTful API(充滿REST風格的服務員?) - 而REST的細節可以利用**CRUD**(增查改刪)來表示,也就是**Create、Read、Update、Delete**。 - RESTful API實際作法 ⭐⭐⭐⭐⭐⭐ - RESTful API會以各種Http method表示要對資料庫做甚麼事,而所謂的CRUD就會對應到**POST、GET、PUT、DELETE**。 #### Url的路徑階層表示 - 當我們選用Http method時,就代表要對資料庫做某某事,而路徑已`/`作為分隔,就可以讓後端知道前端要針對`哪個資料中 的 哪個資料中的...`,其中 `的` 就代表 `/`,底下有個明確的圖來表示[(感謝古古的筆記圖w)](https://kucw.io/blog/springboot/21/)。 ![image](https://hackmd.io/_uploads/HyHpKEsvWx.png) #### Response Body用JSON格式 - 在RESTful API的製作中,會以JSON作為主要格式,因此在RESTful API中會使用 `@RestController`。 #### RESTful API選用時機 - 若是**單純的操作資料庫**、**跨平台服務時**,以RESTful API絕對是很好的選擇。 - 尤其在跨平台時,若為了Response,要分別寫網頁版、手機版的格式,但以RESTful API回傳都是JSON格式的情況下,就可以避免這種麻煩事! - 若涉及到**複雜的運算** 或是 **隱私資料**,以RESTful API這種顯示在網址上的作法似乎就不是很好。 ## Day45 #### 學習重點 : Spring Boot - Mapping的種類、實作RESTful API - Mapping種類 ⭐⭐⭐⭐ - 除了 `@GetMapping`,還有 `@PostMapping`、`@PutMapping`、`@DeleteMapping`,這些讓我們可以對同一資源,同一網址,做不同的操作! - 以往都是利用 `RequestMapping("路徑", method = RequestMethod.POST)` 醬子去選擇,十分麻煩,因此後來多了四個直接寫XXXMapping的方法,很省事! - RESTful API實作 ⭐⭐⭐⭐⭐⭐ - 其實就是**把CRUD功能做出來**!因此會用到**四大Http method**,但因為我還沒學資料庫所以單純把之前學的annotation放上來而已,至於真的要更新到資料庫中...還要一段時間w - 這邊用我之前寫的Point類別當作資料操作。 ```java= // ...省略 public class PointTest{ // ...省略 @PostMapping("/points") public String create(@RequestBody Point point){ this.point = point; return "點創建成功"; } @GetMapping("/points/{point}") public String get(@PathVariable String point){ if (this.point == null){ return "點不存在"; }else if (this.point.getName().equals(point)){ return "點查詢成功 " + this.point.getX() + "," + this.point.getY(); }else{ return "點查詢失敗"; } } @PutMapping("/points/{point}") public String put(@PathVariable String point, @RequestBody Point p){ if (this.point == null){ return "點不存在"; }else if (!this.point.getName().equals(point)){ return "點更新失敗"; }else{ this.point = p; return "點更新成功"; } } @DeleteMapping("/points/{point}") public String delete(@PathVariable String point){ if (this.point == null){ return "點不存在"; }else if (this.point.getName().equals(point)){ this.point = null; return "點刪除成功"; }else{ return "點刪除失敗"; } } } ``` #### REST細節注意🚨🚨 - 這邊要注意,雖然網址路徑相同,但「**動作**」不同,這是REST中很厲害的地方 -> 當我們操作Http method時,可以避免網址**濫用** -> 利用**單一網址**對上不同資源的操作。我覺得可以把這個主題放到明天來研究,今天先這樣! ## Day46 #### 學習重點 : Spring Boot - RESTful API 深入研究 - 名詞解釋 - 由於很多名詞一直出現,但又搞不清楚,所以我稍微統整了一下! --- #### Resources(資源)⭐⭐⭐⭐ - 它就是被Http method操作的東西,可以是物件、狀態、紀錄等,只要跟前後端操作有關的,都可以說它是一種資源,其只會有一個**唯一Url指向**,操作可以不同(這就剛好符合REST風格的寫法)。 #### Endpoint ⭐⭐⭐⭐⭐⭐ - 在API當中,是一種**特定**的URL,其中可能有特定的參數、動作等...,它是處理前端與後端的接頭。 - 它與一般URL不一樣的點在於 -> 一般URL可能長這樣 `/api/points`,但enpoint是 `[GET] /api/points`、`[POST] /api/points`,**這些都是不同的endpoint,但卻是同一個URL**。 #### Richardson Maturity Model ⭐⭐⭐⭐⭐⭐⭐⭐ - 該模型用來評估一個API是否真的具有REST風格,共分為四級。 這邊引用 [Shiny的軟件開發的瑣碎閒聊(8)](https://www.threads.com/@shiny2024hk/post/DBJcneBzDHt?xmt=AQF0iJ7Ox1Pt_h19Kb1YUQe4HTeUjCyxjeivOSmaxgy1UQ) 所展示的四個REST風格等級。 🟥等級0:單一 Endpoint 配以參數來識別操作。 🟨等級1:將單一 Endpoint 拆分成多個資源導向的 Endpoint。 🟦等級2:引入標準的 HTTP 動詞(GET、POST、PUT、DELETE)。 🟩等級3:支持 HATEOAS,提供清晰的資源導航。 - 至於甚麼是HATEOAS?其實就是提供資源操作之外,還會提供**多餘**的連結,讓使用者可以導向其他頁面!(至於內部細節,可以安排到後面來寫寫看!) ## Day47 #### 學習重點 : Spring Boot - Http Status Code - 狀態碼介紹 ⭐ - 甚麼是狀態碼呢?在前幾天有介紹狀態碼是**Response**的其中一個部分,也代表這次**請求的狀態**,這邊就來仔細看看每個狀態碼的涵義! - 常見狀態碼 #### 1XX : 資訊回應 ⭐⭐ - 1開頭不常看到,就不寫太多了。 - **100** : `Continue`,讓使用者繼續請求。 #### 2XX : 成功回應 ⭐⭐⭐⭐⭐⭐ - 一般來說,2開頭的都是跟Pass✅有關,看到這個要開心啊w - **200** : `OK`,成功的概念。 - **201** : `Created`,通常用於POST請求 - **202** : `Accepted`,表示該請求已經通過,**但** 後續處理尚未完成,有可能是導向其他伺服器,或者本身伺服器有其他工作要處理。 #### 3XX : 重新導向訊息 ⭐⭐⭐⭐⭐⭐ - **300** : `Multiple Choices`,重新導向後,可能會有多個URL供使用者選擇,像是選擇語言之類的~ - **301** : `Moved Permanently`,基本上就是URL**永久搬家**了!舊網址收到301,就會**重新導向**至Response header中Location的新URL。 - **302** : `Found`,他是**臨時搬家**,與301一樣都會導向新網址! 對於301、302除了搬家模式不同,流量也會不同,301因為是永久搬家,因此新網址會**繼承舊網址的SEO排名**,但302因為是暫時搬家,因此不會繼承。 #### 4XX : 前端請求錯誤 ⭐⭐⭐⭐⭐⭐⭐⭐ - **400** : `Bad Request`,前端的參數請求錯誤,像是下面的範例,若請求參數中填入**字串**,就會回傳400 ```java= @GetMapping("/echo") public String ec(@RequestParam int n1, @RequestParam int n2){ return "Sum is " + (n1 + n2); } ``` ![image](https://hackmd.io/_uploads/Byo5vmJdbx.png) - **401** : `Unauthorized`,跟字面上的意思差不多,就是**未通過驗證**,像是帳號密碼打錯啦,token錯誤之類的~ - **403** : `Forbidden`,權限不足的概念!像是我不是Youtube Premium會員就不能背景播放(嗚嗚嗚嗚嗚嗚嗚嗚),通常會把401跟403綁在一起看,因為兩個很像! - **404** : `Not Found`,這個應該算是最為人知的一個狀態碼了w,基本上就是前端網址輸入錯誤啦~ #### 5XX : 後端處理問題 ⭐⭐⭐⭐⭐⭐⭐⭐ - **500** : `Internal Server Error`,基本上只要後端程式錯誤有Bug,都會回傳500! - **503** : `Service Unavailable`,在伺服器維護期間去call API的話,會回傳503,表示現在伺服器無法處理請求,也有可能是因為流量過大,太過忙碌導致。 - **504** : `Gateway Timeout`,如果後端處理該請求過久,就會被認定超時,有可能程式不小心進到無限Loop了w。 ## Day48 #### 學習重點 : Spring Boot - MVC與API的關係釐清、Spring JDBC介紹 - MVC與API ⭐⭐⭐⭐⭐⭐⭐ - 其實這兩個名詞一直在我腦海中不斷交錯在一起,因為我常常會搞混他們(看來是學不精的關係🤦‍♂️🤦‍♀️) - 我稍微統整了一下這兩個東西的差別 : #### 模式不同 - MVC主要是以拆分前後端為架構,將重點交由不同部門處理(關注點分離),且Model與資料庫溝通,View負責處理前端畫面並呈現數據,Controller則**充當Model、View的橋樑**,處理使用者與View的互動。 - 而API本身是**一種介面協議**,而Controller只是一種實作API的方法,API不與View結合,只回傳純數據結構,如JSON、XML。 - 資料庫操作 ⭐⭐⭐⭐⭐⭐⭐ - 在Spring Boot當中,後端與資料庫的互動需要藉由一些工具來操作,常見的有 `Spring JDBC、Hibernate、Spring Data JPA` 等等,而其中可分為 : **SQL操作DB**、**ORM概念操作DB**,JDBC屬於前者,後面兩個屬於後者。 - ORM概念不會直接使用SQL語法,而是利用Java語法操作。 - 現在先來看JDBC是甚麼,ORM之類再來研究! #### Spring JDBC介紹 - JDBC(Java Database Connectivity),就是一種利用Java與Database連結的技術,由於單純用Java操作資料庫會很麻煩,因此Spring JDBC簡化了中間步驟,只需要專心寫SQL語法即可。 ## Day49 #### 學習重點 : Spring Boot - Spring JDBC建置 - `pom.xml` ⭐⭐ - 建置Spring JDBC要先引入Spring打包好的JDBC啦~ 另外還要加入SQL資料庫! ```html= <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency> ``` - `application.properties` ⭐⭐⭐⭐ - 接下來要與SQL資料庫做連線! ```= // 使用MySQL驅動程式 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver // 連接到自己的電腦SQL資料庫,並指定資料庫的位置myjdbc,並把編碼與時區都調整為常用 spring.datasource.url=jdbc:mysql://localhost:3306/myjdbc?serverTimezone=Asia/Taipei&characterEncoding=utf-8 // 放入SQL的帳密 spring.datasource.username=root spring.datasource.password=springboot ``` - 基本上在application與pom中設定好,就可以開始操作SQL語法啦~不過因為我之前有學過一些些的SQL所以還蠻好理解的w ## Day50 #### 學習重點 : Spring Boot - SQL資料庫建置、實際程式操作 - SQL資料庫建置 ⭐⭐⭐⭐ - 由於我幾年前有稍微碰過SQL!所以還算熟悉SQL語法操作,這邊就先利用Intellij的內建功能來連結SQL吧~ - 由於這幾天都很忙,我先以教學的範例做為參考,做一個student的table出來 -> ```SQL= CREATE TABLE student ( id INT PRIMARY KEY , name VARCHAR(30) ) ``` - 基本上就是建置出一個ID與Name的pair,ID具**唯一性**(PRIMARY key),Name每個字元數不超過30。 - 建置完的Table如下 ![image](https://hackmd.io/_uploads/rk2B1_7uZx.png) - 實際程式操作(**C**reate)⭐⭐⭐⭐⭐⭐ - 今天先單純利用Create(POST)方法來嘗試建立資料(Resources)。 #### NamedParameterJdbcTemplate - 他是一種模板,用於與SQL資料庫溝通,而我們會在程式中**注入**它,當成Bean來使用。 ```java= @Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate; @PostMapping("/jdbc") public String insert(){ String sql = "INSERT INTO student(id, name) VALUES (3, 'John')"; Map<String, Object> map = new HashMap<>(); namedParameterJdbcTemplate.update(sql, map); return "INSERT passed"; } ``` - 老實說,它其實就是把SQL語法塞進字串ww。 #### map問題 - 要注意!map的出現是因為後續有動態寫法,可以在SQL語法中寫入動態參數,並以map的key-value pair對應到 id-name的pair,但目前先以固定寫法,因此map是空的,只是裝飾品w。 #### 今日心得 - 今天一直在重複POST error羅生門,後面先暫時把程式移到Main區執行,這個問題之後得好好來解決一下QAQ。 ## Day51 #### 學習重點 : Spring Boot - 資料庫動態參數使用 - SQL動態參數 ⭐⭐⭐⭐⭐⭐ - 在SQL當中,使用 `:` 來宣告變數,這樣就可以跟昨天的Map搭配啦! ```java= String sql = "INSERT INTO student(id, name) VALUES (:studentId, :studentName)"; ``` - 表示studentId存入student table中的id鍵,以此類推。 - Map搭配 ⭐⭐⭐⭐⭐⭐ - 而Map本身就是存取key-value pair的最佳工具!我們利用HashMap建構後,將參數輸入的id & name以及getter放入key-value pair,實作如下 : ```java= @PostMapping("/jdbc") public String insert(@RequestBody Student student){ String sql = "INSERT INTO student(id, name) VALUES (:studentId, :studentName)"; Map<String, Object> map = new HashMap<>(); // 這邊放入的key會根據其指向的值,對上SQL的變數值,並放入table map.put("studentId", student.getId()); map.put("studentName", student.getName()); namedParameterJdbcTemplate.update(sql, map); return "INSERT passed"; } ``` - 這邊就利用前幾天學的POST(Create)的**RequestBody來放入參數**,而這邊利用Student類別的範例來做參考。 ```java= public class Student { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` #### 疑問 - 這兩天都在處理Update的東西,我發現Map本身存的就是SQL中的參數映射值,因此一次的Create只會有一組數據做上傳,但若是SQL語法有多組,是不是可以在Map中放入多組SQL參數映射呢?還是這樣就會放在不同的路徑當中? ## Day52 #### 學習重點 : Spring Boot - UPDATE、DELETE實作 - 實作 ⭐⭐⭐⭐ - 其實Update與Delete的概念與前幾天的CRUD實作相同,只是SQL語法會稍做更動。 - 這邊SQL語法的重點在於 `WHERE` 的條件限制,不然很容易就毀了整個Table。 - 這邊利用了 `namedParameterJdbcTemplate.update()` 回傳 **更動資料筆數** 來判斷操作是否執行成功。 #### UPDATE - 語法 : `UPDATE {TABLE} SET 欄位 = 值 WHERE 條件` ```java= @PutMapping("/jdbc") public String update(@RequestBody Student student){ String sql = "UPDATE student SET name = :name WHERE id = :id"; Map<String, Object> map = new HashMap<>(); map.put("id", student.getId()); map.put("name", student.getName()); int count = namedParameterJdbcTemplate.update(sql, map); if (count > 0) { return "已更新 " + count + " 筆資料"; } else { return "找不到 ID 為 " + student.getId() + " 的學生"; } } ``` - 這邊把SQL動態參數、map的key變數名稱 **統一** 跟student table的 **欄位名稱相同**!這樣日後在debug可以更加迅速! #### DELETE - 語法 : `DELETE FROM {TABLE} WHERE 條件` ```java= @DeleteMapping("/jdbc/{id}") public String delete(@PathVariable Integer id){ String sql = "DELETE FROM student WHERE id = :id"; Map<String, Object> map = new HashMap<>(); map.put("id", id); int count = namedParameterJdbcTemplate.update(sql, map); if (count > 0){ return "DELETE Successful"; }else{ return "DELETE Failed"; } } ``` ## Day53 #### 學習重點 : Spring Boot - READ(GET)實作 - query ⭐⭐⭐ - 在CRUD中,READ不屬於Update的範圍,因為它並沒有實際更動到資料,因此會使用**query來做查詢操作**,而其回傳的是 `List<T>` 型態,這邊的T自然宣告為Student型態! - RowMapper ⭐⭐⭐⭐⭐ - 我們知道 `String sql = "SELECT ..."` 是針對TABLE做查詢,而後續則會回傳**ResultSet這個原始數據**,此時靠著RowMapper即可將ResultSet數據處理成Student的型態! - 但由於RowMapper只是一種概念,我們要將其實作,底下是實作 : ```java= public class StudentRowMapper implements RowMapper<Student>{ @Override public Student mapRow(ResultSet rs, int rowNum) throws SQLException{ Student student = new Student(); student.setId(rs.getInt("id")); student.setName(rs.getString("name")); return student; } } ``` - READ實作 - 實作完Mapper的部分就可以來看主程式了! ```java= @GetMapping("/jdbc") public List<Student> query(){ String sql = "SELECT id, name FROM student"; Map<String, Object> map = new HashMap<>(); StudentRowMapper rowMapper = new StudentRowMapper(); List<Student> list = namedParameterJdbcTemplate.query(sql, map, rowMapper); return list; } ``` #### 流程 - 1️⃣ SQL語法取得ResultSet數據。 - 2️⃣ Mapper將Row的key-value pair轉成Student物件並回傳。 - 3️⃣ 利用 `namedParameterJdbcTemplate.query()` 回傳List型態。 - 總結CRUD實作 ⭐⭐ - 透過這幾天的歷程,我能夠感受到前後端的分工了,但是對於程式分區以及除錯得需要再來研究研究! ## Day54 #### 學習重點 : Spring Booot - Controller-Service-Dao - MVC 與 Controller-Service-Dao ⭐⭐⭐⭐⭐⭐ - MVC本身就是一種架構概念而已,並不會影響程式本身,只是將程式分類,以便日後較好維護。 - 而在Spring Boot中,會利用Controller-Service-Dao來實踐MVC的概念。 - 擷取自 [古古的後端筆記-Day28](https://kucw.io/blog/springboot/28/),我覺得這張圖寫得很清楚! ![image](https://hackmd.io/_uploads/rybdNiOdbl.png) #### MVC 與 Controller-Service-Dao 的實際對應 - View在Spring Boot實踐中並不需要處理,因為它是屬於**前端框架**的部分。 - Controller 就是對應到 Controller層,像是RequetMapping、RequestParam、PathVariable等與前端溝通的都屬於Controller層。 - 而Model所對應的就是**Service與Dao層**,但這兩層的分別十分重要。 - 三層架構實際實作 ⭐⭐⭐⭐⭐ #### Controller層 ```java= @SpringBootApplication @RestController public class StudentController{ public static void main(String[] args){ SpringApplication.run(StudentController.class, args); } @Autowired private StudentService studentService; // GetMapping屬前端溝通 @GetMapping("/jdbc") public Student query(@PathVariable Integer id){ return studentService.getById(id); } } ``` #### Service層 ```java= @Component public class StudentService { @Autowired private StudentDao studentDao; public Student getById(Integer id){ List<Student> list = studentDao.getById(id); if (!list.isEmpty()){ return list.getFirst(); }else{ return null; } } } ``` #### Dao層 ```java= @Component public class StudentDao{ @Autowired private NamedParameterJdbcTemplate namedParameterJdbcTemplate; public List<Student> getById(Integer id){ // SQL語法 String sql = "SELECT id, name FROM student WHERE id = :id"; Map<String, Object> map = new HashMap<>(); map.put("id", id); return namedParameterJdbcTemplate.query( sql, map, new StudentRowMapper() ); } } ``` - 可以注意到三層都必須是Bean,才可連貫注入。 - Dao層專注在**SQL語法與資料庫溝通**、Service層專注在**商業邏輯處理**、Contorller專注在與前端溝通(**資料呈現、選擇View**)。 ## Day55 #### 學習重點 : Spring Boot - ResponseEntity - ResponseEntity介紹 ⭐⭐⭐⭐ - 在Controller決定對前端顯示何訊息時,其中同時包含 : `狀態碼`、`Response Header`、`Response Body`,之前在寫的時候,我都只有 `return String`,因此狀態碼除非網址輸錯,不然都是顯示 ==200ok==。 - 而ResponseEntity就可以解決這個問題,他的工作就是負責設定上述的三個東西。 - ResponseEntity實際作用 ⭐⭐⭐⭐⭐⭐ - 我稍微整理了一些我遇到的錯誤狀態碼 : - 404 : 輸入id後 **查詢不到** 資料 | 放入student資料update,但 **找不到** id更新。 - 409 : student id **重複** 更新、創建。 - 500 : SQL語法錯誤 | DB連線中斷 | nullPointerException。 - 我將昨天簡易的分層架構變成 : - 💡**Dao**層 : 回傳更動資料筆數。 - 💡**Service**層 : "暫時" 單純return Dao層回傳的筆數。 - 💡**Controller**層 : 判斷筆數並使用ResponseEntity做更精確的前端呈現。 - 一般來說,500會被放在try-catch中的Exception,因此架構如下 : ```java= @GetMapping("/jdbc/{id}") public ResponseEntity<?> query(@PathVariable Integer id){ try{ List<Student> student = studentService.getById(id); if (!student.isEmpty()){ return ResponseEntity.status(200). body(student.getFirst()); }else{ return ResponseEntity.status(404). body("Student ID : " + id + " Not Found"); } }catch (Exception e){ return ResponseEntity.status(500). body("Internal Service Error"); } } ``` - 其餘丟到github比較省空間 - [Student Controller-Service-Dao實作](https://github.com/learning-official/JavaLearning/tree/main/SpringBoot/src/main/java/test/web/student) ## Day56 #### 學習重點 : Spring Boot - Controller-Service-Dao整體實作 - 1 - 圖書管理系統分析 ⭐⭐⭐ - 按照 [古古的後端筆記 Spring Boot Day29](https://kucw.io/blog/springboot/29/),裡面有詳細的程式碼,我也跟著實作了一下,其實跟Student的實作並沒有太大差異,但某些部份可以拿出來研究。 - SQL : Auto increment ⭐⭐⭐ - 若在創建TABLE時,給予像id這種INT形態一個 `AUTO INCREMENT` 的key word。當我們新增數據時,則 **不需提供id**,數據庫會自動新增,並 **依序遞增**。 #### Java中取得遞增id方法 : KeyHolder ⭐⭐⭐⭐⭐⭐ - 我們會利用KeyHolder來取得自動遞增的Key ```java= KeyHolder keyHolder = new GeneratedKeyHolder(); namedParameterJdbcTemplate.update( sql, new MapSqlParameterSource(map), keyHolder ); int book_id = keyHolder.getKey().intValue(); ``` #### ❓疑問區 : 為何是MapSqlParameterSource(map) - 仔細看update方法內部的流程 : ![image](https://hackmd.io/_uploads/rysyWZnuZl.png) - 🔑前提 : - 從圖中會發現共有四種update method(**method overloading**) - 且當我們傳map進去後,其實也會被Spring放進MapSqlParameterSource中,可以說它是做為跟 **SQL參數對應的工具包**。 - 當今天搭配KeyHolder時,Spring就沒有幫忙做放入工具包的動作了,因此要自行打包 `new ...Source(map)`。 - 若有多個 `auto increment`,則可以在參數放入 `String[] id = {"book_id"}` 去指定要抓取的欄位。 ## Day57 #### 學習重點 : Spring Boot - Controller-Service-Dao整體實作 - 2 - MapSqlParameterSource省略map ⭐⭐⭐⭐ - 該類別提供一個方法 `addValue` 可以直接加入key-value pair,省去**創建**HashMap的動作。 ```java= namedParameterJdbcTemplate.update( sql, new MapSqlParameterSource() .addValue("title", book.getTitle()) .addValue("author", book.getAuthor()) .addValue("image_url", book.getImage_url()) .addValue("price", book.getPrice()) .addValue("published_date", book.getPublished_date()) .addValue("created_date", now) .addValue("last_modified_date", now), keyHolder ); ``` - Dao介面、實作(解耦)⭐⭐⭐⭐⭐⭐ - 如 [Day38](##Day38) 所介紹,Bean是由Spring IoC管理的物件,可**降低依賴、提高維護性**。 - 其中的精隨就在於我們注入的是 **介面變數**,使用Qualifier選擇實作的類別,而 **不是直接自行建立** 實作類別的物件變數。 ```java= public interface BookDao { public Integer insert(Book book); public Book query(Integer book_id); public void update(Integer book_id, Book book); public void delete(Integer book_id); } ``` - 這樣Service在呼叫Dao層時,**不需知道內部邏輯**,只知道有insert、query、update、delete四種方法,若日後有多個實作Dao介面時,只需要利用Qualifier選擇即可。 - 而Service層以及Controller層也可使用同種方式進行! ## Day58 #### 學習重點 : Spring Boot - Controller-Service-Dao整體實作 - 3 - Inject Clock ⭐⭐⭐⭐⭐⭐ - ❓問題 : 幹嘛要用Clock?為何不用原本的new Date即可? - 💬回答 : - 假設圖書系統中借書的時長是30天,且逾期未還則立即註銷圖書會員。 - 若我們在 **測試檔** 中,**預期** `2026-03-01 00:00:00` 借書後,超過 `2026-03-31 00:00:00` 就立刻註銷會員,且不因資料庫延遲或時區等問題。 - 當使用new Date時,系統會抓取電腦當前時區時間,若其中慢了幾秒或是測試地區不同,導致與預期註銷時間不符,則測試不通過。 - 但當我們使用clock後,能夠自行設置一個固定時區時間,讓測試能夠按照流程,最後通過。 - Clock注入 ⭐⭐⭐⭐⭐⭐ - 如同上面所說,注入clock後,就可以在測試檔中直接測試,而注入方法如下。 ```java= package test.web.library.config; // 重要 @Configuration public class AppConfig{ @Bean public Clock clock(){ return Clock.systemDefaultZone(); } } ``` ```java= package test.web.library.serivce; @Component public class BookService { // 注入clock @Autowired private Clock clock; public Integer insert(BookRequest book){ Date now = Date.from(clock.instant()); return bookDao.insert(book, now); } public void update(Integer bookId, BookRequest book){ if (bookDao.query(bookId) == null) return; Date now = Date.from(clock.instant()); bookDao.update(bookId, book, now); } } ``` ```java= @SpringBootTest public class BookServiceTest { // 尚未學習w } ``` #### 實際測試流程 - 1️⃣ 開始測試,測試檔寫一個固定時區時間,並丟給Clock。 - 2️⃣ BookService向Clock要時間,Clock給他1️⃣設置的。 - 3️⃣ BookService注入clock變數並且放入BookDao method中。 - 4️⃣ BookDao操作資料庫時,放入1️⃣設置的時間。 ## Day59 #### 學習重點 : Spring Boot - 測試檔建置、Unit Test - 1 - `pom.xml` 引入 ⭐⭐ ```html= <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> ``` - scope的存在是因為該依賴檔只需要在測試時使用,於正式打包後,不會被加進來,省下空間。 - 檔案結構 ⭐⭐ ``` | -- test | -- java | -- test | -- web | -- library | -- BookServiceTest.java ``` - 測試檔程式結構 ⭐⭐⭐⭐⭐ - 在測試檔中,需要對class做註解 `@SpringBootTest`,才能夠做為測試的單元。 ```java= @SpringBootTest public class BookServiceTest{ @Autowired private BookService bookService; @MockitoBean private Clock clock; @Test public void testInsert(){ // ... } } ``` #### MockBean - 這邊使用的MockitoBean亦即 **模擬一個靜止的clock bean**,而該clock則是 **由test檔所操控**,相對應的AppConfig中的clock則是在實際run後所使用之 **會動的時鐘**。 - 雖然說今天有實際測試過testInsert,但未認真認識內部的使用方法,而是注重在如何讓測試環境通過,因此先不放上來! - 時間微調 ⭐⭐⭐⭐ - 在Mapper中,需要將 `rs.getDate` 改成 `rs.getTimestamp`,因為當初建立TABLE時,是利用TIMESTAMP建立的,若使用getDate,則會捨去 `HH:mm:ss` 這段,這對test檔中測試時間正確性有極大影響。 ```java= book.setPublished_date(rs.getTimestamp("published_date")); book.setCreated_date(rs.getTimestamp("created_date")); book.setLast_modified_date(rs.getTimestamp("last_modified_date")); ``` ## Day60 #### 學習重點 : Spring Boot - Unit Test - 2 - Instant類別、與Date差異 ⭐⭐⭐ - 簡單來說,Instant是Java的一個時間類別,相較於Date來說,Instant較統一,以UTC作為基準,全世界使用Instant都會得到同一時間。 - Instant取得時間是以 `1970-01-01 00:00:00 UTC 起算的秒數 + 奈秒數`(Unix時間原點),十分精確。 - 適合用於**存取資料庫時間**、**跨時區**的時間同步。 - Instant實際操作、與Clock關係 ⭐⭐⭐ - 透過Instant的內部邏輯,可以知道 : - `Instant.now()` 會 `return Clock.currentInstant()` ```java= Instant instant = Instant.now(); ``` - 設定時間(字串) ```java= Instant fixedInstant = Instant.parse("2026-03-01T00:00:00Z"); ``` - T作為分隔日期與時間,Z是零時區(格林威治時間)。 - 透過上述,可以知道,Clock是一個**鐘**(可以自由調整指針),而Instant則是**取得時間點**(自Unix時間開始)。 - 測試檔操作時間 ⭐⭐⭐⭐ ```java= // 建立時間點 Instant fixedInstant = Instant.parse("2026-03-01T00:00:00Z"); // 固定鐘,利用Instant參數 + 時區 Clock fixedClock = Clock.fixed(fixedInstant, ZoneId.of("UTC")); ```