---
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()`
- 但不是每次都可以這樣設計,請小心使用