1. 程式人生 > >【給小白看的Java教程】第二十三章,生命的遺傳:繼承

【給小白看的Java教程】第二十三章,生命的遺傳:繼承

繼承思想

需求,使用面向物件的知識定義出老師(Teacher)、學生(Student)、員工(Employee)三個類:

+ 老師:擁有名字、年齡、級別三個狀態,有授課和休息兩個功能

+ 學生:擁有名字、年齡、學好三個狀態,有學習和休息兩個功能

+ 員工:擁有名字、年齡、入職時間三個狀態,有工作和休息兩個功能

程式碼截圖如下:

image.png

此時,發現三個類中的存在著大量的共同程式碼,而我們要考慮的就是如何解決程式碼重複的問題。

面向物件的繼承思想,可以解決多個類存在共同程式碼的問題。

繼承關係設計圖:

image.png

記住幾個概念:

+ 被繼承的類,稱之為父類、基類

+ 繼承父類的類,稱之為子類,拓展類

+ 父類:存放多個子類共同的欄位和方法

+ 子類:存放自己特有的欄位和方法

繼承語法(重點)

在程式中,如果一個類需要繼承另一個類,此時使用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只能在本類中訪問

+ 父類的構造器,子類也不能繼承,因為構造器必須和當前的類名相同

image.png

方法覆蓋(掌握)

子類繼承了父類,可以擁有父類的部分方法和成員變數。可是當父類的某個方法不適合子類本身的特徵時,此時怎麼辦?比如鴕鳥(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,一看就能看出是抽象類。

特點:

+ 抽象類不能建立物件,呼叫沒有方法體的抽象方法沒有任何意義

+ 抽象類中可以同時擁有抽象方法和普通方法

+ 抽象類要有子類才有意義,子類必須覆蓋父類的抽象方法,否則子類也得作為抽象類

image.png

父類程式碼:

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。