【給小白看的Java教程】第二十三章,生命的遺傳:繼承
繼承思想
需求,使用面向物件的知識定義出老師(Teacher)、學生(Student)、員工(Employee)三個類:
+ 老師:擁有名字、年齡、級別三個狀態,有授課和休息兩個功能
+ 學生:擁有名字、年齡、學好三個狀態,有學習和休息兩個功能
+ 員工:擁有名字、年齡、入職時間三個狀態,有工作和休息兩個功能
程式碼截圖如下:
此時,發現三個類中的存在著大量的共同程式碼,而我們要考慮的就是如何解決程式碼重複的問題。
面向物件的繼承思想,可以解決多個類存在共同程式碼的問題。
繼承關係設計圖:
記住幾個概念:
+ 被繼承的類,稱之為父類、基類
+ 繼承父類的類,稱之為子類,拓展類
+ 父類:存放多個子類共同的欄位和方法
+ 子類:存放自己特有的欄位和方法
繼承語法(重點)
在程式中,如果一個類需要繼承另一個類,此時使用extends關鍵字。
public class 子類名 extends 父類名{
}
注意:Java中類只支援單繼承,但是支援多重繼承。也就是說一個子類只能有一個直接的父類,父類也可以再有父類。
+ 下面是錯誤的寫法! Java中的類只支援單繼承。
class SuperClass1{}
class SuperClass2{}
class SubClass extends SuperClass1,SuperClass2{}//錯誤
+ 下面程式碼是正確的。一個父類可以有多個子類。
class SuperClass{} class SubClass1 extends SuperClass{} class SubClass2 extends SuperClass{}
+ 下面程式碼是正確的,支援多重繼承。
class SupperSuperClass{}
class SupperClass extends SupperClass{}
class SubClass extends SupperClass
+ Object類是Java語言的根類,任何類都是Object的子類,要麼是直接子類,要麼是間接子類(後講)
public class Person{} 等價於 public class Person **extends Object**{}
繼承操作(重點)
父類程式碼:
public class Person { private String name; private int age; public void rest() { System.out.println("休息"); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
子類程式碼:
public class Student extends Person{
private String sn;// 學號
public void study() {
System.out.println("學習");
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
}
測試程式碼:
public class ExtendsDemo {
public static void main(String[] args) {
//建立學生物件
Student stu = new Student();
//設定欄位資訊
stu.setName("will"); //繼承了父類
stu.setAge(17); //繼承了父類
stu.setSn("s_123");
//呼叫方法
stu.study();
stu.rest(); //繼承了父類
}
}
子類可以繼承到父類哪些成員(瞭解)
子類繼承父類之後,可以擁有到父類的某一些成員(欄位和方法),根據訪問修飾符來判斷:
+ 如果父類中的成員使用public和protected修飾,子類都能繼承.
+ 如果父類和子類在同一個包中,使用預設訪問修飾的成員,此時子類可以繼承到
+ 如果父類中的成員使用private修飾,子類繼承不到。private只能在本類中訪問
+ 父類的構造器,子類也不能繼承,因為構造器必須和當前的類名相同
方法覆蓋(掌握)
子類繼承了父類,可以擁有父類的部分方法和成員變數。可是當父類的某個方法不適合子類本身的特徵時,此時怎麼辦?比如鴕鳥(Ostrich)是鳥類(Bird)中的一個特殊品種,所以鴕鳥類是鳥類的一個子類,但是鳥類有飛翔的功能,但是對應鴕鳥,飛翔的行為顯然不適合於它。
父類:
public class Bird {
public void fly() {
System.out.println("飛呀飛...");
}
}
子類:
public class Ostrich extends Bird{
}
測試類:
public class OverrideDemo {
public static void main(String[] args) {
//建立鴕鳥物件
Ostrich os = new Ostrich();
//呼叫飛翔功能
os.fly();
}
}
執行結果:
飛呀飛…
上述程式碼從語法是正確的,但從邏輯上是不合理的,因為鴕鳥不能飛翔,此時怎麼辦?——方法覆蓋操作。
方法覆蓋操作(重點掌握)
當子類存在一個和父類一模一樣的方法時,我們就稱之為子類覆蓋了父類的方法,也稱之為重寫。那麼我們就可以在子類方法體中,重寫編寫邏輯程式碼。
public class Ostrich extends Bird{
public void fly() {
System.out.println("撲撲翅膀,快速奔跑...");
}
}
執行測試程式碼:
撲撲翅膀,快速奔跑…
方法的呼叫順序:
通過物件呼叫方法時,先在子類中查詢有沒有對應的方法,若存在就執行子類的,若子類不存在就執行父類的,如果父類也沒有,報錯。
方法覆蓋的細節:
private修飾的方法不能被子類所繼承,也就不存在覆蓋的概念。
① 例項方法簽名必須相同 (方法簽名= 方法名 + 方法的引數列表)
② 子類方法的返回值型別是和父類方法的返回型別相同或者是其子類
③ 子類方法中宣告丟擲的異常小於或等於父類方法宣告丟擲異常型別
④ 子類方法的訪問許可權比父類方法訪問許可權更大或相等
上述的方法覆蓋細節真多,記不住,那麼記住下面這句話就萬事OK了。
精華:直接拷貝父類中方法的定義貼上到子類中,再重新編寫子類方法體,打完收工!
super關鍵字(掌握)
問題,在子類中的某一個方法中需要去呼叫父類中被覆蓋的方法,此時得使用super關鍵字。
public class Ostrich extends Bird{
public void fly() {
System.out.println("撲撲翅膀,快速奔跑...");
}
public void say() {
super.fly();//呼叫父類被覆蓋的方法
fly();//呼叫本類中的方法
}
}
如果呼叫被覆蓋的方法不使用super關鍵字,此時呼叫的是本類中的方法。
super關鍵字表示父類物件的意思,更多的操作,後面再講。
super.fly()可以翻譯成呼叫父類物件的fly方法。
抽象方法和抽象類(掌握)
需求:求圓(Circle)和矩形(Rectangle)兩種圖形的面積。
分析:無論是圓形還是矩形,還是其他形狀的圖形,只要是圖形,都有面積,也就說圖形都有求面積的功能,那麼我們就可以把定義一個圖形(Graph)的父類,該類擁有求面積的方法,但是作為圖形的概念,而並不是某種具體的圖形,那麼怎麼求面積是不清楚的,姑且先讓求面積的getArea方法返回0。
父類程式碼:
public class Graph {
public double getArea() {
return 0.0;
}
}
子類程式碼(圓形):
public class Circle extends Graph {
private int r; //半徑
public void setR(int r) {
this.r = r;
}
public double getArea() {
return 3.14 * r * r;
}
}
子類程式碼(矩形):
public class Rectangle extends Graph {
private int width; // 寬度
private int height; // 高度
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public double getArea() {
return width * height;
}
}
測試程式碼:
public class GraphDemo {
public static void main(String[] args) {
// 圓
Circle c = new Circle();
c.setR(10);
double ret1 = c.getArea();
System.out.println("圓的面積:" + ret1);
// 矩形
Rectangle r = new Rectangle();
r.setWidth(5);
r.setHeight(4);
double ret2= r.getArea();
System.out.println("矩形的面積:" + ret2);
}
}
執行結果如下:
圓的面積:314.0
矩形的面積:20.0
引出抽象方法(瞭解)
問題1:既然不同的圖形求面積的演算法是不同的,所以必須要求每一個圖形子類去覆蓋getArea方法,如果沒有覆蓋,應該以語法報錯的形式做提示。
問題2:在Graph類中的getArea方法的方法體沒有任何存在意義,因為不同圖形求面積演算法不一樣,子類必須要覆蓋getArea方法。
要滿足上述對方法的要求,就得使用abstract來修飾方法,被abstract修飾的方法具備兩個特徵:
+ 該方法沒有方法體
+ 要求子類必須覆蓋該方法
這種方法,我們就稱之為抽象方法。
抽象方法和抽象類(重點掌握)
使用abstract修飾的方法,稱為抽象方法。
public abstract 返回型別 方法名(引數);
特點:
+ 使用abstract修飾,沒有方法體,留給子類去覆蓋
+ 抽象方法必須定義在抽象類或介面中
使用abstract修飾的類,成為抽象類。
public abstract class 類名{
}
一般的,抽象類以Abstract作為類名字首,如AbstractGraph,一看就能看出是抽象類。
特點:
+ 抽象類不能建立物件,呼叫沒有方法體的抽象方法沒有任何意義
+ 抽象類中可以同時擁有抽象方法和普通方法
+ 抽象類要有子類才有意義,子類必須覆蓋父類的抽象方法,否則子類也得作為抽象類
父類程式碼:
public abstract class AbstractGraph {
public abstract double getArea(); //沒有方法體
}
子類程式碼:
public class Circle extends AbstractGraph {
private int r;// 半徑
public void setR(int r) {
this.r = r;
}
public double getArea() {//覆蓋父類抽象方法
return 3.14 * r * r;//編寫方法體
}
}
測試類沒有改變。
###Object類和常用方法(掌握)
Object本身表示物件的意思,是Java中的根類,要麼是一個類的直接父類,要麼就是一個類的間接父類。
class A{} 其實等價於 class A extends Object{}
因為所有類都是Object類的子類, 所有類的物件都可以呼叫Object類中的方法,常見的方法:
- boolean equals(Object obj):拿當前呼叫該方法的物件和引數obj做比較
在Object類中的equals方法和“ == ”符號相同都是比較物件是否是同一個的儲存地址。
public class ObjectDemo {
public static void main(String[] args) {
//建立Person物件p1
Person p1 = new Person();
//建立Person物件p2
Person p2 = new Person();
//比較p1和p2的記憶體地址是否相同
boolean ret1 = p1 == p2;
boolean ret2 = p1.equals(p2);
System.out.println(ret1); //false
System.out.println(ret2); //false
}
}
官方建議:每個類都應該覆蓋equals方法去比較我們關心的資料,而不是記憶體地址。
- String toString():表示把物件中的欄位資訊轉換為字串格式 列印物件時其實列印的就是物件的toString方法
Person p = new Person();
p.setName("will");
p.setAge(17);
System.out.println(p);
System.out.println(p.toString());
其中:
System.out.println(p);//等價於 System.out.println(p.toString());
列印格式如:
[email protected]
預設情況下列印的是物件的hashCode值,但是我們更關心物件中欄位儲存的資料。 官方建議:應該每個類都應該覆蓋toString返回我們關心的資料,如:
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
此時列印物件,看到的是該物件的欄位資訊。
Person [name=will, age=17]
可以通過Eclipse生成toString方法,剛開始一定要手寫。 == 符號到底比較的是什麼: 比較基本資料型別:比較兩個值是否相等 比較物件資料型別:比較兩個物件是否是同一塊記憶體空間 每一次使用new關鍵字,都表示在堆中建立一塊新的記憶體空間。
若要獲得最好的學習效果,需要配合對應教學視訊一起學習。需要完整教學視訊,請參看https://ke.qq.com/course/272077。