--- tags: Java --- # Extends & Constructor ## 懶人包 - 如果父類別有定義 constructor,且**有參數**,那子類別一定要呼叫 `super()` 並帶入對應的引數 所以 - 盡量所有 class 都有 default constructor - 盡量所有子類別都要在 constructor 呼叫 `super()` ## Constructor - 和 class 名稱相同的 method - 用來**初始化**物件的 properties - Method 可以 [**overloading**](http://hsingjungchen.blogspot.com/2017/04/java-overloadingoverriding.html),所以同一個 class 可以有多個 constructors,但是不同的 constructors 參數不能相同 - **Exp**: ```java= class Data { int a, b; public Data() { a = 0; b = 0; } public Data(int x, int y) { a = x; b = y; } public void print() { System.out.printf("(%d, %d)\r\n"); } } class Main { public static void main(String[] args) { Data d1 = new Data(); // 用第4行的 Data() 初始化 Data d2 = new Data(2, 3); // 用第9行的 Data(int, int) 初始化 d1.print(); d2.print(); } } ``` **Output**: ``` (0, 0) (2, 3) ``` <br> - 除了直接 assign,也可以在 constructor 裡面運算 - **Exp** ```java class Data { int a, b; public Data(int x, int y) { a = 2 * x + y; b = 6 * y + 3; } public void print() { System.out.printf("(%d, %d)\r\n", a, b); } } class Main { public static void main(String[] args) { Data d = new Data(2, 3); d.print(); } } ``` **Output** ``` (7, 21) ``` ## Default Constructor - 沒有參數的 constructor - **Exp** ```java class Data { int a, b; public Data() { a = 0; b = 0; } public void print() { System.out.printf("(%d, %d)\r\n", a, b); } } class Main { public static void main(String[] args) { Data d = new Data(); d.print(); } } ``` **Output**: ``` (0, 0) ``` ### Compiler-Generated Constructor - 當你的 class **沒有定義任何 constructor 時**,compiler 會自動產生一個 default constructor - 它只會在編譯的時候加到你的 class 中,原始的程式碼檔案不會改變 - **證明**: 即使沒有定義 `Data()`,還是可以用 `new Data()` 建立物件,說明這個情況下 `Data()` 確實是存在的 - **Exp** ```java class Data { int a, b; public void print() { System.out.printf("(%d, %d)\r\n", a, b); } } class Main { public static void main(String[] args) { Data d = new Data(); d.print(); } } ``` **Output** ``` (0, 0) ``` <br> - 會用預設的方式初始化 properties - **Primary type** (`int`, `float`, `char`, `boolean`, ...) - 會被設為 0 (在記憶體裡面的 bits 全為 0) - 對 `boolean` 來說是 `false`,`char` 是 `'\0'`(空字元,印出來不會有東西) - **Exp** ```java class Data { char c; int i; float f; boolean b; public void print() { System.out.println(c); System.out.println(i); System.out.println(f); System.out.println(b); } } class Main { public static void main(String[] args) { Data d = new Data(); d.print(); } } ``` **Output** ``` 0 0.0 false ``` - **Class** (`Scanner`, `String`, `Integer`, ...): 設為 null - **Exp** ```java import java.util.Scanner; class Data { String str; Scanner sc; Integer i; public void print() { System.out.println(str); System.out.println(sc); System.out.println(i); } } class Main { public static void main(String[] args) { Data d = new Data(); d.print(); } } ``` **Output** ``` null null null ``` <br> - **如果有自己定義的、有參數的 constructor** (非 default constructor),那 compiler 就**不會**自動產生 default constructor :::danger - **Exp** ```java= class Data { int a, b; public Data(int x, int y) { a = x; b = y; } public void print() { System.out.printf("(%d, %d)\r\n", a, b); } } class Main { public static void main(String[] args) { Data d = new Data(); d.print(); } } ``` - **Error Message** ``` Data.java:16: error: constructor Data in class Data cannot be applied to given types; Data d = new Data(); ^ required: int,int found: no arguments reason: actual and formal argument lists differ in length 1 error ``` ::: ## 子類別的 Constructor - 子類別**一定要**呼叫父類別的 constructor - 可以自己透過 `super()` 呼叫父類別的 constructor - `super()` 的引數 (arguments) 沒有限制,只要能對應父類別 constructor 的參數 (parameters) 就可以 - **Exp** ```java= class Rectangle { int length, width; public Rectangle() { length = 0; width = 0; } public Rectangle(int l, int w) { length = l; width = w; } public int getArea() { return length * width; } } class Cuboid extends Rectangle { int height; public Cuboid() { super(); // 呼叫第 4 行的 Rectangle() height = 0; } public Cuboid(int l, int w, int h) { super(l, w); // 呼叫第 9 行的 Rectangle(int, int) height = h; } public int getVolume() { return getArea() * height; } } class Main { public static void main(String[] args) { Cuboid c1 = new Cuboid(); Cuboid c2 = new Cuboid(1, 2, 3); System.out.println("c1 base area: " + c1.getArea()); System.out.println("c1 volume: " + c1.getVolume()); System.out.println("c2 base area: " + c2.getArea()); System.out.println("c2 volume: " + c2.getVolume()); } } ``` **Output**: ``` c1 base area: 0 c1 volume: 0 c2 base area: 2 c2 volume: 6 ``` <br> - 如果在子類別中**沒有自己呼叫 `super()`**,編譯器會自動幫你呼叫 - 自動呼叫的 `super()` 不會有引數,所以會呼叫父類別的 default constructor - 會這樣設計,是因為編譯器沒有強大到可以判斷正確的引數 - **Exp** ```java class Rectangle { int length, width; public Rectangle() { length = 0; width = 0; } public Rectangle(int l, int w) { length = l; width = w; } public int getArea() { return length * width; } } class Cuboid extends Rectangle { int height; public Cuboid() { // 不呼叫 super() // 但得到一樣的結果 // 表示實際上 Rectangle() 有被執行 height = 0; } public Cuboid(int l, int w, int h) { // 不呼叫 super(int, int) // getArea(), getVolume() 都得到 0 // 表示實際上被執行的是 Rectangle() height = h; } public int getVolume() { return getArea() * height; } } class Main { public static void main(String[] args) { Cuboid c1 = new Cuboid(); Cuboid c2 = new Cuboid(1, 2, 3); System.out.println("c1 base area: " + c1.getArea()); System.out.println("c1 volume: " + c1.getVolume()); System.out.println("c2 base area: " + c2.getArea()); System.out.println("c2 volume: " + c2.getVolume()); } } ``` **Output** ``` c1 base area: 0 c1 volume: 0 c2 base area: 0 c2 volume: 0 ``` ### 可能發生的錯誤 :::danger 以下的程式碼會編譯錯誤 ```java class Rectangle { int length, width; public Rectangle(int l, int w) { length = l; width = w; } public int getArea() { return length * width; } } class Cuboid extends Rectangle { int height; public Cuboid(int l, int w, int h) { length = l; width = w; height = h; } public int getVolume() { return getArea() * height; } } class Main { public static void main(String[] args) { Cuboid c = new Cuboid(1, 2, 3); System.out.println("c1 base area: " + c.getArea()); System.out.println("c1 volume: " + c.getVolume()); } } ``` ``` Data.java:17: error: constructor Rectangle in class Rectangle cannot be applied to given types; public Cuboid(int l, int w, int h) { ^ required: int,int found: no arguments reason: actual and formal argument lists differ in length 1 error ``` ::: **因為**... - class `Rectangle` 中只有一個**有參數**的 constructor - `Rectangle(int, int)` - $\Rightarrow$ 因為有自訂的了,所以編譯器不會自動產生 default constructor - `Rectangle()` - $\Rightarrow$ 所以 `Rectangle()` 不存在 - `Cuboid(int, int, int)` 中沒有 `super()` - $\Rightarrow$ 編譯器會自動呼叫 `Rectangle()` - $\Rightarrow$ 但 `Rectangle()` 不存在,所以編譯錯誤 ### 修正方案 - **手動呼叫 `super()`** - 因為 `Rectangle` 只有 `Rectangle(int, int)` 一個 constructor,所以在 `Cuboid()` 中要傳遞兩個引數給 `super()` <br> ```java class Rectangle { int length, width; public Rectangle(int l, int w) { length = l; width = w; } public int getArea() { return length * width; } } class Cuboid extends Rectangle { int height; public Cuboid(int l, int w, int h) { super(l, w); height = h; } public int getVolume() { return getArea() * height; } } class Main { public static void main(String[] args) { Cuboid c = new Cuboid(1, 2, 3); System.out.println("c1 base area: " + c.getArea()); System.out.println("c1 volume: " + c.getVolume()); } } ``` - **實作 default constrcutor** - 在 `Rectangle` 加入 default constrcutor - `Rectangle()` <br> ```java class Rectangle { int length, width; public Rectangle() { length = 0; width = 0; } public Rectangle(int l, int w) { length = l; width = w; } public int getArea() { return length * width; } } class Cuboid extends Rectangle { int height; public Cuboid(int l, int w, int h) { height = h; } public int getVolume() { return getArea() * height; } } class Main { public static void main(String[] args) { Cuboid c = new Cuboid(1, 2, 3); System.out.println("c1 base area: " + c.getArea()); System.out.println("c1 volume: " + c.getVolume()); } } ``` - **我全都要** - **最推薦的方式** - 如果情況允許,請盡量實作 default constructor - 並在所有子類別都呼叫 `super()`,且盡可能的把事情丟給 `super()` 做 - 在此例中,`Rectangle` 有 `length`, `width` 共兩個 properties - `Cuboid` 除了繼承來的兩個,還有 `height` 共三個 properties - 在 `Cuboid()` 中,`length` 和 `width` 應該盡量由 `Rectangle()` 負責初始化 - 因為 `Cuboid` 作為 `Rectangle` 的擴充,它新增了 `height`,那 `Cuboid` 就應該只負責 `height`,剩下的應該交給已經定義好的 `Rectangle` - 當然這只是建議,這個原則是可以依照需求打破的,能正確的完成需求才是優先考慮的事情 - **移除 `Rectangle` 的所有 constructor** - 在這個例子中,因為不會宣告 `Rectangle` 的物件,所以 `Rectangle` 可以不用有 constructor - 這樣編譯器就會自動產生 `Rectangle()` - 在 `Cuboid()` 中也可以不用 `super()` - 但不是每次都可以這樣設計,請小心使用