# 繼承的使用
如果有一種情況,定義了 Aminal
類別,它的定義有:
屬性: | |
年齡、重量、名字 | |
方法: | |
移動 |
若此時,我又定義了一個類別,稱作 Dog :
屬性: | |
年齡、重量、名字、毛色、身長 | |
方法: | |
移動、睡、吃、喝 |
這時,我們會發現 Dog
有許多部分與 Animal
重複。
我們可以在定義時,就延伸動物的類別,再加上狗專屬的特性即可。Dog
就繼承了 Animal
, Animal
在這裡是父類別, Dog
就成了繼承父類別的子類別。
# 程式碼呈現 extends
class 子類別 extends 父類別{ | |
// 程式碼 | |
} |
只要在子類別的後方加上 extends
(延伸) 關鍵字就可以全擁有父類別的所有成員了。
如果用上述的例子還原程式碼:
class Animal{ | |
int age; | |
int weight; | |
String name; | |
int move(){ | |
} | |
} | |
class Dog extends Animal{ | |
Color hair; | |
void sleep(){ | |
} | |
void eat(){ | |
} | |
void drink(){ | |
} | |
} |
我們可以將繼承想成是一般化與特殊化的關係,
繼承樹上越頂端的父類別擁有越一般的特性,越底端的子類別越特殊
子類別擁有父類別定義的所有成員,再多了自己特有的東西。
Java 是純物件導向的程式語言,
每個類別,包括自訂的類別,都繼承 Object。
# this
關鍵字
指到自己,也就是自己類別的成員,也可以理解為:指向對象的一個指針。
class People{ | |
String name; | |
int age; | |
People(String name){ | |
this.name = name; | |
} | |
String getName(){ | |
return this.name; | |
} | |
} |
this.name
意思是『自己這個類別的成員 name』,繼承關係越複雜的情況下這樣寫法可以大大增加程式的可讀性。
# 自己的建構字 this (.)
如果寫了很多建構子提供多元的建構物件方式,建構子之間彼此可以互相呼叫:
class Human{ | |
String name; | |
int age; | |
static int totalCount = 0; | |
Human(){ | |
name = "untitled"; | |
age = -1; // 使用 - 1 來標記沒有被設定,否則會初始化為 0,但人類有可能 0 歲 | |
totalCount++; | |
} | |
Human(String name){ | |
this(); | |
this.name = name; | |
} | |
Human(String name,int age){ | |
this(name); | |
this.age = age; | |
} | |
void printInfo(){ | |
System.out.println(name+" 年齡:"+age+" 目前總人數:"+totalCount); | |
} | |
} |
this()
表示呼叫自己不帶參數的建構子, this(String)
表示呼叫自己帶有一個字串參數的建構子,以此類推。
this()
須放置於第一行,否則,初始化的部分會出錯!!
class Test{ | |
public static void main(String[] args){ | |
Human h1 = new Human(); | |
h1.printInfo(); | |
Human h2 = new Human("Zrn"); | |
h2.printInfo(); | |
Human h3 = new Human("Zen",17); | |
h3.printInfo(); | |
} | |
} |
untitled 年齡:-1 目前總人數:1 | |
Zrn 年齡:-1 目前總人數:2 | |
Zen 年齡:18 目前總人數:3 |
# super
關鍵字
指到父類別,使用方法跟 this 類似。
class Animal { | |
int height; | |
int weight; | |
static int totalCount = 0; | |
Animal() { | |
this(-1, -1); | |
} | |
Animal(int h) { | |
this(h, -1); | |
} | |
Animal(int h, int w) { | |
this.height = h; | |
this.weight = w; | |
totalCount++; | |
} | |
String getInfo() { | |
return "身長:" + height + " 重量:" + weight; | |
} | |
} |
class Dog extends Animal { | |
String color; | |
static int totalCount = 0; | |
Dog() { | |
this(-1, -1, "noset"); | |
} | |
Dog(int h, int w) { | |
this(h, w, "noset"); | |
} | |
Dog(String c) { | |
this(-1, -1, c); | |
} | |
Dog(int h, int w, String c) { | |
super(h, w); | |
this.color = c; | |
totalCount++; | |
} | |
String getInfo() { | |
return super.getInfo() + " 毛色:" + this.color; | |
} | |
} |
# 父類別的建構子 super (.)
很多時候父類別已經定義好的東西,子類別直接用就好,設計上比較好維護,設計邏輯比較有階層性。
# 覆寫
對照上面的程式碼,依據繼承原則,我們可以知道 Dog
繼承了 Animal
。所以,不需要多寫,但是父類別又太過於一般化,沒辦法滿足子類別需要的功能,所以子類別『 覆寫(override)
』了父類別的方法,創造了特殊且適合自己的方法。
class Test { | |
public static void main(String[] args) { | |
Dog d1 = new Dog(); | |
System.out.println(d1.getInfo()); | |
Dog d2 = new Dog(30, 10); | |
System.out.println(d2.getInfo()); | |
Dog d3 = new Dog("white"); | |
System.out.println(d3.getInfo()); | |
Dog d4 = new Dog(30, 10, "white"); | |
System.out.println(d4.getInfo()); | |
System.out.println("動物數量:" + Animal.totalCount); | |
System.out.println("狗狗數量:" + Dog.totalCount); | |
}// end of main(String[]) | |
} |
身長:-1 重量:-1 毛色:noset | |
身長:30 重量:10 毛色:noset | |
身長:-1 重量:-1 毛色:white | |
身長:30 重量:10 毛色:white | |
動物數量:4 | |
狗狗數量:4 |
# 覆寫的存取修飾限制
覆寫 Override
,字面上的意思就是『覆蓋重寫』。
在繼承中關係,父類別定義了一些方法,子類別覺得不適用的話可以『覆蓋』掉父類別的方法,然後『重寫』屬於自己的方法。
class A{ | |
void printInfo(){ | |
System.out.println("hello, I am A."); | |
} | |
} | |
class B extends A{ | |
void printInfo(){ | |
System.out.println("hello, I am B."); | |
} | |
} | |
class C extends A{ | |
} |
class Test { | |
public static void main(String[] args) { | |
B b = new B(); | |
b.printInfo(); | |
C c = new C(); | |
c.printInfo(); | |
} | |
} |
hello, I am B. | |
hello, I am A. |
上述程式中,B 與 C 都是繼承 A,表示擁有了 A 所有的成員,但 B 覆寫了 printInfo () 方法,而 C 沒有。 所以在呼叫的時候,物件 b 會使用 B 類別覆寫的方法,而物件 c 因為 C 類別沒有自己定義,所以會使用到父類別 A 所定義的 printInfo ()。
要覆寫父類別方法必須滿足幾個條件:
- 父類別方法不能用 final 修飾。
- 子類別覆寫的方法名稱、回傳型態、參數個數順序需相同。
- 子類別覆寫的方法,其開放權限不可以小於要覆寫的父類別方法。
存取修飾子的開放權限從大到小:public
->protected
->(no modifier)
->private
第三點範例
class A{ | |
// 注意存取修飾子是 (no modifier) | |
void printInfo(){ | |
System.out.println("hello, this is A."); | |
} | |
} | |
class B extends A{ | |
// ↓ 編譯錯誤,覆寫的方法存取權限小於覆寫對象 | |
private void printInfo(){ | |
System.out.println("hello, this is B."); | |
} | |
} |
在 A 類別中的 printInfo()
方法修飾子是 (no modifier)
,依據覆寫的開放權限規則,
B 類別繼承了 A 類別想覆寫 printInfo () ,覆寫的開放權限必須為 public
或 protected
或 (no modifier)
,重點就是不能小於覆寫對象,否則會發生編譯錯誤: 『Cannot reduce the visibility of the inherited method from A』
。