一、繼承(extends)
1.1、繼承是什麼
繼承就是 Java 允許我們用 extends 關鍵字,讓一個類與另一個類建立起一種父子關係;
被繼承的類稱為父類(基類、超類),繼承父類的類都稱為子類(派生類) ,當子類繼承父類後,就可以直接使用父類公共的屬性和方法了
當子類繼承父類後,就可以直接使用父類公共的屬性和方法了
2.2、繼承的用處
來看看下面兩個類
- 可以看到,一個是學生類,一個是老師類;他們之間都有著相同的特徵:成員變數、方法;
- 這樣的話,重複程式碼又多了,是一種很不好的現象。
我們使用繼承,來最佳化程式碼
這樣減少程式碼冗餘,提高了程式碼的複用性,增強類的功能擴充套件性。
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();
}
}
控制檯輸出結果:
2.5、繼承的特點
子類可以繼承父類的屬性和行為,但是子類不能繼承父類的構造器;
java 是單繼承模式:一個類 只能繼承一個直接父類;
- 比如你,只能有一個親爸,不可能隔壁的老王叔、老楊叔 … 都是你的親爸吧? 那從倫理角度看,肯定是不支援
- 從 Java 的角度的角度看,多繼承會造成程式碼二義性,因此不支援
Java 不支援多繼承,但是支援多層繼承;
- 就是你 不可以繼承多個爸爸;但是你可以繼承你爸的,你爸可以繼承你爺爺的,多層繼承;
- 子類A 繼承父類B;父類B 可以繼承父類 C
Java 中所有的類都是 Object 類的子類。
- java 中所有的類,要麼是直接繼承了 Object 類,要麼預設繼承了 Object 類,要麼間接繼承了 Object 類;
二、繼承中成員訪問的特點
2.1、繼承中變數訪問的特點
在子類方法中訪問一個變數,採用的是就近原則。
- 子類區域性範圍找
- 子類成員範圍找
- 父類成員範圍找
- 如果都沒有就報錯(不考慮父親的父親…)
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類的無引數建構函式執行
在子類構造方法中,並沒有寫任何關於基類構造的程式碼,但是在構造子類物件時,先執行基類的構造方法,然後執行子類的構造方法,因為:**子類物件中成員是有兩部分組成的,基類繼承下來的以及子類新增加的部分 。父子父子肯定是先有父再有子,所以在構造子類物件時候 ,先要呼叫基類的構造方法,將從基類繼承下來的成員構造完整,然後再呼叫子類自己的構造方法,將子類自己新增加的成員初始化完整 **
問題:如果父類中沒有無參構造方法,只有帶參構造方法,該怎麼辦呢?
- 透過使用
super
關鍵字去顯示的呼叫父類的帶參構造方法 - 子類透過
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
加一個構造方法,給name
和age
都賦值。如下:
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(…)使用者不寫則沒有