1 概述
1.1 引入
假如我們要定義如下類:
學生類,老師類和工人類,分析如下。
- 學生類
屬性:姓名,年齡
行為:吃飯,睡覺 - 老師類
屬性:姓名,年齡,薪水
行為:吃飯,睡覺,教書 - 班主任
屬性:姓名,年齡,薪水
行為:吃飯,睡覺,管理
如果我們定義了這三個類去開發一個系統,那麼這三個類中就存在大量重複的資訊(屬性:姓名,年齡。行為:吃飯,睡覺)。這樣就導致了相同程式碼大量重複,程式碼顯得很臃腫和冗餘,那麼如何解決呢?
假如多個類中存在相同屬性和行為時,我們可以將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為,只要繼承那一個類即可。如圖所示:
其中,多個類可以稱為子類,單獨被繼承的那一個類稱為父類、超類(superclass)或者基類。
1.2 繼承的含義
繼承描述的是事物之間的所屬關係,這種關係是:is-a
的關係。例如,兔子屬於食草動物,食草動物屬於動物。可見,父類更通用,子類更具體。我們透過繼承,可以使多種事物之間形成一種關係體系。
繼承:就是子類繼承父類的屬性和行為,使得子類物件可以直接具有與父類相同的屬性、相同的行為。子類可以直接訪問父類中的非私有的屬性和行為。
1.3 繼承的好處
- 提高程式碼的複用性(減少程式碼冗餘,相同程式碼重複利用)。
- 使類與類之間產生了關係。
2 繼承的格式
透過 extends
關鍵字,可以宣告一個子類繼承另外一個父類,定義格式如下:
class 父類 {
...
}
class 子類 extends 父類 {
...
}
需要注意:Java是單繼承的,一個類只能繼承一個直接父類,跟現實世界很像,但是Java中的子類是更加強大的。
3 繼承案例
3.1 案例
請使用繼承定義以下類:
- 學生類
屬性:姓名,年齡
行為:吃飯,睡覺 - 老師類
屬性:姓名,年齡,薪水
行為:吃飯,睡覺,教書 - 班主任
屬性:姓名,年齡,薪水
行為:吃飯,睡覺,管理
3.2 案例圖解分析
老師類,學生類,還有班主任類,實際上都是屬於人類的,我們可以定義一個人類,把他們相同的屬性和行為都定義在人類中,然後繼承人類即可,子類特有的屬性和行為就定義在子類中了。
如下圖所示。
3.3 案例程式碼實現
1.父類Human類
public class Human {
// 合理隱藏
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;
}
}
2.子類Teacher類
public class Teacher extends Human {
// 工資
private double salary ;
// 特有方法
public void teach(){
System.out.println("老師在認真教技術!");
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
3.子類Student類
public class Student extends Human{
}
4.子類BanZhuren類
public class Teacher extends Human {
// 工資
private double salary ;
// 特有方法
public void admin(){
System.out.println("班主任強調紀律問題!");
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
}
5.測試類
public class Test {
public static void main(String[] args) {
Teacher dlei = new Teacher();
dlei.setName("播仔");
dlei.setAge("31");
dlei.setSalary(1000.99);
System.out.println(dlei.getName());
System.out.println(dlei.getAge());
System.out.println(dlei.getSalary());
dlei.teach();
BanZhuRen linTao = new BanZhuRen();
linTao.setName("靈濤");
linTao.setAge("28");
linTao.setSalary(1000.99);
System.out.println(linTao.getName());
System.out.println(linTao.getAge());
System.out.println(linTao.getSalary());
linTao.admin();
Student xugan = new Student();
xugan.setName("播仔");
xugan.setAge("31");
//xugan.setSalary(1000.99); // xugan沒有薪水屬性,報錯!
System.out.println(xugan.getName());
System.out.println(xugan.getAge());
}
}
3.4 小結
1.繼承實際上是子類相同的屬性和行為可以定義在父類中,子類特有的屬性和行為由自己定義,這樣就實現了相同屬性和行為的重複利用,從而提高了程式碼複用。
2.子類繼承父類,就可以直接得到父類的成員變數和方法。是否可以繼承所有成分呢?請看下節!
4 子類不能繼承的內容
4.1 引入
並不是父類的所有內容都可以給子類繼承的:
子類不能繼承父類的構造方法。
值得注意的是子類可以繼承父類的私有成員(成員變數,方法),只是子類無法直接訪問而已,可以透過getter/setter方法訪問父類的private成員變數。
4.1 演示程式碼
public class Demo03 {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println(z.num1);
// System.out.println(z.num2); // 私有的子類無法使用
// 透過getter/setter方法訪問父類的private成員變數
System.out.println(z.getNum2());
z.show1();
// z.show2(); // 私有的子類無法使用
}
}
class Fu {
public int num1 = 10;
private int num2 = 20;
public void show1() {
System.out.println("show1");
}
private void show2() {
System.out.println("show2");
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
}
class Zi extends Fu {
}
5 繼承後的特點—成員變數
當類之間產生了繼承關係後,其中各類中的成員變數,又產生了哪些影響呢?
5.1 成員變數不重名
如果子類父類中出現不重名的成員變數,這時的訪問是沒有影響的。程式碼如下:
class Fu {
// Fu中的成員變數
int num = 5;
}
class Zi extends Fu {
// Zi中的成員變數
int num2 = 6;
// Zi中的成員方法
public void show() {
// 訪問父類中的num
System.out.println("Fu num="+num); // 繼承而來,所以直接訪問。
// 訪問子類中的num2
System.out.println("Zi num2="+num2);
}
}
class Demo04 {
public static void main(String[] args) {
// 建立子類物件
Zi z = new Zi();
// 呼叫子類中的show方法
z.show();
}
}
演示結果:
Fu num = 5
Zi num2 = 6
5.2 成員變數重名
如果子類父類中出現重名的成員變數,這時的訪問是有影響的。程式碼如下:
class Fu1 {
// Fu中的成員變數。
int num = 5;
}
class Zi1 extends Fu1 {
// Zi中的成員變數
int num = 6;
public void show() {
// 訪問父類中的num
System.out.println("Fu num=" + num);
// 訪問子類中的num
System.out.println("Zi num=" + num);
}
}
class Demo04 {
public static void main(String[] args) {
// 建立子類物件
Zi1 z = new Zi1();
// 呼叫子類中的show方法
z1.show();
}
}
演示結果:
Fu num = 6
Zi num = 6
子父類中出現了同名的成員變數時,子類會優先訪問自己物件中的成員變數。如果此時想訪問父類成員變數如何解決呢?我們可以使用super關鍵字。
5.3 super訪問父類成員變數
子父類中出現了同名的成員變數時,在子類中需要訪問父類中非私有成員變數時,需要使用super
關鍵字,修飾父類成員變數,類似於之前學過的 this
。
需要注意的是:super代表的是父類物件的引用,this代表的是當前物件的引用。
使用格式:
super.父類成員變數名
子類方法需要修改,程式碼如下:
class Fu {
// Fu中的成員變數。
int num = 5;
}
class Zi extends Fu {
// Zi中的成員變數
int num = 6;
public void show() {
int num = 1;
// 訪問方法中的num
System.out.println("method num=" + num);
// 訪問子類中的num
System.out.println("Zi num=" + this.num);
// 訪問父類中的num
System.out.println("Fu num=" + super.num);
}
}
class Demo04 {
public static void main(String[] args) {
// 建立子類物件
Zi1 z = new Zi1();
// 呼叫子類中的show方法
z1.show();
}
}
演示結果:
method num=1
Zi num=6
Fu num=5
小貼士:Fu 類中的成員變數是非私有的,子類中可以直接訪問。若Fu 類中的成員變數私有了,子類是不能直接訪問的。通常編碼時,我們遵循封裝的原則,使用private修飾成員變數,那麼如何訪問父類的私有成員變數呢?對!可以在父類中提供公共的getXxx方法和setXxx方法。
6 繼承後的特點—成員方法
當類之間產生了關係,其中各類中的成員方法,又產生了哪些影響呢?
6.1 成員方法不重名
如果子類父類中出現不重名的成員方法,這時的呼叫是沒有影響的。物件呼叫方法時,會先在子類中查詢有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。程式碼如下:
class Fu {
public void show() {
System.out.println("Fu類中的show方法執行");
}
}
class Zi extends Fu {
public void show2() {
System.out.println("Zi類中的show2方法執行");
}
}
public class Demo05 {
public static void main(String[] args) {
Zi z = new Zi();
//子類中沒有show方法,但是可以找到父類方法去執行
z.show();
z.show2();
}
}
6.2 成員方法重名
如果子類父類中出現重名的成員方法,則建立子類物件呼叫該方法的時候,子類物件會優先呼叫自己的方法。
程式碼如下:
class Fu {
public void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
//子類重寫了父類的show方法
public void show() {
System.out.println("Zi show");
}
}
public class ExtendsDemo05{
public static void main(String[] args) {
Zi z = new Zi();
// 子類中有show方法,只執行重寫後的show方法
z.show(); // Zi show
}
}
7 方法重寫
7.1 概念
方法重寫 :子類中出現與父類一模一樣的方法時(返回值型別,方法名和引數列表都相同),會出現覆蓋效果,也稱為重寫或者複寫。宣告不變,重新實現。
7.2 使用場景與案例
發生在子父類之間的關係。
子類繼承了父類的方法,但是子類覺得父類的這方法不足以滿足自己的需求,子類重新寫了一個與父類同名的方法,以便覆蓋父類的該方 法。
例如:我們定義了一個動物類程式碼如下:
public class Animal {
public void run(){
System.out.println("動物跑的很快!");
}
public void cry(){
System.out.println("動物都可以叫~~~");
}
}
然後定義一個貓類,貓可能認為父類cry()方法不能滿足自己的需求
程式碼如下:
public class Cat extends Animal {
public void cry(){
System.out.println("我們一起學貓叫,喵喵喵!喵的非常好聽!");
}
}
public class Test {
public static void main(String[] args) {
// 建立子類物件
Cat ddm = new Cat();
// 呼叫父類繼承而來的方法
ddm.run();
// 呼叫子類重寫的方法
ddm.cry();
}
}
7.2 @Override重寫註解
- @Override:註解,重寫註解校驗!
- 這個註解標記的方法,就說明這個方法必須是重寫父類的方法,否則編譯階段報錯。
建議重寫都加上這個註解,一方面可以提高程式碼的可讀性,一方面可以防止重寫出錯!
加上後的子類程式碼形式如下:
public class Cat extends Animal { // 宣告不變,重新實現 // 方法名稱與父類全部一樣,只是方法體中的功能重寫寫了! @Override public void cry(){ System.out.println("我們一起學貓叫,喵喵喵!喵的非常好聽!"); } }
7.3 注意事項
- 方法重寫是發生在子父類之間的關係。
- 子類方法覆蓋父類方法,必須要保證許可權大於等於父類許可權。
- 子類方法覆蓋父類方法,返回值型別、函式名和引數列表都要一模一樣。
8 繼承後的特點—構造方法
8.1 引入
當類之間產生了關係,其中各類中的構造方法,又產生了哪些影響呢?
首先我們要回憶兩個事情,構造方法的定義格式和作用。
- 構造方法的名字是與類名一致的。所以子類是無法繼承父類構造方法的。
- 構造方法的作用是初始化物件成員變數資料的。所以子類的初始化過程中,必須先執行父類的初始化動作。子類的構造方法中預設有一個
super()
,表示呼叫父類的構造方法,父類成員變數初始化後,才可以給子類使用。(先有爸爸,才能有兒子)
繼承後子類構方法器特點:子類所有構造方法的第一行都會預設先呼叫父類的無參構造方法
8.2 案例演示
按如下需求定義類:
- 人類
成員變數: 姓名,年齡
成員方法: 吃飯 - 學生類
成員變數: 姓名,年齡,成績
成員方法: 吃飯
程式碼如下:
class Person {
private String name;
private int age;
public Person() {
System.out.println("父類無參");
}
// getter/setter省略
}
class Student extends Person {
private double score;
public Student() {
//super(); // 呼叫父類無參,預設就存在,可以不寫,必須再第一行
System.out.println("子類無參");
}
public Student(double score) {
//super(); // 呼叫父類無參,預設就存在,可以不寫,必須再第一行
this.score = score;
System.out.println("子類有參");
}
}
public class Demo07 {
public static void main(String[] args) {
Student s1 = new Student();
System.out.println("----------");
Student s2 = new Student(99.9);
}
}
輸出結果:
父類無參
子類無參
----------
父類無參
子類有參
8.3 小結
- 子類構造方法執行的時候,都會在第一行預設先呼叫父類無引數構造方法一次。
- 子類構造方法的第一行都隱含了一個super()去呼叫父類無引數構造方法,super()可以省略不寫。
9 super(...)和this(...)
9.1 引入
請看上節中的如下案例:
class Person {
private String name;
private int age;
public Person() {
System.out.println("父類無參");
}
// getter/setter省略
}
class Student extends Person {
private double score;
public Student() {
//super(); // 呼叫父類無參構造方法,預設就存在,可以不寫,必須再第一行
System.out.println("子類無參");
}
public Student(double score) {
//super(); // 呼叫父類無參構造方法,預設就存在,可以不寫,必須再第一行
this.score = score;
System.out.println("子類有參");
}
// getter/setter省略
}
public class Demo07 {
public static void main(String[] args) {
// 呼叫子類有引數構造方法
Student s2 = new Student(99.9);
System.out.println(s2.getScore()); // 99.9
System.out.println(s2.getName()); // 輸出 null
System.out.println(s2.getAge()); // 輸出 0
}
}
我們發現,子類有引數構造方法只是初始化了自己物件中的成員變數score,而父類中的成員變數name和age依然是沒有資料的,怎麼解決這個問題呢,我們可以藉助與super(...)去呼叫父類構造方法,以便初始化繼承自父類物件的name和age.
9.2 super和this的用法格式
super和this完整的用法如下,其中this,super訪問成員我們已經接觸過了。
this.成員變數 -- 本類的
super.成員變數 -- 父類的
this.成員方法名() -- 本類的
super.成員方法名() -- 父類的
接下來我們使用呼叫構造方法格式:
super(...) -- 呼叫父類的構造方法,根據引數匹配確認
this(...) -- 呼叫本類的其他構造方法,根據引數匹配確認
9.3 super(....)用法演示
程式碼如下:
class Person {
private String name ="鳳姐";
private int age = 20;
public Person() {
System.out.println("父類無參");
}
public Person(String name , int age){
this.name = name ;
this.age = age ;
}
// getter/setter省略
}
class Student extends Person {
private double score = 100;
public Student() {
//super(); // 呼叫父類無參構造方法,預設就存在,可以不寫,必須再第一行
System.out.println("子類無參");
}
public Student(String name , int age,double score) {
super(name ,age);// 呼叫父類有參構造方法Person(String name , int age)初始化name和age
this.score = score;
System.out.println("子類有參");
}
// getter/setter省略
}
public class Demo07 {
public static void main(String[] args) {
// 呼叫子類有引數構造方法
Student s2 = new Student("張三",20,99);
System.out.println(s2.getScore()); // 99
System.out.println(s2.getName()); // 輸出 張三
System.out.println(s2.getAge()); // 輸出 20
}
}
注意:
子類的每個構造方法中均有預設的super(),呼叫父類的空參構造。手動呼叫父類構造會覆蓋預設的super()。
super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。
super(..)是根據引數去確定呼叫父類哪個構造方法的。
9.4 super(...)案例圖解
父類空間優先於子類物件產生
在每次建立子類物件時,先初始化父類空間,再建立其子類物件本身。目的在於子類物件中包含了其對應的父類空間,便可以包含其父類的成員,如果父類成員非private修飾,則子類可以隨意使用父類成員。程式碼體現在子類的構造七呼叫時,一定先呼叫父類的構造方法。理解圖解如下:
9.5 this(...)用法演示
this(...)
- 預設是去找本類中的其他構造方法,根據引數來確定具體呼叫哪一個構造方法。
- 為了借用其他構造方法的功能。
package com.itheima._08this和super呼叫構造方法;
/**
* this(...):
* 預設是去找本類中的其他構造方法,根據引數來確定具體呼叫哪一個構造方法。
* 為了借用其他構造方法的功能。
*
*/
public class ThisDemo01 {
public static void main(String[] args) {
Student xuGan = new Student();
System.out.println(xuGan.getName()); // 輸出:徐幹
System.out.println(xuGan.getAge());// 輸出:21
System.out.println(xuGan.getSex());// 輸出: 男
}
}
class Student{
private String name ;
private int age ;
private char sex ;
public Student() {
// 很弱,我的兄弟很牛逼啊,我可以呼叫其他構造方法:Student(String name, int age, char sex)
this("徐幹",21,'男');
}
public Student(String name, int age, char sex) {
this.name = name ;
this.age = age ;
this.sex = sex ;
}
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 char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
9.6 小結
- 子類的每個構造方法中均有預設的super(),呼叫父類的空參構造。手動呼叫父類構造會覆蓋預設的super()。
- super() 和 this() 都必須是在構造方法的第一行,所以不能同時出現。
- super(..)和this(...)是根據引數去確定呼叫父類哪個構造方法的。
- super(..)可以呼叫父類構造方法初始化繼承自父類的成員變數的資料。
- this(..)可以呼叫本類中的其他構造方法。
10 繼承的特點
Java只支援單繼承,不支援多繼承。
// 一個類只能有一個父類,不可以有多個父類。 class A {} class B {} class C1 extends A {} // ok // class C2 extends A, B {} // error
一個類可以有多個子類。
// A可以有多個子類 class A {} class C1 extends A {} class C2 extends A {}
可以多層繼承。
class A {} class C1 extends A {} class D extends C1 {}
頂層父類是Object類。所有的類預設繼承Object,作為父類。