Java物件導向03——三大特性之繼承

白夜的白發表於2024-04-27

一、繼承(extends)

1.1、繼承是什麼

繼承就是 Java 允許我們用 extends 關鍵字,讓一個類與另一個類建立起一種父子關係;

被繼承的類稱為父類(基類、超類),繼承父類的類都稱為子類(派生類) ,當子類繼承父類後,就可以直接使用父類公共的屬性和方法了

當子類繼承父類後,就可以直接使用父類公共的屬性和方法了

2.2、繼承的用處

來看看下面兩個類

圖片01

  • 可以看到,一個是學生類,一個是老師類;他們之間都有著相同的特徵:成員變數、方法
  • 這樣的話,重複程式碼又多了,是一種很不好的現象

我們使用繼承,來最佳化程式碼

圖片02

這樣減少程式碼冗餘,提高了程式碼的複用性,增強類的功能擴充套件性。

2.3、繼承的語法

​ 修飾符 class 子類名 extends 父類名


//父類:定義了一個公用方法ports
public class People {
    public void ports(){
        System.out.println("父類方法被呼叫!");
    }
}
//子類:使用extends繼承了來自父類的people類
public class Student extends People{
 
}
public class ExtendsTest01 {
    public static void main(String[] args) {
        //建立student子類物件
        Student s = new Student(); 
        //由於student類繼承了people類,所以子類可以呼叫父類的公用方法
        s.ports();              
    }

2.4、繼承的設計規範

子類們相同特徵:共性屬性,共性方法;都放在父類中定義

子類獨有的屬性,方法;應該定義在子類自己裡面。

// 人類:父類
// 包含了子類相同的屬性和行為
public class People {
//    1.定義需求中子類們相同的屬性
    private String name;
    private int age;
//    2.提供屬性對應的getter,setter方法,暴露其取值和賦值
    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;
    }
//    3.定義子類們相同的行為方法:檢視課表
    public void queryCourse(){
        System.out.println(name+"檢視了課表");
    }
}
//學生類:子類繼承People02父類得到屬性和行為
public class Student extends People{
//    1.定義學生子類自己獨有的屬性
    private String className;
 
//    2.提供屬性的getter和setter方法,暴露其取值和賦值
    public String getClassName() {
        return className;
    } 
    public void setClassName(String className) {
        this.className = className;
    }
//    3.定義子類自己獨有的行為方法:填寫聽課反饋
    public void writeInfo(){
        System.out.println(className+"的"+getName()+"對老師給出極高的評價!");
    }
}
//教師類:子類 繼承父類得到屬性和行為
public class Teacher extends People{
//    1.定義子類自己的獨有屬性
    private String departmentName;
 
//    2.提供屬性對應的getter,setter方法,暴露其取值和賦值
    public String getDepartmentName() {
        return departmentName;
    }
 
    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }
//    3.定義子類自己獨有的行為方法:釋出問題
    public void problem(){
        System.out.println(departmentName+"的"+getName()+"老師問:這節課教的怎麼樣?");
    }
}
//繼承測試類
public class ExtendsTest {
    public static void main(String[] args) {
//        1.建立學生物件
        Student02 s = new Student02();
//        1.1呼叫父類行為方法寫入學生資訊
        s.setName("海綿寶寶");
        s.setAge(36);
//        1.2呼叫學生類自己的方法寫入所在班級
        s.setClassName("19級比奇堡駕校一班");
//        1.3呼叫父類的行為方法:檢視課表
        s.queryCourse();
        System.out.println("---------------");
 
//        2.建立教師物件
        Teacher02 t = new Teacher02();
//        2.1呼叫父類行為寫入教師資訊
        t.setName("泡芙");
        t.setAge(55);
//        2.2呼叫教師類自己的方法寫入部門名稱
        t.setDepartmentName("比奇堡駕校金牌教練組");
//        2.3呼叫父類的行為方法:檢視課表
        t.queryCourse();
        System.out.println("---------------");
 
//        3.呼叫教師子類自己獨有的方法:problem 釋出問題
        t.problem();
//        4.呼叫學生子類自己獨有的方法:writeInfo 填寫反饋
        s.writeInfo();
    }
}

控制檯輸出結果:

圖片03

2.5、繼承的特點

子類可以繼承父類的屬性和行為,但是子類不能繼承父類的構造器;

java 是單繼承模式:一個類 只能繼承一個直接父類;

  • 比如你,只能有一個親爸,不可能隔壁的老王叔、老楊叔 … 都是你的親爸吧? 那從倫理角度看,肯定是不支援
  • 從 Java 的角度的角度看,多繼承會造成程式碼二義性,因此不支援

Java 不支援多繼承,但是支援多層繼承;

  • 就是你 不可以繼承多個爸爸;但是你可以繼承你爸的,你爸可以繼承你爺爺的,多層繼承;
  • 子類A 繼承父類B;父類B 可以繼承父類 C

Java 中所有的類都是 Object 類的子類。

  • java 中所有的類,要麼是直接繼承了 Object 類,要麼預設繼承了 Object 類,要麼間接繼承了 Object 類;

圖片04

二、繼承中成員訪問的特點

2.1、繼承中變數訪問的特點

在子類方法中訪問一個變數,採用的是就近原則。

  1. 子類區域性範圍找
  2. 子類成員範圍找
  3. 父類成員範圍找
  4. 如果都沒有就報錯(不考慮父親的父親…)
class Fu {
    int num = 10;
}
class Zi extends Fu{
    //int num = 20;
    public void show(){
        //num = 30;
        System.out.println(num);
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show(); // 輸出10
    }
}

2.2、super

this & super 關鍵字:

  • this:代表本類物件的引用
  • super:代表父類儲存空間的標識(可以理解為父類物件引用)

在子類方法中,如果想要明確訪問父類中成員時,藉助super關鍵字即可。

this 和 super 的使用分別

  • 成員變數:
    • this. 成員變數 - 訪問本類成員變數
    • super. 成員變數 - 訪問父類成員變數
  • 成員方法:
    • this. 成員方法 - 訪問本類成員方法
    • super. 成員方法 - 訪問父類成員方法
  • 構造方法:
    • this(…) - 訪問本類構造方法
    • super(…) - 訪問父類構造方法

2.3、繼承中構造方法訪問的特點

注意:子類中所有的構造方法預設都會訪問父類中無參的構造方法。

子類會繼承父類中的資料,可能還會使用父類的資料。所以,子類初始化之前,一定要先完成父類資料的初始化。

//父類,Animal類
class Animal {
	//建構函式
	public Animal() {
		System.out.println("Animal類的無引數建構函式執行");
	}
}
//子類,Cat類
class Cat extends Animal{
	//建構函式
	public Cat() {
        // super(); // 注意子類構造方法中預設會呼叫基類的無參構造方法:super(),
        // 使用者沒有寫時,編譯器會自動新增,而且super()必須是子類構造方法中第一條語句,
        // 並且只能出現一次
		System.out.println("Cat類的無引數建構函式執行");
	}
}
//執行程式碼
public static void main(String[] args) {
	Cat cat = new Cat(); 
}
//輸出結果
Animal類的無引數建構函式執行
Cat類的無引數建構函式執行

在子類構造方法中,並沒有寫任何關於基類構造的程式碼,但是在構造子類物件時,先執行基類的構造方法,然後執行子類的構造方法,因為:**子類物件中成員是有兩部分組成的,基類繼承下來的以及子類新增加的部分 。父子父子肯定是先有父再有子,所以在構造子類物件時候 ,先要呼叫基類的構造方法,將從基類繼承下來的成員構造完整,然後再呼叫子類自己的構造方法,將子類自己新增加的成員初始化完整 **

問題:如果父類中沒有無參構造方法,只有帶參構造方法,該怎麼辦呢?

  1. 透過使用 super 關鍵字去顯示的呼叫父類的帶參構造方法
  2. 子類透過 this 去呼叫本類的其他構造方法,本類其他構造方法再透過 super 去手動呼叫父類的帶參的構造方法

我們看下面這個例子:

public class MyTest {
	
	public static void main(String[] args) {
		Cat c1 = new Cat(3); 
		System.out.println("名字:" + c1.getName());
		System.out.println("年齡:" + c1.getAge());
	}
}

//父類,Animal類
class Animal {
	//私有屬性:名字
	private String name;
	
	//setter and getter
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	
	//建構函式
	public Animal() {
		
	}
	public Animal(String name) {
		this.name = name;
	}
}

//子類,Cat類
class Cat extends Animal{
	//私有欄位:年齡
	private int age;
	
	//setter and getter
	public void setAge(int age) {
		this.age = age;
	}
	public int getAge() {
		return age;
	}
	
	//建構函式
	public Cat() {
		
	}
	public Cat(int age) {
		this.age = age;
	}
}

輸出結果

名字:null
年齡:3

因為c1物件沒有給name賦值,所以是null

那麼我們給Cat加一個構造方法,給nameage都賦值。如下:

public Cat(String name, int age) {
	this.name = name; //報錯
	this.age = age
}

顯然這樣做是會報錯的,因為name已經被父類封裝成private的了,不能直接訪問,可能有的人會這樣做:

public Cat(String name, int age) {
	setName(name); // 因為父類的setName()方法是public的
	this.age = age;
}

顯然這樣做的確可以做到給父類的name賦值,但這樣做是不建議的,我們在構造方法中通常只呼叫構造方法,不會去呼叫例項方法,況且當不止一個變數時,用set方法時,我們就要呼叫好多個例項方法去完成多個變數的賦值。這時候為什麼不考慮使用super()方法呢?如下:

public Cat(String name, int age) {
   super(name);
   this.age = age;
}

注意: this(…)super(…) 必須放在構造方法的第一行有效語句,並且二者不能共存

2.4、繼承中成員方法的特點

成員方法名字不同

public class Base {
	public void methodA(){
	System.out.println("Base中的methodA()");
	}
}
public class Derived extends Base{
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
	}
	public void methodC(){
		methodB(); // 訪問子類自己的methodB()
		methodA(); // 訪問父類繼承的methodA()
		// methodD(); // 編譯失敗,在整個繼承體系中沒有發現方法methodD()
	}
}

所以說,成員方法沒有同名時,在子類方法中或者透過子類物件訪問方法時,則優先訪問自己的,自己沒有時再到父類中找,如果父類中也沒有則報錯。

成員方法名字相同

public class Base {
	public void methodA(){
		System.out.println("Base中的methodA()");
	}
	public void methodB(){
		System.out.println("Base中的methodB()");
	}
}
public class Derived extends Base{
	public void methodA(int a) {
		System.out.println("Derived中的method(int)方法");
	}
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
	}
	public void methodC(){
		methodA(); // 沒有傳參,訪問父類中的methodA()
		methodA(20); // 傳遞int引數,訪問子類中的methodA(int)
		methodB(); // 直接訪問,則永遠訪問到的都是子類中的methodB(),基類的無法訪問到
	}
}
@Test
public void test() {
    Derived d = new Derived();
    d.methodC();
}

輸出結果:

Base中的methodA()
Derived中的method(int)方法
Derived中的methodB()方法

說明:

  • 透過子類物件訪問父類與子類中不同名方法時,優先在子類中找,找到則訪問,否則在父類中找,找到則訪問,否則編譯報錯。
  • 透過派生類物件訪問父類與子類同名方法時,如果父類和子類同名方法的引數列表不同(過載),根據呼叫方法適傳遞的引數選擇合適的方法訪問,如果沒有則報錯;

那麼問題來了,如果子類中存在與父類中相同的成員時,那如何在子類中訪問父類相同名稱的成員呢?

使用 super 關鍵字

由於設計不好,或者因場景需要,子類和父類中可能會存在相同名稱的成員,如果要在子類方法中訪問父類同名成員時,該如何操作?直接訪問是無法做到的,Java 提供了 super 關鍵字,該關鍵字主要作用:在子類方法中訪問父類的成員。

public class Base {
	int a;
	int b;
	public void methodA(){
		System.out.println("Base中的methodA()");
	}
	public void methodB(){
		System.out.println("Base中的methodB()");
	}
}
public class Derived extends Base{
	int a; // 與父類中成員變數同名且型別相同
	char b; // 與父類中成員變數同名但型別不同
	// 與父類中methodA()構成過載
	public void methodA(int a) {
		System.out.println("Derived中的method()方法");
	}
	// 與基類中methodB()構成重寫(即原型一致,重寫後序詳細介紹)
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
	}
	public void methodC(){
		// 對於同名的成員變數,直接訪問時,訪問的都是子類的
		a = 100; // 等價於: this.a = 100;
		b = 101; // 等價於: this.b = 101;
		// 注意:this是當前物件的引用
		// 訪問父類的成員變數時,需要藉助super關鍵字
		// super是獲取到子類物件中從基類繼承下來的部分
		super.a = 200;
		super.b = 201;
		// 父類和子類中構成過載的方法,直接可以透過引數列表區分清訪問父類還是子類方法
		methodA(); // 沒有傳參,訪問父類中的methodA()
		methodA(20); // 傳遞int引數,訪問子類中的methodA(int)
		// 如果在子類中要訪問重寫的基類方法,則需要藉助super關鍵字
		methodB(); // 直接訪問,則永遠訪問到的都是子類中的methodA(),基類的無法訪問到
		super.methodB(); // 訪問基類的methodB()
	}
}

2.5、super和this

super 和this 都可以在成員方法中用來訪問:成員變數和呼叫其他的成員函式,都可以作為構造方法的第一條語句,那他們之間有什麼區別呢?

相同點

  • 都是 Java 中的關鍵字
  • 只能在類的非靜態方法中使用,用來訪問非靜態成員方法和欄位
  • 在構造方法中呼叫時,必須是構造方法中的第一條語句,並且不能同時存在

不同點

  • this是當前物件的引用,當前物件即呼叫例項方法的物件,super 相當於是子類物件中從父類繼承下來部分成員的引用
  • 在非靜態成員方法中,this 用來訪問本類的方法和屬性,super 用來訪問父類繼承下來的方法和屬性
  • 在構造方法中:this(…)用於呼叫本類構造方法,super(…)用於呼叫父類構造方法,兩種呼叫不能同時在構造方法中出現
  • 構造方法中一定會存在super(…)的呼叫,使用者沒有寫編譯器也會增加,但是this(…)使用者不寫則沒有

相關文章