目錄
1.?多態(tài)的概念
2. 多態(tài)實現(xiàn)條件
3. 重寫
4. 向上轉(zhuǎn)型和向下轉(zhuǎn)型
4.1 向上轉(zhuǎn)型
4.2 向下轉(zhuǎn)型
5. 多態(tài)的優(yōu)缺點
6. 避免在構(gòu)造方法中調(diào)用重寫的方法
1.?多態(tài)的概念
多態(tài)的概念:通俗來說,就是多種形態(tài),具體點就是去完成某個行為,當(dāng)不同的對象去完成時會產(chǎn)生出不同的狀態(tài)。
?總的來說:同一件事情,發(fā)生在不同對象身上,就會產(chǎn)生不同的結(jié)果。
2. 多態(tài)實現(xiàn)條件
在java中要實現(xiàn)多態(tài),必須要滿足如下幾個條件,缺一不可:
1. 必須在繼承體系下
2. 子類必須要對父類中方法進行重寫
3. 通過父類的引用調(diào)用重寫的方法
多態(tài)體現(xiàn):在代碼運行時,當(dāng)傳遞不同類對象時,會調(diào)用對應(yīng)類中的方法。?
下面是一個多態(tài)的示例:
class Animal {
public void makeSound() {
System.out.println("動物發(fā)出聲音");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("貓發(fā)出喵喵的聲音");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("狗發(fā)出汪汪的聲音");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Cat();
Animal animal2 = new Dog();
animal1.makeSound(); // 輸出:貓發(fā)出喵喵的聲音
animal2.makeSound(); // 輸出:狗發(fā)出汪汪的聲音
}
}
3. 重寫
引入:
在閱讀下面代碼之前,建議先學(xué)習(xí)向上轉(zhuǎn)型。
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 + "吃飯");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog("小狗",2);
animal.eat();
}
}
?對于上面的代碼,我們可以輕松的看出程序?qū)蛴〉膬?nèi)容:
?但是,如果在Dog這個類中也有eat()這個方法,程序會輸出什么樣的結(jié)果呢:
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 + "吃飯");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
public void eat() {
System.out.println(name + "吃骨頭~~");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog("小狗", 2);
animal.eat();
}
}
這時我們發(fā)現(xiàn),程序輸出結(jié)果:
?此時的你是不是也在想,為什么輸出的不是父類的eat方法呢?這里就要講到重寫了:
重寫(override):也稱為覆蓋。重寫是子類對父類非靜態(tài)、非private修飾,非final修飾,非構(gòu)造方法等的實現(xiàn)過程進行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。 也就是說子類能夠根據(jù)需要實現(xiàn)父類的方法。
以上面代碼為例:
?此時通過父類的引用調(diào)用的是子類的eat()方法,我們把這個過程稱為動態(tài)綁定。
【方法重寫的規(guī)則】
- 子類在重寫父類的方法時,一般必須與父類方法原型一致: 返回值類型方法名 (參數(shù)列表) 要完全一致
- 被重寫的方法返回值類型可以不同,但是必須是具有父子關(guān)系的
- 訪問權(quán)限不能比父類中被重寫的方法的訪問權(quán)限更低。例如:如果父類方法被public修飾,則子類中重寫該方法就不能聲明為 protected.訪問權(quán)限:private < default < protected <public
- 父類被static、private修飾的方法、構(gòu)造方法都不能被重寫。
- 重寫的方法, 可以使用 @Override 注解來顯式指定. 有了這個注解能幫我們進行一些合法性校驗. 例如不小心將方法名字拼寫錯了 (比如寫成 aet), 那么此時編譯器就會發(fā)現(xiàn)父類中沒有 aet 方法, 就會編譯報錯, 提示無法構(gòu)成重寫.
對于第3種情況的代碼示意:
?文章來源地址http://www.zghlxwxcb.cn/news/detail-628885.html
?【重寫和重載的區(qū)別】
?即:方法重載是一個類的多態(tài)性表現(xiàn),而方法重寫是子類與父類的一種多態(tài)性表現(xiàn)。
?
【重寫的設(shè)計原則】
對于已經(jīng)投入使用的類,盡量不要進行修改。最好的方式是:重新定義一個新的類,來重復(fù)利用其中共性的內(nèi)容, 并且添加或者改動新的內(nèi)容。
例如:若干年前的手機,只能打電話,發(fā)短信,來電顯示只能顯示號碼,而今天的手機在來電顯示的時候,不僅僅可以顯示號碼,還可以顯示頭像,地區(qū)等。在這個過程當(dāng)中,我們不應(yīng)該在原來老的類上進行修改,因為原來的類,可能還在有用戶使用,正確做法是:新建一個新手機的類,對來電顯示這個方法重寫就好了,這樣就達到了我們當(dāng)今的需求了。
靜態(tài)綁定:也稱為前期綁定(早綁定),即在編譯時,根據(jù)用戶所傳遞實參類型就確定了具體調(diào)用那個方法。典型代表函數(shù)重載。
動態(tài)綁定:也稱為后期綁定(晚綁定),即在編譯時,不能確定方法的行為,需要等到程序運行時,才能夠確定具體調(diào)用那個類的方法。
4. 向上轉(zhuǎn)型和向下轉(zhuǎn)型
4.1 向上轉(zhuǎn)型
向上轉(zhuǎn)型:實際就是創(chuàng)建一個子類對象,將其當(dāng)成父類對象來使用。
語法格式:父類類型 對象名 = new 子類類型()
舉個“栗”子:
?animal是父類類型,但可以引用一個子類對象,因為是從小范圍向大范圍的轉(zhuǎn)換。
?【使用場景】
1. 直接賦值
直接賦值:子類對象賦值給父類對象
2. 方法傳參
方法傳參:形參為父類型引用,可以接收任意子類的對象
3. 方法返回
作返回值:返回任意子類對象
向上轉(zhuǎn)型的優(yōu)點:讓代碼實現(xiàn)更簡單靈活。
向上轉(zhuǎn)型的缺陷:不能調(diào)用到子類特有的方法。
4.2 向下轉(zhuǎn)型
將一個子類對象經(jīng)過向上轉(zhuǎn)型之后當(dāng)成父類方法使用,再無法調(diào)用子類的方法,但有時候可能需要調(diào)用子類特有的方法,此時:將父類引用再還原為子類對象即可,即向下轉(zhuǎn)換。
?
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 + "吃飯");
}
}
class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name + "吃魚~~~");
}
public void mew() {
System.out.println(name + "喵喵叫");
}
}
class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name + "吃骨頭~~~");
}
public void bark() {
System.out.println(name + "汪汪叫");
}
}
class Test {
public static void main(String[] args) {
Animal animal = new Dog("小狗", 1);
Dog dog = (Dog) animal; //向下轉(zhuǎn)型
dog.bark();
}
}
上面的代碼時可以成功編譯的:
但是,當(dāng)main方法寫成下面這樣時:
此時編譯器并沒有錯誤提示,但當(dāng)程序運行的時候就出錯了:
在上面的代碼中,存在一個類型轉(zhuǎn)換錯誤。在Test
類的main
方法中,嘗試將一個Animal
對象轉(zhuǎn)換為Cat
對象,這是不正確的,因為animal
實際上是一個Dog
對象,無法強制轉(zhuǎn)換為Cat
對象。
向下轉(zhuǎn)型用的比較少,而且不安全,萬一轉(zhuǎn)換失敗,運行時就會拋異常。Java中為了提高向下轉(zhuǎn)型的安全性,引入 了 instanceof ,如果該表達式為true,則可以安全轉(zhuǎn)換。
class Test {
public static void main(String[] args) {
Animal animal = new Dog("小狗", 1);
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.mew();
}else {
System.out.println("dog 無法轉(zhuǎn)化為 cat");
}
}
}
輸出結(jié)果:?
?
5. 多態(tài)的優(yōu)缺點
假設(shè)有如下代碼:
class Shape {
//屬性....
public void draw() {
System.out.println("畫圖形!");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("?");
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("?");
}
}
【使用多態(tài)的好處】
1. 能夠降低代碼的 "圈復(fù)雜度", 避免使用大量的 if - else
什么叫 "圈復(fù)雜度" ? 圈復(fù)雜度是一種描述一段代碼復(fù)雜程度的方式. 一段代碼如果平鋪直敘, 那么就比較簡單容易理解. 而如果有很多的條件分支或者循環(huán)語句, 就認(rèn)為理解起來更復(fù)雜. 因此我們可以簡單粗暴的計算一段代碼中條件語句和循環(huán)語句出現(xiàn)的個數(shù), 這個個數(shù)就稱為 "圈復(fù)雜度". 如果一個方法的圈復(fù)雜度太高, 就需要考慮重構(gòu). 不同公司對于代碼的圈復(fù)雜度的規(guī)范不一樣. 一般不會超過10。
例如我們現(xiàn)在需要打印的不是一個形狀了, 而是多個形狀. 如果不基于多態(tài), 實現(xiàn)代碼如下:
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), 則不必寫這么多的 if - else 分支語句, 代碼更簡單.
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)缺陷:代碼的運行效率降低。
1. 屬性沒有多態(tài)性
????????當(dāng)父類和子類都有同名屬性的時候,通過父類引用,只能引用父類自己的成員屬性
2. 構(gòu)造方法沒有多態(tài)性
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();
}
}
代碼分析:?
- 構(gòu)造 D 對象的同時, 會調(diào)用 B 的構(gòu)造方法.
- B 的構(gòu)造方法中調(diào)用了 func 方法, 此時會觸發(fā)動態(tài)綁定, 會調(diào)用到 D 中的 func
- 此時 D 對象自身還沒有構(gòu)造, 此時 num 處在未初始化的狀態(tài), 值為 0. 如果具備多態(tài)性,num的值應(yīng)該是1.
- 所以在構(gòu)造函數(shù)內(nèi),盡量避免使用實例方法,除了final和private方法。
?輸出結(jié)果:
結(jié)論: "用盡量簡單的方式使對象進入可工作狀態(tài)", 盡量不要在構(gòu)造器中調(diào)用方法(如果這個方法被子類重寫, 就會觸發(fā)動態(tài)綁定, 但是此時子類對象還沒構(gòu)造完成), 可能會出現(xiàn)一些隱藏的但是又極難發(fā)現(xiàn)的問題.
文章來源:http://www.zghlxwxcb.cn/news/detail-628885.html
?
到了這里,關(guān)于【JavaSE】面向?qū)ο缶幊趟枷胫鄳B(tài)(圖文詳解)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!