天天看點

你了解多态嗎?在哪裡用過?

作者:尚矽谷教育

這是面試過程中常常被問到的,那麼今天我們就來從一個完全不懂得的狀态到一步步了解什麼是多态,來深入掌握多态的概念。

什麼是多态?

多态:一個父類和多個子類,即父類引用指向子類對象。在調用一個方法時,從源代碼上看,無法确定調用了哪個對象的方法(因為父子類有相同的方法),隻有在程式運作期間根據對象變量引用的實際對象才能确定此方法是哪個對象的,這種現象稱之為動态綁定。一句話概括就是:事物在運作過程中存在不同的狀态。

多态是怎樣實作的?

繼承:多個子類對同一方法的重寫

接口:實作接口并覆寫接口中的同一方法

多态實作的三個必要條件

  • 要有繼承關系;
  • 子類要重寫父類的方法;
  • 父類引用指向子類對象。
你了解多态嗎?在哪裡用過?

圖檔源于網絡,侵删

舉個例子

首先我們定義幾個類,一個父類Person,以及幾個子類Student、Teacher、Doctor。

package com.atguigu.polymorphic;

class Person {

public void print() {

System.out.println("父類需改進方法");

}

public void fun() {

System.out.println("父類中其他方法");

}

//行為

public void eat() {

System.out.println("人在吃飯!");

}

public static void look() {

System.out.println("人在看東西!");

}

public void move() {

System.out.println("人在移動!");

}

}

class Student extends Person {

@Override

public void print() {

System.out.println("Student類改進後的方法");

}

public void eat() {

System.out.println("學生在吃面包!");

}

public static void look() {

System.out.println("學生在看書!");

}

public void play() {

System.out.println("學生在進行課外活動!");

}

}

class Teacher extends Person {

@Override

public void print() {

System.out.println("Teacher類改進後的方法");

}

}

class Doctor extends Person {

@Override

public void print() {

System.out.println("Doctor類改進後的方法");

}

}

然後我們建立測試類

package com.atguigu.polymorphic;

public class Test_1008 {

public static void main(String[] args) {

System.out.println("=====重寫測試=====");

Person per = new Student();//向上轉型,子類對象給了父類的引用

per.print();//由于該方法被覆寫,那麼調用的是被覆寫後的方法

per.fun();//調用父類的fun方法

System.out.println("=====多态測試=====");

Person per1 = new Student();

Person per2 = new Teacher();

Person per3 = new Docter();

per1.print();//由于該方法被覆寫,那麼調用的是被覆寫後的方法

per2.print();

per3.print();

System.out.println("=====行為測試=====");

Person person = new Student();

person.eat();

person.look();

person.move();

}

}

測試結果如下:

=====重寫測試=====

Student類改進後的方法

父類中其他方法

=====多态測試=====

Student類改進後的方法

Teacher類改進後的方法

Doctor類改進後的方法

=====行為測試=====

學生在吃面包!

人在看東西!

人在移動!

在以上代碼中,Student、Teacher、Doctor類繼承了Person類,其中Student子類重寫(override)了父類的兩個成員方法eat(),look()。其中eat()是非靜态的,look()是靜态的(static)。并且在測試類Test_1008中 Person person= new Student();語句在堆記憶體中開辟了子類(Student)的對象,并把棧記憶體中的父類(Person)的引用指向了這個Student對象。進而滿足了Java多态的的必要三個前提。

由測試結果可以看出來

子類Student重寫了父類Person的非靜态成員方法person.eat();的輸出結果為:學生在吃面包!

子類重寫了父類(Person)的靜态成員方法person.look();的輸出結果為:人在看東西!

未被子類(Student)重寫的父類(Person)方法person.play()輸出結果為:人在移動!

在實際開發工作中,常常遇到一個功能有多種實作方式,比如支付方式,有分微信支付、京東支付、支付寶、銀聯等支付方式,不同支付方式的大概流程大抵相似,實作細節有所差別。這個時候就可以用到java的多态機制,先定義一個公共接口,接口定義支付流程的各個方法,具體的支付方式實作該接口的方法。在控制層,利用spring的注入擷取支付類型和支付方式實作類的引用映射,根據請求需要的支付類型就可以調用對應支付方式的方法,以此實作業務的解耦和拓展。後期需要增加支付方式,隻需要實作共同接口即可。

PaymentTypeService.java

/**

* 支付方式接口

*/

public interface PaymentTypeService {

public String type();

public void methodA();

public void methodB();

}

實作A:APaymentTypeServiceImpl.java

/**

* 支付方式A實作類

*/

@Service

public class APaymentTypeServiceImpl implements PaymentTypeService {

private final String type = "A";

@Override

public void methodA() {

// TODO Auto-generated method stub

System.out.println("PaymentType A invoke methodA");

}

@Override

public void methodB() {

// TODO Auto-generated method stub

System.out.println("PaymentType A invoke methodB");

}

@Override

public String type() {

return type;

}

}

實作B:BPaymentTypeServiceImpl.java

/**

* 支付方式B實作類

*/

@Service

public class BPaymentTypeServiceImpl implements PaymentTypeService {

private final String type = "B";

@Override

public void methodA() {

// TODO Auto-generated method stub

System.out.println("PaymentType B invoke methodA");

}

@Override

public void methodB() {

// TODO Auto-generated method stub

System.out.println("PaymentType B invoke methodB");

}

@Override

public String type() {

return type;

}

}

實際引用: DemoController.java

@RestController

public class DemoController {

private Map<String, PaymentTypeService> paymentTypeServices;

/**

* 構造函數初始化不同支付方式類型和實作類引用map

* @param services

*/

public DemoController(@Autowired List<PaymentTypeService> services){

paymentTypeServices = services.stream().collect(Collectors.toMap(PaymentTypeService::type, i->i));

}

/**

* 請求某個支付方式

* @date: 2018年4月23日 下午2:21:28

* @param type

*/

@GetMapping("/test/{type}")

public void test(@PathVariable("type") String type){

// 擷取該支付方式實作類

PaymentTypeService service = paymentTypeServices.get(type);

service.methodA();

service.methodB();

}

}

那麼我們可以根據以上情況總結出多态成員通路的特點:

  • 成員變量:編譯看左邊,運作看左邊。
  • 構造方法:建立子類對象的時候,通路父類的構造方法,對父類的資料進行初始化。
  • 成員方法:編譯看左邊,運作看右邊。(方法重寫的意義)
  • 靜态方法:編譯看左邊,運作看左邊。靜态和類相關,算不上重寫,是以通路還是左邊的。隻有非靜态的成員方法,編譯看左邊,運作看右邊。

多态的優點

可替換性:多态對已存在的代碼具有可替換性

可擴充性:增加新的子類并不影響已存在類的多态性、繼承性以及其他特性的運作和操作

接口性:多态是父類(超類)通過方法簽名,向子類提供了共同的接口,子類可以通過覆寫完善或者覆寫這個接口。

靈活性:在應用中展現了靈活多樣的操作,提高了使用效率。

多态的使用場景

多态的實作依賴于繼承,先聲明一個父類的執行個體,再于合适之時給它分别賦予不同的子類執行個體,此後操作該執行個體就仿佛操作子類的執行個體一般。就好比一個退了伍的軍人去當了廚師,他的首先是一個人,當有召時穿上軍服就是一名軍人,然後在工作是就是一名廚師。這個現象便是多态特性的一個實際運用,所謂多态,意思是有多種狀态。

引入多态概念的好處是,隻要某些類型都從同一個父類派生而來,就能在方法内部把它們當作同一種類型來處理,而無需區分具體的類型。仍以人的不同職業為例,不管是學生、醫生還是教師,都是某種職業,于是完全可以定義一個相似方法,根據輸入的不同職業的參數,讓這這個職業自己去進行相應的操作。

總結

定義方法參數清單時、定義方法傳回值類型時、定義類的成員變量時、定義數組元素類型時,都定義為父類類型,這樣就可以傳遞、傳回、指派、裝任意子類類型的對象定義時定義父類類型使用時使用子類類型的對象