Java之繼承和抽象類
繼承
繼承的實現
繼承通過extends實現 格式:class 子類 extends 父類 { } 舉例:class Dog extends Animal { } 繼承帶來的好處 繼承可以讓類與類之間產生關係,子父類關係,產生子父類後,子類則可以使用父類中非私有的成員。public class Fu {
public void show() { System.out.println("show方法被呼叫"); } } public class Zi extends Fu { public void method() { System.out.println("method方法被呼叫"); } } public class Demo { public static void main(String[] args) { //建立物件,呼叫方法 Fu f = new Fu(); f.show(); Zi z = new Zi(); z.method(); z.show(); } }
繼承的好處和弊端
繼承好處
提高了程式碼的複用性(多個類相同的成員可以放到同一個類中) 提高了程式碼的維護性(如果方法的程式碼需要修改,修改一處即可)繼承弊端
繼承讓類與類之間產生了關係,類的耦合性增強了,當父類發生變化時子類實現也不得不跟著變化,削弱了子類的獨立性
繼承的應用場景:
使用繼承,需要考慮類與類之間是否存在is..a的關係,不能盲目使用繼承is..a的關係:誰是誰的一種,例如:老師和學生是人的一種,那人就是父類,學生和老師就是子類
Java中繼承的特點
1. Java中類只支援單繼承,不支援多繼承 錯誤範例:class A extends B, C { } 2. Java中類支援多層繼承 多層繼承示例程式碼public class Granddad { public void drink() { System.out.println("爺爺愛喝酒"); } } public class Father extends Granddad { public void smoke() { System.out.println("爸爸愛抽菸"); } } public class Mother { public void dance() { System.out.println("媽媽愛跳舞"); } } public class Son extends Father { // 此時,Son類中就同時擁有drink方法以及smoke方法 }
繼承中的成員訪問特點
繼承中變數的訪問特點
在子類方法中訪問一個變數,採用的是就近原則。
- 子類區域性範圍找
- 子類成員範圍找
- 父類成員範圍找
- 如果都沒有就報錯(不考慮父親的父親…)
class Fu { int num = 10; } class Zi { int num = 20; public void show(){ int num = 30; System.out.println(num); } } public class Demo1 { public static void main(String[] args) { Zi z = new Zi(); z.show(); // 輸出show方法中的區域性變數30 } }
super
this&super關鍵字:
this:代表本類物件的引用
super:代表父類儲存空間的標識(可以理解為父類物件引用)
this和super的使用分別成員變數:
this.成員變數 - 訪問本類成員變數
super.成員變數 - 訪問父類成員變數
成員方法:
this.成員方法 - 訪問本類成員方法
super.成員方法 - 訪問父類成員方法
構造方法:
this(…) - 訪問本類構造方法
super(…) - 訪問父類構造方法
繼承中構造方法的訪問特點
注意:子類中所有的構造方法預設都會訪問父類中無參的構造方法
子類會繼承父類中的資料,可能還會使用父類的資料。所以,子類初始化之前,一定要先完成父類資料的初始化, 原因在於,每一個子類構造方法的第一條語句預設都是:super()
問題:如果父類中沒有無參構造方法,只有帶參構造方法,該怎麼辦呢?
1. 通過使用super關鍵字去顯示的呼叫父類的帶參構造方法 2. 子類通過this去呼叫本類的其他構造方法,本類其他構造方法再通過super去手動呼叫父類的帶參的構造方法 注意: this(…)super(…) 必須放在構造方法的第一行有效語句,並且二者不能共存繼承中成員方法的訪問特點
通過子類物件訪問一個方法 1. 子類成員範圍找 2. 父類成員範圍找 3. 如果都沒有就報錯(不考慮父親的父親…)方法重寫
1、方法重寫概念 子類出現了和父類中一模一樣的方法宣告(方法名一樣,引數列表也必須一樣) 2、方法重寫的應用場景 當子類需要父類的功能,而功能主體子類有自己特有內容時,可以重寫父類中的方法,這樣,即沿襲了父類的功能,又定義了子類特有的內容 3、Override註解 用來檢測當前的方法,是否是重寫的方法,起到【校驗】的作用方法重寫的注意事項
1. 私有方法不能被重寫(父類私有成員子類是不能繼承的) 2. 子類方法訪問許可權不能更低(public > 預設 > 私有) 3. 靜態方法不能被重寫,如果子類也有相同的方法,並不是重寫的父類的方法,是將父類的方法隱藏public class Fu { private void show() { System.out.println("Fu中show()方法被呼叫"); } void method() { System.out.println("Fu中method()方法被呼叫"); } } public class Zi extends Fu { /* 編譯【出錯】,子類不能重寫父類私有的方法*/ @Override private void show() { System.out.println("Zi中show()方法被呼叫"); } /* 編譯【出錯】,子類重寫父類方法的時候,訪問許可權需要大於等於父類 */ @Override private void method() { System.out.println("Zi中method()方法被呼叫"); } /* 編譯【通過】,子類重寫父類方法的時候,訪問許可權需要大於等於父類 */ @Override public void method() { System.out.println("Zi中method()方法被呼叫"); } }
許可權修飾符
super記憶體圖
物件在堆記憶體中,會單獨存在一塊super區域,用來存放父類的資料(父類的空參建構函式必須寫)
父類沒有空參構造
1.在子類通過super呼叫父類有參建構函式
public Child(int age){ super(age); this.age=age; System.out.println("Child parameter constructor called"); }
2.子類通過this呼叫本類其他構造方法,本類其他構造方法再通過呼叫super去手動呼叫父類有參構造方法
public Child(){ this(10); System.out.println("Child non_parameter constructor called"); } public Child(int age){ super(age); System.out.println("Child parameter constructor called"); }
注意this和super都必須放在構造方法第一行才能作為有效語句,並且二者不能共存
抽象類
抽象類的概述
當我們在做子類共性功能抽取時,有些方法在父類中並沒有具體的體現,這個時候就需要抽象類了!
在Java中,一個沒有方法體的方法應該定義為抽象方法,而類中如果有抽象方法,該類必須定義為抽象類!
抽象類的特點
抽象類和抽象方法必須使用 abstract 關鍵字修飾//抽象類的定義 public abstract class 類 名 {} //抽象方法的定義 public abstract void eat();抽象類中不一定有抽象方法,有抽象方法的類一定是抽象類 抽象類不能例項化 抽象類可以有構造方法 抽象類的子類,要麼重寫抽象類中的所有抽象方法,要麼是自己變成抽象類
抽象類的案例
案例需求 定義貓類(Cat)和狗類(Dog) 貓類成員方法:eat(貓吃魚)drink(喝水…) 狗類成員方法:eat(狗吃肉)drink(喝水…) 實現步驟 1. 貓類和狗類中存在共性內容,應向上抽取出一個動物類(Animal) 2. 父類Animal中,無法將 eat 方法具體實現描述清楚,所以定義為抽象方法 3. 抽象方法需要存活在抽象類中,將Animal定義為抽象類 4. 讓 Cat 和 Dog 分別繼承 Animal,重寫eat方法 5. 測試類中建立 Cat 和 Dog 物件,呼叫方法測試動物類
public abstract class Animal { public void drink(){ System.out.println("喝水"); } public Animal(){ } public abstract void eat(); }
貓類
public class Cat extends Animal { @Override public void eat() { System.out.println("貓吃魚"); } }
狗類
public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃肉"); } }
測試類
public static void main(String[] args) { Dog d = new Dog(); d.eat(); d.drink(); Cat c = new Cat(); c.drink(); c.eat(); //Animal a = new Animal(); //抽象類不能例項話物件 //a.eat(); }
模板設計模式
設計模式
設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性、程式的重用性。
模板設計模式
把抽象類整體就可以看做成一個模板,模板中不能決定的東西定義成抽象方法 讓使用模板的類(繼承抽象類的類)去重寫抽象方法實現需求
模板設計模式的優勢
模板已經定義了通用結構,使用者只需要關心自己需要實現的功能即可
示例程式碼
模板類
/* 作文模板類 */ public abstract class CompositionTemplate { public final void write(){ System.out.println("<<我的爸爸>>"); body(); System.out.println("啊~ 這就是我的爸爸"); } public abstract void body(); }
實現類A
public class Tom extends CompositionTemplate { @Override public void body() { System.out.println("那是一個秋天, 風兒那麼纏綿,記憶中, " + "那天爸爸騎車接我放學回家,我的腳卡在了自行車鏈當中, 爸爸蹬不動,他就 站起來蹬..."); } }
測試類
public class Test { public static void main(String[] args) { Tom t = new Tom(); t.write(); } }
final
fianl關鍵字的作用
final代表最終的意思,可以修飾成員方法,成員變數,類final修飾類、方法、變數的效果
fianl修飾類:該類不能被繼承(不能有子類,但是可以有父類)
public final class Parent{};
final修飾方法:該方法不能被重寫
public final void show{};
final修飾變數:表明該變數是一個常量,不能再次賦值。變數是基本型別,不能改變值
final int a=10; //正確 a=20; //錯誤
變數是引用型別,不能改變的是地址值,但地址裡面的內容是可以改變的
public static void main(String[] args){ final Student s = new Student(23); s = new Student(24); // 錯 誤 s.setAge(24); // 正 確 }
程式碼塊
程式碼塊概述
在Java中,使用 { } 括起來的程式碼被稱為程式碼塊程式碼塊分類
區域性程式碼塊 位置: 方法中定義 作用: 限定變數的生命週期,及早釋放,提高記憶體利用率public class Test { /* 區域性程式碼塊 位置:方法中定義 作用:限定變數的生命週期,及早釋放,提高記憶體利用率 */ public static void main(String[] args) { { int a = 10; System.out.println(a); } // System.out.println(a); } }
構造程式碼塊
位置: 類中方法外定義
特點: 每次構造方法執行的時,都會執行該程式碼塊中的程式碼,並且在構造方法執行前執行作用: 將多個構造方法中相同的程式碼,抽取到構造程式碼塊中,提高程式碼的複用性
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student(10);
}
class Student { { System.out.println("好好學習"); } public Student(){ System.out.println("空引數構造方法"); } public Student(int a){ System.out.println("帶引數構造方法"); } }
輸出:
好好學習
空引數構造方法
好好學習
帶引數構造方法
靜態程式碼塊
位置: 類中方法外定義
特點: 需要通過static關鍵字修飾,隨著類的載入而載入,並且只執行一次作用: 在類載入的時候做一些資料初始化的操作
public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person(10); } class Person { static { System.out.println("我是靜態程式碼塊, 我執行了"); } public Person(){ System.out.println("我是Person類的空引數構造方法"); } public Person(int a){ System.out.println("我是Person類的帶引數構造方法"); } }
輸出:
我是靜態程式碼塊, 我執行了
我是Person類的空引數構造方法
我是Person類的帶引數構造方法