首頁技術文章正文

Java培訓:面向對象 - 繼承

更新時間:2022-08-25 來源:黑馬程序員 瀏覽量:

  前言 :

   Hello , 各位同學朋友大家好啊, 今天給大家分享的技術呢, 是面向對象三大特征之一的繼承,我們今天主要按照以下幾個點, 展開繼承的講解。

  目錄 :

  * 繼承的介紹

  * 繼承的好處和弊端

  * 繼承中成員訪問特點 - 成員變量

  * 繼承中成員訪問特點 - 成員方法

  * 方法重寫

  * 繼承中成員訪問特點 - 構造方法

  * this 和 super 關鍵字

  1. 繼承的介紹

   提到了繼承, 大家想到了什么 ? 是的, 子女繼承到的家產(chǎn), 那家產(chǎn)是存在著一種關系的, 父親和兒子的關系 。

   對比生活中的繼承, Java 中的繼承, 其實就是讓**類與類**之間產(chǎn)生關系, 什么關系 ? 子父類關系, 當這種關系建立起來之后, 子類就可以直接使用父類中, **非私有**的成員了。

   聊到這個地方, 很多同學就在想, 我為什么讓我的類與類之間建立這種關系呢 ? 請同學們先閱讀下方代碼。

   假設, 我要編寫一款 xxx 公司的人員管理系統(tǒng), 這款管理系統(tǒng)需要對項目經(jīng)理和程序員的信息做管理, 所以就需要編寫如下兩個類

1661413215489_1.jpg

   現(xiàn)在大家可以發(fā)現(xiàn), 這兩個類中的東西, 完全是長一樣的 !!! 那我將來要是再多一個 Hr 的類, 對人事的信息做管理, 這些重復的代碼, 豈不是要再寫一遍 ? 麻煩 !

   所以, 這時候我要用繼承來優(yōu)化代碼了

   我將這些類當中, [共性] 或者說是 [相同] 的內(nèi)容, 抽取到一個父類 (Employee 員工 ) 當中 , 再讓 Coder 和 Manager 繼承 Employee, 代碼就不需要重復編寫了 , 這就能夠提高代碼的復用性了 ~

1661413238404_2.jpg

   但是, 代碼層面, 我給如何讓 Coder 和 Manager 繼承 Employee 呢 ? 請同學們繼續(xù)看繼承的格式。

  繼承的格式 :

1661413252495_3.jpg

  可以看到, 實現(xiàn)繼承, 我們是通過 extends 關鍵字進行編寫的 , 下面我們編寫下代碼

  示例代碼 :

class Employee {
    String name;
    int age;
    double salary;
}

class Coder extends Employee {

}

class Manager extends Employee {

}

public class Test {
    public static void main(String[] args) {
        Coder c = new Coder();
        c.name = "張三";
        c.age = 23;
        c.salary = 12000;

        System.out.println(c.name + "---" + c.age + "---" + c.salary);

        Manager m = new Manager();
        m.name = "李四";
        m.age = 24;
        m.salary = 18000;

        System.out.println(m.name + "---" + m.age + "---" + m.salary);
    }
}
```

  通過上面的代碼, 同學們可以發(fā)現(xiàn), Coder 和 Manager 中, 明明什么都沒寫, 但是卻能夠訪問到父類 Employee 中的屬性了。

  所以, 重復的代碼就不需要重復編寫, 代碼的復用性就提高了 ~

  但是, 我相信有些同學可能會想

  Q : 按照標準 JavaBean 來說, 成員變量不應該私有化嗎 ? 那如果父類中的成員私有了, 子類豈不是用不到這些數(shù)據(jù)了。

  A : 能想到這個問題非常好, 說明之前的知識掌握的很扎實, 但是繼續(xù)想, 我們私有成員變量之后, 會提供對應的 set 和 get 方法吧, 這些set \ get 可都是公共的, 子類是可以繼承到直接用的。

  示例代碼 :

package com.itheima.myextends;


class Employee {
    private String name;
    private int age;
    private double salary;


    public Employee() {
    }

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }


    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 double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

}

class Coder extends Employee {

}

class Manager extends Employee {

}

public class Test {
    public static void main(String[] args) {
        Coder c = new Coder();
        c.setName("張三");
        c.setAge(23);
        c.setSalary(12000);
       
        System.out.println(c.getName() + "---" + c.getAge() + "---" + c.getSalary());

        Manager m = new Manager();
        m.setName("李四");
        m.setAge(24);
        m.setSalary(18000);

        System.out.println(m.getName() + "---" + m.getAge() + "---" + m.getSalary());
    }
}

```

  2. 繼承的好處和弊端

  好處 :

   通過上面的代碼, 同學們感受到繼承的什么好處了啊 ? 是的, 非常明顯的發(fā)現(xiàn), 代碼的復用性提高了。

  除此之外呢, 繼承還可以提高代碼的維護性 , 這什么意思 ?

   假設, 我要在這款管理系統(tǒng)中, 加入一個 id 的屬性, 那使用繼承前, 我就需要在每一個類中, 挨個編寫, 但是現(xiàn)在有了繼承后, 我只需要在父類中編寫一個 id, 所有的子類, 是不是就都具備了這個屬性了 ? 是的 ! 這就提高了代碼的維護性了 !

1661413374358_4.jpg

  弊端 :

  繼續(xù)剛剛的思路, 同學們可能會想, 增加一個, 所有子類都有了, 那刪除一個, 豈不是所有子類都沒有了啊...

   這不就牽一發(fā)而動全身了嘛... 是的, 這就是繼承的弊端, 類的耦合性太強了。

1661413443658_5.jpg

  何時使用繼承 :

  聊到現(xiàn)在, 大家發(fā)現(xiàn)繼承有好處, 也有弊端, 那該如何設計, 才能讓利大于弊呢 ?

1661413457267_6.jpg

  正確示例 :

1661413469699_7.jpg

  錯誤示例 :

1661413482556_8.jpg

  3. 繼承中成員變量訪問特點

  思考:子父類中,如果出現(xiàn)了重名的成員變量,使用的時候會優(yōu)先使用??

1661413500129_9.jpg

  運行效果 :

1661413514561_10.jpg

  原因 :

1661413526149_11.jpg

  一定要使用父類的, 可以使用 super 關鍵字進行區(qū)分

  這里同學們可以先建立起一個使用思路

  this. 調用本類成員

  super.調用父類成員

public class Zi extends Fu {
    int num = 20;
   
    public void method(){
        System.out.println(super.num);
    }
}
```

1661413583010_11.jpg

  4. 繼承中成員方法訪問特點

  * 思考:子類繼承了父類之后,是否可以自己繼續(xù)定義方法?

  * 回答:當然可以, 子類繼承父類, 是可以在繼承到父類成員之后, 繼續(xù)加東西的

1661413605001_12.jpg

  * 問題 : 那子父類中, 要是出現(xiàn)了重名的成員, 邏輯卻不一樣呢 ?

public class Fu {
    public void method() {
       // 父類的方法
       System.out.println("父類method...");
    }
}

class Zi extends Fu {
    public void method() {
        System.out.println("子類method...");
    }
}
```

  * 運行結果 :

子類method...
```

  結論 :

  子父類中如果出現(xiàn)了重名的成員方法,優(yōu)先使用子類的方法 , 但這其實是子類方法對父類方法進行了重寫。

  方法重寫 Override :

   先跟大家聊聊什么是方法重寫,在繼承體系中,子類可以繼承到父類的方法, 但有時子類并不想原封不動地繼承父類的方法,而是想作一定的修改, 這就需要采用方法的重寫,方法重寫又稱方法覆蓋。

  

1661413686848_13.jpg

class Fu {
    public void catch(){
        System.out.println("使用弓箭捉羊...");
    }
}

class Zi extends Fu {
    @Override 
    public void catch(){
        System.out.println("使用98Kar狙擊槍捉羊...");
    }
}
```

  注意事項 :

1661413721972_14.jpg

  > 解釋 :

  >

  > 父類中私有的方法, 子類訪問不到, 就更不能重寫了

  >

  > 子類重寫父類方法, 可以理解為是對功能進行增強, 那如果允許把訪問權限降低, 那豈不是功能越做越差了嘛

  Java 中繼承的特點 :

   了解了 Java 中方法重寫后, 我們再來說下 Java 中繼承的特點

  * 特點 : Java只支持單繼承,不支持多繼承,但支持多層繼承

  * 其實就是說, 一個類, 最多只能有一個父類, 不能同時有多個父類

  class A {}
 
  class B {}
 
  class C extends A , B {}   // 編譯出錯
  ```

  * 為什么 ? 因為**擔心邏輯沖突**

1661413834734_15.jpg

  
       * 問題 : 那多層繼承呢 ?

  5. 繼承中構造方法的訪問特點

  * 思考問題 : 父類的構造方法, 子類能不能繼承到呢 ?

  * 回答 : 不允許, 因為構造方法要求, 方法名和類名必須相同, 如果子類繼承到父類的構造方法, 那方法名和類名就不一致了。

  * 結論 : 子類不能繼承父類的構造方法, 將來子類的構造方法, 需要自己編寫。

public Fu {
    public Fu(){}
}

class Zi extends Fu {
    public Fu(){}   // 如果子類繼承到父類的構造方法,  那方法名和類名就不一致了
}
```

  繼承中構造方法的訪問流程

  * 閱讀代碼材料 :

public class Test {
    public static void main(String[] args) {
        Zi z1 = new Zi();
        Zi z2 = new Zi(10);
    }
}

class Fu {
    public Fu() {
        System.out.println("Fu類空參構造方法");
    }

    public Fu(int num) {
        System.out.println("Fu類帶參數(shù)構造方法");
    }
}

class Zi extends Fu {

    public Zi() {
        System.out.println("Zi類空參數(shù)構造方法");
    }

    public Zi(int num) {
        System.out.println("Zi類帶參數(shù)構造方法");
    }

}
```

  * 運行效果 :

Fu類空參構造方法
Zi類空參數(shù)構造方法
Fu類空參構造方法
Zi類帶參數(shù)構造方法
```

  通過打印效果同學們可以看出, 我們無論通過子類的哪一個構造方法創(chuàng)建對象, 都會執(zhí)行到 Fu類的空參構造方法。

  為什么 ? 因為在子類的構造方法, 第一行代碼, 系統(tǒng)會默認幫我們加入 super ();

  通過super() 在訪問父類的構造方法 , 現(xiàn)在我們手動把 super(); 加上, 同學們再閱讀下試試吧~

public class Test {
    public static void main(String[] args) {
        Zi z1 = new Zi();
        Zi z2 = new Zi(10);
    }
}

class Fu {
    public Fu() {
        System.out.println("Fu類空參構造方法");
    }

    public Fu(int num) {
        System.out.println("Fu類帶參數(shù)構造方法");
    }
}

class Zi extends Fu {

    public Zi() {
        super();
        System.out.println("Zi類空參數(shù)構造方法");
    }

    public Zi(int num) {
        super();
        System.out.println("Zi類帶參數(shù)構造方法");
    }

}
```

  okk, 看懂了執(zhí)行流程之后, 新的問題來了, 我們知道學習這個細節(jié), 對代碼有什么幫助呢 ?

  答案是 : 子類將來可以把一部分數(shù)據(jù), 交給父類初始化了

  我們來看一段代碼

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = 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;
    }
}
```

```java
public class Student extends Person {
    private double score;

    public Student() {
    }

    public Student(String name, int age, double score) {
        super(name, age);
        this.score = score;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    public void study() {
        System.out.println(
                "姓名為" + super.getName()
                + ", 年齡為" + super.getAge()
                + ", 成績?yōu)?quot; + score + "分的學生, 正在學習Java");
    }
}

```

```java
public class ExtendsTest2 {
    public static void main(String[] args) {
        Student stu = new Student("李四", 24, 100);
        stu.study();
    }
}
```

  在上述代碼中, 我們可以發(fā)現(xiàn), main方法中 , 創(chuàng)建學生對象, 我們給了 3個參數(shù), 先執(zhí)行子類的帶參數(shù)構造方法。

  其中前兩個數(shù)據(jù), 我們通過super, 交給了父類初始化, 最后一個成績數(shù)據(jù), 父類沒有, 子類自己完成了初始化。

public Student(String name, int age, double score) {    // "李四", 24, 100
        super(name, age);   // "李四", 24 交給父類
        this.score = score; // 100 子類自己初始化
}
```

  配合一張內(nèi)存圖 , 大家再看一下

1661414109228_16.jpg

  這里我們可以發(fā)現(xiàn), 堆內(nèi)存的對象中, 會存在一個 super 區(qū)域, 專門存放父類的成員。

  所以, 前兩個數(shù)據(jù), 父類中有, 就交給父類初始化, 最后一個數(shù)據(jù), 父類沒有, 子類就自己完成初始化。

  6. this 和 super

  上述文章中, 我們其實關于 this 和 super 做了很多使用了, 接下來我們來梳理下

  this:代表本類對象的引用

  super:代表父類存儲空間的標識(可以理解為父類對象引用)

1661414183031_17.jpg

  好啦, 本次關于繼承的技術, 就跟大家聊到這里了

  我們下次再見 ~

  掰掰~ (*^▽^*)

分享到:
在線咨詢 我要報名
和我們在線交談!