柚子快報激活碼778899分享:開發(fā)語言 Java之多態(tài)
柚子快報激活碼778899分享:開發(fā)語言 Java之多態(tài)
一、多態(tài)前言
1.為什么要使用多態(tài)
Java中使用多態(tài)的主要目的是提高代碼的可重用性和擴(kuò)展性,使得代碼更加靈活和易于維護(hù)。通過多態(tài),我們可以將不同的對象看做是同一種類型,從而使得我們可以使用同一種接口來操作這些對象,而不必關(guān)心具體的實現(xiàn)細(xì)節(jié)。
2.多態(tài)概念
當(dāng)父類的引用所指向的子類對象引用指向的對象不一樣時。調(diào)用重寫的方法,所表現(xiàn)出來的行為是不一樣的,我們把這種思想叫做多態(tài)。上面所說的可能大家會覺得有點抽象,看到后面就懂了。 多態(tài)的基礎(chǔ)是動態(tài)綁定,所以要了解多態(tài)前提我們還要了解動態(tài)綁定。 要想實現(xiàn)動態(tài)綁定,需要滿足以上幾個條件: 1.要發(fā)生向上轉(zhuǎn)型 2.要發(fā)生重寫 3.使用父類對象的引用去調(diào)用重寫方法 完成了這三部分,就會發(fā)生動態(tài)綁定,而在這里,出現(xiàn)了重寫以及向上轉(zhuǎn)型這些概念。所以我們得先了解它們才能去了解動態(tài)綁定。進(jìn)而了解多態(tài)。
二、重寫
1.重寫的概念
重寫
(override)
:也稱為覆蓋。將父類的方法重新在子類中使用。
返回值和形參都不能改變
。
即外殼不變,核心重寫!
重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。 也就是說子類能夠根據(jù)需要實現(xiàn)父類的方法。
方法重寫的規(guī)則:1.子類在重寫父類的方法時,必須與父類方法原型一致:即返回值、方法名、參數(shù)列表要完全一致 2.被重寫的方法的訪問修飾限定符在子類中要大于等于父類的。 3.父類中被static或private或final修飾的方法以及構(gòu)造方法都不能被重寫。? 4.在子類中重寫的方法, 可以使用 @Override 注解來顯式指定. 有了這個注解能幫我們進(jìn)行一些合法性校驗。
2.重寫的作用
對于已經(jīng)投入使用的類,盡量不要進(jìn)行修改。最好的方式是:重新定義一個新的類,來重復(fù)利用其中共性的內(nèi)容,并且添加或者改動新的內(nèi)容。
例如:若干年前的手機,只能打電話,發(fā)短信,來電顯示只能顯示號碼,而今天的手機在來電顯示的時候,不僅僅可以顯示號碼,還可以顯示頭像,地區(qū)等。在這個過程當(dāng)中,我們不應(yīng)該在原來老的類上進(jìn)行修改,因為原來的
類,可能還在有用戶使用
,正確做法是:
新建一個新手機的類,對來電顯示這個方法重寫就好了,這樣就達(dá)到了我
們當(dāng)今的需求了
。
三、向上轉(zhuǎn)型
向上轉(zhuǎn)型:實際就是創(chuàng)建一個子類對象,將其當(dāng)成父類對象來使用。 語法格式:父類類型 對象名
= new
子類類型
()
Animal animal
=
Dog
(
);
我們對以上代碼進(jìn)行實質(zhì)化分析,以上的代碼其實是省略化了,見以下代碼
Dog dog = new Dog(); ?Animal animal = dog;//該代碼發(fā)生了向上轉(zhuǎn)換,將Dog對象轉(zhuǎn)換為Animal類型
通過向上轉(zhuǎn)型后,就可以父類對象名來訪問子類的方法了。使用animal.eat();這語句來訪問
這個語句發(fā)生了動態(tài)綁定(在編譯過程中調(diào)用的其實是父類的eat,但是在運行時換為調(diào)用子類的eat了)故實現(xiàn)了
創(chuàng)建一個子類對象,將其當(dāng)成父類對象來使用。見以下代碼
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
?void sound() {
System.out.println("Dog barks");
}
?void fetch() {
System.out.println("Dog fetches a ball");
}
}
public class Main {
public static void main(String[] args) {
Dog dog = new Dog(); // 創(chuàng)建Dog對象
?Animal animal = dog; // 向上轉(zhuǎn)型,將Dog對象轉(zhuǎn)換為Animal類型
?animal.sound();//調(diào)用子類的覆蓋方法
?// animal.fetch(); // 編譯錯誤,因為Animal類中沒有fetch方法
}
}
// 輸出: Dog barks
通過以上代碼發(fā)現(xiàn)一個問題,不能調(diào)用到子類特有的方法(因為編譯時調(diào)用的是父類的方法),我們可以通過向下轉(zhuǎn)型來調(diào)用到子類特有的方法(后面介紹)靜態(tài)綁定:也稱為前期綁定(早綁定),即在編譯時,根據(jù)用戶所傳遞實參類型就確定了具體調(diào)用那個方法。典型代表函數(shù)重載。 動態(tài)綁定:也稱為后期綁定(晚綁定),即在編譯時,不能確定方法的行為,需要等到程序運行時,才能夠確定具體調(diào)用那個類的方法。當(dāng)發(fā)生重寫時,通過父類調(diào)用該方法時會發(fā)生動態(tài)綁定。
【向上轉(zhuǎn)型
使用場景
】
1.
直接賦值
2.
方法傳參
3.
方法返回
見以下代碼
public class TestAnimal {
// 2. 方法傳參:形參為父類型引用,可以接收任意子類的對象
public static void eatFood(Animal a){ //因為主方法的原因使用靜態(tài)方法
a.eat();? //方法傳參向上轉(zhuǎn)型
}
// 3. 作返回值:返回任意子類對象的實例
public static Animal buyAnimal(String var){
return new Dog();
}
public static void main() {
Animal cat = new Cat("元寶",2); // 1. 直接賦值:子類對象賦值給父類對象
Dog dog = new Dog("小七", 1);
animal.eat();? //直接賦值向上轉(zhuǎn)型
eatFood(cat);? //兩種傳參方式都可
eatFood(dog);?
Animal animal = buyAnimal();??
animal.eat();//方法返回向上轉(zhuǎn)型
}
}
向上轉(zhuǎn)型的優(yōu)點:讓代碼實現(xiàn)更簡單靈活。
向上轉(zhuǎn)型的缺陷:不能調(diào)用到子類特有的方法。
四、多態(tài)的實現(xiàn)
多態(tài)具體點就是去完成某個行為時,當(dāng)不同的對象去完成同一件事時(調(diào)用eat方法)會產(chǎn)生出不同的狀態(tài)。代碼如下:
class Animal {
????public void eat(){
????????System.out.println( "吃飯");
????}
}
?class Cat extends Animal{
????@Override //注解
????public void eat(){
????????System.out.println("吃魚~~~");
????}
}
?class Dog extends Animal {
????@Override
????public void eat(){
????????System.out.println("吃骨頭~~~");
????}
}
public class TestAnimal {
????public static void eat(Animal a){
????????a.eat(); //兩次調(diào)用該方法,但是結(jié)果卻不一樣
????}
????public static void main(String[] args) {
????????Cat cat = new Cat();
????????Dog dog = new Dog();
????????eat(cat);
????????eat(dog);
????}
}
//輸出結(jié)果
吃魚~~~ 吃骨頭~~~
此時在上述代碼中當(dāng)父類的引用所指向的子類對象引用指向的對象不一樣時。調(diào)用重寫的方法(eat),所表現(xiàn)出來的行為是不一樣的(輸出結(jié)果不一樣),我們把它叫做多態(tài)。
五、向下轉(zhuǎn)型
將一個子類對象經(jīng)過向上轉(zhuǎn)型之后當(dāng)成父類方法使用,再無法調(diào)用子類的方法,但有時候可能需要調(diào)用子類特有的方法,此時可以實例化子類,然后調(diào)用子類方法即可。我們其實還可以將父類引用再還原為子類對象即可,即
向下轉(zhuǎn)型
。 語法格式:子類類型 對象名
= (強制轉(zhuǎn)換)父類對象名
?Dog myDog = (Dog) animal;
那么以上代碼為什么要強制類型轉(zhuǎn)換呢?向上轉(zhuǎn)型可以不用,因為是從小范圍向大范圍的轉(zhuǎn)換。(可以類比整型里面的強制轉(zhuǎn)換),我們現(xiàn)在提出一個問題:什么時候都可以向下轉(zhuǎn)型嗎? 答案是不,在Java中,向下轉(zhuǎn)型(將父類引用轉(zhuǎn)換為子類引用)一般需要先進(jìn)行向上轉(zhuǎn)型 見以下代碼
class Animal {
????void sound() {
????????System.out.println("Animal的sound");
????}
????void sun() {
????????System.out.println("Animal特有的sun");
????}
}
class Dog extends Animal {
????void sound() {
????????System.out.println("Dog的sound");
????}
????void fetch() {
????????System.out.println("Dog特有的fetches ");
????}
}
public class Mainn {
????public static void main(String[] args) {
????????Animal animal = new Dog(); // 向上轉(zhuǎn)型
????????Dog myDog = (Dog) animal; // 向下轉(zhuǎn)型
????????myDog.sound(); // 輸出: Dog barks,調(diào)用子類的覆蓋方法
????????myDog.fetch(); // 輸出: Dog fetches a ball,調(diào)用子類特有的方法
????????myDog.sound(); // 輸出: Dog barks,調(diào)用子類的覆蓋方法
????}
}
如果上面的代碼沒有?Animal animal = new Dog();,向下轉(zhuǎn)型將報錯,同時注意必須確保父類引用所指向的對象確實是子類的實例。如果父類引用所指向的對象不是子類的實例,那么即使進(jìn)行了向上轉(zhuǎn)型,向下轉(zhuǎn)型也是不安全的:見以下代碼
class Parent {} class Child extends Parent {} class AnotherChild extends Parent {}
public class Main { public static void main(String[] args) { Parent parent = new AnotherChild(); // 向上轉(zhuǎn)型 ?// 這里如果嘗試向下轉(zhuǎn)型為Child,編譯器將會報錯 ?// Child child = (Child) parent;//不安全的向下轉(zhuǎn)型 } }
//為了演示方便,這個代碼是不完整的
因此,向下轉(zhuǎn)型之前,你需要確保父類引用所指向的對象確實是你要轉(zhuǎn)型的子類的實例。這通常通過instanceof操作符來檢查:用來判斷parent是否為Child的實例,若是,返回true,否則返回false
if (parent instanceof Child) { ? ? Child child = (Child) parent; // 安全的向下轉(zhuǎn)型} else { ? ?......? ? ? ? ? ? ? ? ? ? ? ? ?// 不能轉(zhuǎn)換為Child }
我們最后思考一個問題:向上轉(zhuǎn)型的缺陷是不能調(diào)用到子類特有的方法,那么向下轉(zhuǎn)型可以調(diào)用父類特有的方法嗎?是可以的,同時向下轉(zhuǎn)型后不會影響向上轉(zhuǎn)型的操作。見以下代碼
class Animal {
????void sound() {
????????System.out.println("Animal的sound");
????}
????void sun() {
????????System.out.println("Animal特有的sun");
????}
}
class Dog extends Animal {
????void sound() {
????????System.out.println("Dog的sound");
????}
????void fetch() {
????????System.out.println("Dog特有的fetches ");
????}
}
public class Mainn {
????public static void main(String[] args) {
????????Animal animal = new Dog(); // 向上轉(zhuǎn)型
????????Dog myDog = (Dog) animal; // 向下轉(zhuǎn)型
????????myDog.sound(); // 輸出: Dog barks,調(diào)用子類的覆蓋方法
????????myDog.fetch(); // 輸出: Dog fetches a ball,調(diào)用子類特有的方法
????
????????myDog.sound(); // 輸出: Dog barks,調(diào)用子類的覆蓋方法
????????myDog.sun(); ??//觀察到向下轉(zhuǎn)型過程中可以調(diào)用父類的特有的方法
????????animal.sun();? ?//觀察到向下轉(zhuǎn)型后不會影響向上轉(zhuǎn)型的操作
????????animal.sound();
????}
}
//輸出結(jié)果
Dog的sound Dog特有的fetches? Dog的sound Animal特有的sun Animal特有的sun Dog的sound
六、多態(tài)的優(yōu)缺點
如我們現(xiàn)在需要打印的不是一個形狀了
,
而是多個形狀
.
如果不基于多態(tài)
,
實現(xiàn)代碼如下
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("?");
????}
}
public class Mainn {
????public static void main(String[] args) {
????????????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();
????????????}
????????}
????}
}
以上代碼使用了大量的 if - else,增加了代碼的
"
圈復(fù)雜度",
什么叫
"
圈復(fù)雜度
" ?
圈復(fù)雜度是一種描述一段代碼復(fù)雜程度的方式
.
一段代碼如果平鋪直敘
,
那么就比較簡單容易理解
.
而如
果有很多的條件分支或者循環(huán)語句
,
就認(rèn)為理解起來更復(fù)雜
.
因此我們可以簡單粗暴的計算一段代碼中條件語句和循環(huán)語句出現(xiàn)的個數(shù)
,
這個個數(shù)就稱為
"
圈復(fù)雜度
".
如果一個方法的圈復(fù)雜度太高
,
就需要考慮重構(gòu)
.
不同公司對于代碼的圈復(fù)雜度的規(guī)范不一樣
.
一般不會超過
10
如果使用使用多態(tài)
,
則不必寫這么多的
if - else
分支語句
,
代碼更簡單
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("?");
????}
}
public class Mainn {
????public static void main(String[] args) {
????????????Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
????????????????????new Rect(), new Flower()};
????????????for (Shape shape : shapes) {
????????????????shape.draw();
????????????}
????????}
????????????}
如果要新增一種新的形狀
,
使用多態(tài)的方式代碼改動成本也比較低
.見以下代碼
//公共部分
class Triangle extends Sjx?{
@Override
public void draw() {
System.out.println("△");
}
}
//If lese 改動方式
Sjx sjx=new Sjx();
????????????String[] shapes = {"cycle", "rect", "cycle", "rect", "flower", "sjx"};
????????????for (String shape : shapes) {
????????????????if (shape.equals("cycle")) {
????????????????????cycle.draw();
????????????????} else if (shape.equals("rect")) {
????????????????????rect.draw();
????????????????} else if (shape.equals("flower")) {
????????????????????flower.draw();
????????????????}else if(shape.equals("sjx"){
????????????????????????sjx.draw();
????????????????????}
????????????}
????????}
}
//多態(tài)改動方式
public class Mainn {
????public static void main(String[] args) {
????????Shape[] shapes = {new Cycle(), new Rect(), new Cycle(),
????????????????new Rect(), new Flower(),new Sjx()};
????????for (Shape shape : shapes) {
????????????shape.draw();
????????????????????}
????????????}
????????}
對于類的調(diào)用者來說
(drawShapes
方法
),
只要創(chuàng)建一個新類的實例就可以了
,
改動成本很低
.
而對于不用多態(tài)的情況
,
就要把
drawShapes
中的
if - else
進(jìn)行一定的修改
,
改動成本更高
.
多態(tài)缺陷:
1.
屬性沒有多態(tài)性
當(dāng)父類和子類都有同名屬性的時候,通過父類引用,只能引用父類自己的成員屬性
2.
構(gòu)造方法沒有多態(tài)性
見如下代碼
~
七、避免在構(gòu)造方法中調(diào)用重寫的方法
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 //
此時子類對象還沒構(gòu)造完成,故num的值為0
結(jié)論:盡量不要在構(gòu)造器中調(diào)用方法
(
如果這個方法被子類重寫
,
就會觸發(fā)動態(tài)綁定,
但是此時子類對象還沒構(gòu)造完成
),
可能會出現(xiàn)一些隱藏的但是又極難發(fā)現(xiàn)的問題
.
柚子快報激活碼778899分享:開發(fā)語言 Java之多態(tài)
參考閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。