Java超詳細講解繼承和多態(tài)的使用
繼承和多態(tài)
學(xué)習繼承、組合、多態(tài)
1、繼承
1.1、繼承概念
專門用來進行共性抽取,實現(xiàn)代碼復(fù)用。 它允許程序員在保持原有類特 性的基礎(chǔ)上進行擴展,增加新功能,這樣產(chǎn)生新的類,稱派生類。
1.2、繼承的語法
在Java中如果要表示類之間的繼承關(guān)系,需要借助extends關(guān)鍵字
class Animal{ public String name; public int age; public String sex; public void eat(){ System.out.println(this.name+"cat::eat()!"); } public void sleep(){ System.out.println(this.name+"睡覺!"); } } /** * 繼承 其實就是對共性的抽取 從而達到了代碼的復(fù)用 */ class Cat extends Animal{ public void mew(){ System.out.println(this.name+"cat::mew()!"); } } class Dog extends Animal{ public void bark(){ System.out.println("dog::bark()!"); } } public class TestDemo { public static void main(String[] args) { Cat cat = new Cat(); cat.name = "咪咪"; cat.sleep(); cat.eat(); cat.mew(); } }
注意:
1. 子類會將父類中的成員變量或者成員方法繼承到子類中了
2. 子類繼承父類之后,盡量新添加自己特有的成員,體現(xiàn)出與基類的不同,否則就沒有必要繼承了
1.3、父類成員的訪問
1.31、子類和父類不存在同名成員變量
public class Base { int a; int b; } public class Derived extends Base{ int c; public void method(){ a = 10; // 訪問從父類中繼承下來的a b = 20; // 訪問從父類中繼承下來的b c = 30; // 訪問子類自己的c } }
1.32、子類和父類成員變量同名
class Base { public int a = 1; public int b = 2; public void method(){ System.out.println("BASE::TEST()"); } } class Derived extends Base{ public int a = 3; public int d = 4; public void method2(){ System.out.println("Derived::method2()"); } public void test(){ method2(); method(); /*System.out.println(a);//如果重名類 優(yōu)先訪問自己的 System.out.println(super.a);//寫代碼的時候 讓這個代碼更已讀 System.out.println(this.b); System.out.println(d);*/ } } public class TestDemo2 { public static void main(String[] args) { Derived derived = new Derived(); derived.test(); } }
1、如果訪問的成員變量子類中有,訪問自己的成員變量。
2、如果訪問的成員變量子類中無,則訪問父類繼承下來的,如果父類也沒有定義,則編譯報錯。
3、如果訪問的成員變量與父類中成員變量同名,則優(yōu)先訪問自己的,即:子類將父類同名成員隱藏了。
4、成員變量訪問遵循就近原則,自己有優(yōu)先自己的,如果沒有則向父類中找。
比如:你和你父親各自有一款相同的手機,平時使用時你肯定優(yōu)先用自己的,如果自己手機沒電了,你才會考慮使用父親的。
1.33、成員方法名字不同
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(); // 編譯失敗,在整個繼承體系中沒有發(fā)現(xiàn)方法methodD() } }
總結(jié):成員方法沒有同名時,在子類方法中或者通過子類對象訪問方法時,則優(yōu)先訪問自己的,自己沒有時 再到父類中找,如果父類中也沒有則報錯。
1.34、 成員方法名字相同
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參數(shù),訪問子類中的methodA(int) methodB(); // 直接訪問,則永遠訪問到的都是子類中的methodB(),基類的無法訪問到 } }
通過子類對象訪問父類與子類中不同名方法時,優(yōu)先在子類中找,找到則訪問,否則在父類中找,找到 則訪問,否則編譯報錯。 通過派生類對象訪問父類與子類同名方法時,如果父類和子類同名方法的參數(shù)列表不同(重載),根據(jù)調(diào)用 方法適傳遞的參數(shù)選擇合適的方法訪問,如果沒有則報錯;如果父類和子類同名方法的原型一致,則只能訪問到子類的,父類的無法通過派生類對象直接訪問到。 問題:如果子類中存在與父類中相同的成員時,那如何在子類中訪問父類相同名稱的成員呢?
1.4、super關(guān)鍵字
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()構(gòu)成重載 public void methodA(int a) { System.out.println("Derived中的method()方法"); } // 與基類中methodB()構(gòu)成重寫 public void methodB(){ System.out.println("Derived中的methodB()方法"); } public void methodC(){ // 對于同名的成員變量,直接訪問時,訪問的都是子類的 a = 100; // 等價于: this.a = 100; b = 101; // 等價于: this.b = 101; // 注意:this是當前對象的引用 // 訪問父類的成員變量時,需要借助super關(guān)鍵字 // super是獲取到子類對象中從基類繼承下來的部分 super.a = 200; super.b = 201; // 父類和子類中構(gòu)成重載的方法,直接可以通過參數(shù)列表區(qū)分清訪問父類還是子類方法 methodA(); // 沒有傳參,訪問父類中的methodA() methodA(20); // 傳遞int參數(shù),訪問子類中的methodA(int) // 如果在子類中要訪問重寫的基類方法,則需要借助super關(guān)鍵字 methodB(); // 直接訪問,則永遠訪問到的都是子類中的methodB(),基類的無法訪問到 super.methodB(); // 訪問基類的methodB() } }
【注意事項】
1. 只能在非靜態(tài)方法中使用
2. 在子類方法中,訪問父類的成員變量和方法。
1.5、子類構(gòu)造方法
父子父子,先有父再有子,即:子類對象構(gòu)造時,需要先調(diào)用基類構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法。
public class Base { public Base(){ System.out.println("Base()"); } } public class Derived extends Base{ public Derived(){ super(); // 注意子類構(gòu)造方法中默認會調(diào)用基類的無參構(gòu)造方法:super(), // 用戶沒有寫時,編譯器會自動添加,而且super()必須是子類構(gòu)造方法中第一條語句, // 并且只能出現(xiàn)一次 System.out.println("Derived()"); } } public class Test { public static void main(String[] args) { Derived d = new Derived(); } }
結(jié)果打?。?br />Base()
Derived()
在子類構(gòu)造方法中,并沒有寫任何關(guān)于基類構(gòu)造的代碼,但是在構(gòu)造子類對象時,先執(zhí)行基類的構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法,因為:子類對象中成員是有兩部分組成的,基類繼承下來的以及子類新增加的部分 。父子父子 肯定是先有父再有子,所以在構(gòu)造子類對象時候 ,先要調(diào)用基類的構(gòu)造方法,將從基類繼承下來的成員構(gòu)造完整,然后再調(diào)用子類自己的構(gòu)造方法,將子類自己新增加的成員初始化完整 。
注意:
1. 若父類顯式定義無參或者默認的構(gòu)造方法,在子類構(gòu)造方法第一行默認有隱含的super()調(diào)用,即調(diào)用基類構(gòu)
造方法
2. 如果父類構(gòu)造方法是帶有參數(shù)的,此時編譯器不會再給子類生成默認的構(gòu)造方法,此時需要用戶為子類顯式
定義構(gòu)造方法,并在子類構(gòu)造方法中選擇合適的父類構(gòu)造方法調(diào)用,否則編譯失敗。
3. 在子類構(gòu)造方法中,super(…)調(diào)用父類構(gòu)造時,必須是子類構(gòu)造函數(shù)中第一條語句。
4. super(…)只能在子類構(gòu)造方法中出現(xiàn)一次,并且不能和this同時出現(xiàn)
1.6、super和this
【相同點】
1. 都是Java中的關(guān)鍵字
2. 只能在類的非靜態(tài)方法中使用,用來訪問非靜態(tài)成員方法和字段
3. 在構(gòu)造方法中調(diào)用時,必須是構(gòu)造方法中的第一條語句,并且不能同時存在
【不同點】
1. this是當前對象的引用,當前對象即調(diào)用實例方法的對象,super相當于是子類對象中從父類繼承下來部分成員的引用
2. 在非靜態(tài)成員方法中,this用來訪問本類的方法和屬性,super用來訪問父類繼承下來的方法和屬性
3. this是非靜態(tài)成員方法的一個隱藏參數(shù),super不是隱藏的參數(shù)
4. 成員方法中直接訪問本類成員時,編譯之后會將this還原,即本類非靜態(tài)成員都是通過this來訪問的;在子類中如果通過super訪問父類成員,編譯之后在字節(jié)碼層面super實際是不存在的
5. 在構(gòu)造方法中:this(…)用于調(diào)用本類構(gòu)造方法,super(…)用于調(diào)用父類構(gòu)造方法,兩種調(diào)用不能同時在構(gòu)造方法中出現(xiàn)
6. 構(gòu)造方法中一定會存在super(…)的調(diào)用,用戶沒有寫編譯器也會增加,但是this(…)用戶不寫則沒有
1.7、初始化
class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; System.out.println("構(gòu)造方法執(zhí)行"); } { System.out.println("實例代碼塊執(zhí)行"); } static { System.out.println("靜態(tài)代碼塊執(zhí)行"); } } public class TestDemo { public static void main(String[] args) { Person person1 = new Person("bit",10); System.out.println("============================"); Person person2 = new Person("gaobo",20); } }
執(zhí)行結(jié)果:
靜態(tài)代碼塊執(zhí)行
實例代碼塊執(zhí)行
構(gòu)造方法執(zhí)行
============================
實例代碼塊執(zhí)行
構(gòu)造方法執(zhí)行
注意:
1. 靜態(tài)代碼塊先執(zhí)行,并且只執(zhí)行一次,在類加載階段執(zhí)行
2. 當有對象創(chuàng)建時,才會執(zhí)行實例代碼塊,實例代碼塊執(zhí)行完成后,最后構(gòu)造方法執(zhí)行
繼承關(guān)系上的執(zhí)行順序:
1、父類靜態(tài)代碼塊優(yōu)先于子類靜態(tài)代碼塊執(zhí)行,且是最早執(zhí)行
2、父類實例代碼塊和父類構(gòu)造方法緊接著執(zhí)行
3、子類的實例代碼塊和子類構(gòu)造方法緊接著再執(zhí)行
4、第二次實例化子類對象時,父類和子類的靜態(tài)代碼塊都將不會再執(zhí)行
1.8、protected關(guān)鍵字
// 為了掩飾基類中不同訪問權(quán)限在子類中的可見性,為了簡單類B中就不設(shè)置成員方法了 // extend01包中 public class B { private int a; protected int b; public int c; int d; } // extend01包中 // 同一個包中的子類 public class D extends B{ public void method(){ // super.a = 10; // 編譯報錯,父類private成員在相同包子類中不可見 super.b = 20; // 父類中protected成員在相同包子類中可以直接訪問 super.c = 30; // 父類中public成員在相同包子類中可以直接訪問 super.d = 40; // 父類中默認訪問權(quán)限修飾的成員在相同包子類中可以直接訪問 } } // extend02包中 // 不同包中的子類 public class C extends B { public void method(){ // super.a = 10; // 編譯報錯,父類中private成員在不同包子類中不可見 super.b = 20; // 父類中protected修飾的成員在不同包子類中可以直接訪問 super.c = 30; // 父類中public修飾的成員在不同包子類中可以直接訪問 //super.d = 40; // 父類中默認訪問權(quán)限修飾的成員在不同包子類中不能直接訪問 } } // extend02包中 // 不同包中的類 public class TestC { public static void main(String[] args) { C c = new C(); c.method(); // System.out.println(c.a); // 編譯報錯,父類中private成員在不同包其他類中不可見 // System.out.println(c.b); // 父類中protected成員在不同包其他類中不能直接訪問 System.out.println(c.c); // 父類中public成員在不同包其他類中可以直接訪問 // System.out.println(c.d); // 父類中默認訪問權(quán)限修飾的成員在不同包其他類中不能直接訪問 } }
注意:父類中private成員變量隨時在子類中不能直接訪問,但是也繼承到子類中了
1.9、繼承方式
1.10、final關(guān)鍵字
final關(guān)鍵可以用來修飾變量、成員方法以及類。
1. 修飾變量或字段,表示常量(即不能修改)
2. 修飾類:表示此類不能被繼承
2、繼承與組合
組合表示對象之間的關(guān)系
// 輪胎類 class Tire{ // ... } // 發(fā)動機類 class Engine{ // ... } // 車載系統(tǒng)類 class VehicleSystem{ // ... } class Car{ private Tire tire; // 可以復(fù)用輪胎中的屬性和方法 private Engine engine; // 可以復(fù)用發(fā)動機中的屬性和方法 private VehicleSystem vs; // 可以復(fù)用車載系統(tǒng)中的屬性和方法 // ... } // 奔馳是汽車 class Benz extend Car{ // 將汽車中包含的:輪胎、發(fā)送機、車載系統(tǒng)全部繼承下來 }
3、多態(tài)
3.1、多態(tài)概念
通俗來說,就是多種形態(tài),具體點就是去完成某個行為,當不同的對象去完成時會產(chǎn)生出不同 的狀態(tài)。
3.2、多態(tài)實現(xiàn)條件
1. 必須在繼承體系下
2. 子類必須要對父類中方法進行重寫
3. 通過父類的引用調(diào)用重寫的方法
public class Animal { String name; int age; public Animal(String name, int age){ this.name = name; this.age = age; } public void eat(){ System.out.println(name + "吃飯"); } } public class Cat extends Animal{ public Cat(String name, int age){ super(name, age); } @Override public void eat(){ System.out.println(name+"吃魚~~~"); } } public class Dog extends Animal { public Dog(String name, int age){ super(name, age); } @Override public void eat(){ System.out.println(name+"吃骨頭~~~"); } } ///分割線// public class TestAnimal { // 編譯器在編譯代碼時,并不知道要調(diào)用Dog 還是 Cat 中eat的方法 // 等程序運行起來后,形參a引用的具體對象確定后,才知道調(diào)用那個方法 // 注意:此處的形參類型必須時父類類型才可以 public static void eat(Animal a){ a.eat(); } public static void main(String[] args) { Cat cat = new Cat("元寶",2); Dog dog = new Dog("小七", 1); eat(cat); eat(dog); } }
運行結(jié)果:
元寶吃魚~~~
元寶正在睡覺
小七吃骨頭~~~
小七正在睡覺
3.3、重寫
重寫(override):也稱為覆蓋。重寫是子類對父類非靜態(tài)、非private修飾,非final修飾,非構(gòu)造方法等的實現(xiàn)過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。 也就是說子類能夠根據(jù)需要實現(xiàn)父類的方法。
1. 子類在重寫父類的方法時,一般必須與父類方法原型一致:修飾符 返回值類型 方法名(參數(shù)列表) 要完全一致
2. JDK7以后,被重寫的方法返回值類型可以不同,但是必須是具有父子關(guān)系的
訪問權(quán)限不能比父類中被重寫的方法的訪問權(quán)限更低。例如:如果父類方法被public修飾,則子類中重寫該方法就不能聲明為 protected
3. 父類被static、private修飾的方法、構(gòu)造方法都不能被重寫。
子類和父類在同一個包中,那么子類可以重寫父類所有方法,除了聲明為 private 和 final 的方法。
4. 子類和父類不在同一個包中,那么子類只能夠重寫父類的聲明為 public 和 protected 的非 final 方法。
5. 重寫的方法, 可以使用 @Override 注解來顯式指定. 有了這個注解能幫我們進行一些合法性校驗. 例如不小心將方法名字拼寫錯了 (比如寫成 aet), 那么此時編譯器就會發(fā)現(xiàn)父類中沒有 aet 方法, 就會編譯報錯, 提示無法構(gòu)成重寫.
重載與重寫區(qū)別:
方法重載是一個類的多態(tài)性表現(xiàn),而方法重寫是子類與父類的一種多態(tài)性表現(xiàn)。
3.4、向上轉(zhuǎn)型和向下轉(zhuǎn)型
向上轉(zhuǎn)型:實際就是創(chuàng)建一個子類對象,將其當成父類對象來使用。
語法格式:父類類型 對象名 = new 子類類型()
【使用場景】
1. 直接賦值
2. 方法傳參
3. 方法返回
public class TestAnimal { // 2. 方法傳參:形參為父類型引用,可以接收任意子類的對象 public static void eatFood(Animal a){ a.eat(); } // 3. 作返回值:返回任意子類對象 public static Animal buyAnimal(String var){ if("狗" == var){ return new Dog("狗狗",1); }else if("貓" == var){ return new Cat("貓貓", 1); }else{ return null; } } public static void main(String[] args) { Animal cat = new Cat("元寶",2); // 1. 直接賦值:子類對象賦值給父類對象 Dog dog = new Dog("小七", 1); eatFood(cat); eatFood(dog); Animal animal = buyAnimal("狗"); animal.eat(); animal = buyAnimal("貓"); animal.eat(); } }
將一個子類對象經(jīng)過向上轉(zhuǎn)型之后當成父類方法使用,再無法調(diào)用子類的方法,但有時候可能需要調(diào)用子類特有的
方法,此時:將父類引用再還原為子類對象即可,即向下轉(zhuǎn)換。
public class TestAnimal { public static void main(String[] args) { Cat cat = new Cat("元寶",2); Dog dog = new Dog("小七", 1); // 向上轉(zhuǎn)型 Animal animal = cat; animal.eat(); animal = dog; animal.eat(); // 編譯失敗,編譯時編譯器將animal當成Animal對象處理 // 而Animal類中沒有bark方法,因此編譯失敗 // animal.bark(); // 向上轉(zhuǎn)型 // 程序可以通過編程,但運行時拋出異常---因為:animal實際指向的是狗 // 現(xiàn)在要強制還原為貓,無法正常還原,運行時拋出:ClassCastException cat = (Cat)animal; cat.mew(); // animal本來指向的就是狗,因此將animal還原為狗也是安全的 dog = (Dog)animal; dog.bark(); } }
向下轉(zhuǎn)型用的比較少,而且不安全,萬一轉(zhuǎn)換失敗,運行時就會拋異常。Java中為了提高向下轉(zhuǎn)型的安全性,引入
了 instanceof ,如果該表達式為true,則可以安全轉(zhuǎn)換。
public class TestAnimal { public static void main(String[] args) { Cat cat = new Cat("元寶",2); Dog dog = new Dog("小七", 1); // 向上轉(zhuǎn)型 Animal animal = cat; animal.eat(); animal = dog; animal.eat(); if(animal instanceof Cat){ cat = (Cat)animal; cat.mew(); } if(animal instanceof Dog){ dog = (Dog)animal; dog.bark(); } } }
3.5、多態(tài)優(yōu)缺點
【使用多態(tài)的好處】
1. 能夠降低代碼的 “圈復(fù)雜度”, 避免使用大量的 if - else
就曉得了什么叫 “圈復(fù)雜度” ?那么我們來對比一下
這是if-else分支語句:
public static void drawShapes() { Rect rect = new Rect(); Cycle cycle = new Cycle(); Flower flower = new Flower(); String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"}; for (String shape : shapes) { if (shape.equals("cycle")) { cycle.draw(); } else if (shape.equals("rect")) { rect.draw(); } else if (shape.equals("flower")) { flower.draw(); } } }
如果使用使用多態(tài):
public static void drawShapes() { // 我們創(chuàng)建了一個 Shape 對象的數(shù)組. Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()}; for (Shape shape : shapes) { shape.draw(); } }
2. 可擴展能力更強
如果要新增一種新的形狀, 使用多態(tài)的方式代碼改動成本也比較低.
class Triangle extends Shape { @Override public void draw() { System.out.println("△"); } }
對于類的調(diào)用者來說(drawShapes方法), 只要創(chuàng)建一個新類的實例就可以了, 改動成本很低. 而對于不用多態(tài)的情況, 就要把drawShapes 中的 if - else 進行一定的修改, 改動成本更高.
多態(tài)缺陷:代碼的運行效率降低。
3.6、避免在構(gòu)造方法中調(diào)用重寫的方法
我們創(chuàng)建兩個類, B 是父類, D 是子類. D 中重寫 func 方法. 并且在 B 的構(gòu)造方法中調(diào)用 func
class B { public B() { // do nothing func(); } public void func() { System.out.println("B.func()"); } } class D extends B { private int num = 1; @Override public void func() { System.out.println("D.func() " + num); } } public class Test { public static void main(String[] args) { D d = new D(); } }
執(zhí)行結(jié)果
D.func() 0
1. 構(gòu)造 D 對象的同時, 會調(diào)用 B 的構(gòu)造方法.
2. B 的構(gòu)造方法中調(diào)用了 func 方法, 此時會觸發(fā)動態(tài)綁定, 會調(diào)用到 D 中的 func此時 D 對象自身還沒有構(gòu)造, 此時 num 處在未初始化的狀態(tài), 值為 0.
結(jié)論: “用盡量簡單的方式使對象進入可工作狀態(tài)”, 盡量不要在構(gòu)造器中調(diào)用方法(如果這個方法被子類重寫, 就會觸 發(fā)動態(tài)綁定,
但是此時子類對象還沒構(gòu)造完成), 可能會出現(xiàn)一些隱藏的但是又極難發(fā)現(xiàn)的問題.
到此這篇關(guān)于Java超詳細講解繼承和多態(tài)的使用的文章就介紹到這了,更多相關(guān)Java繼承和多態(tài)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA導(dǎo)入外部項目報Error:java: 無效的目標發(fā)行版: 11的解決方法
這篇文章主要介紹了IDEA導(dǎo)入外部項目報Error:java: 無效的目標發(fā)行版: 11,本文給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09Java數(shù)據(jù)結(jié)構(gòu)超詳細分析二叉搜索樹
二叉搜索樹是以一棵二叉樹來組織的。每個節(jié)點是一個對象,包含的屬性有l(wèi)eft,right,p和key,其中,left指向該節(jié)點的左孩子,right指向該節(jié)點的右孩子,p指向該節(jié)點的父節(jié)點,key是它的值2022-03-03java EasyExcel實現(xiàn)動態(tài)列解析和存表
這篇文章主要為大家介紹了java EasyExcel實現(xiàn)動態(tài)列解析和存表示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06springboot 中異步任務(wù),定時任務(wù),郵件任務(wù)詳解
這篇文章主要介紹了springboot 與異步任務(wù),定時任務(wù),郵件任務(wù),本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09