繼承與多態

繼承

概述

由來

多個類中存在相同屬性和行爲時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行爲,只要繼承那一個類便可。如圖所示:java

其中,多個類能夠稱爲子類,單獨那一個類稱爲父類、超類(superclass)或者基類數組

繼承描述的是事物之間的所屬關係,這種關係是: is-a 的關係。例如,圖中兔子屬於食草動物,食草動物屬於動物。可見,父類更通用,子類更具體。咱們經過繼承,可使多種事物之間造成一種關係體系。dom

定義

  • 繼承:就是子類繼承父類的屬性行爲,使得子類對象具備與父類相同的屬性、相同的行爲。子類能夠直接訪問父類中的非私有的屬性和行爲。

好處

  1. 提升代碼的複用性
  2. 類與類之間產生了關係,是多態的前提

繼承的格式

經過 extends 關鍵字,能夠聲明一個子類繼承另一個父類,定義格式以下:ide

class 父類 {
    ...
}
class 子類 extends 父類{
    ...
}

繼承演示代碼以下:函數

//定義員工類Employee做爲父類
class Employee {    
    //定義name屬性  
    String name;
    //定義員工的工做方法
    public void work() {
        System.out.println("全力以赴地工做");
    }
}

//定義教師類Teacher繼承員工類Employee
class Teacher extends Employee {
    //定義一個打印name的方法
    public void printName() {
        System.out.println("name="+ name);
    }
}

//定義測試類
public class ExtendDemo01 {
    public static void main(String[] args) {
        //建立一個教師類對象
        Teacher t = new Teacher();
        //爲該員工類的name屬性賦值
        t.name = "小明";
        //調用Teacher類中的printName()方法
        t.printName(); // name=小明
        //調用Teacher類繼承來的Work()方法
        t.work(); // 全力以赴地工做
    }
}

繼承後的特色——成員變量

當類之間產生了關係後,其中各種中的成員變量,又產生了哪些影響呢?學習

成員變量不重名

若是子類父類中出現不重名的成員變量,這時的訪問是沒有影響的。代碼以下:測試

class Fu {
    //Fu中的成員變量
    int num = 5;
}

class Zi extends Fu {
    //Zi中的成員變量
    int num2 = 6;
    //Zi中的成員方法
    public void show() {
        //訪問父類中的num
        System.out.println("Fu num = "+num); //繼承而來,因此直接訪問。         
        //訪問子類中的num2
        System.out.println("Zi num2 = "+num2);
    }
}

class ExtendsDemo02 {
    public static void main(String[] args) {
        //建立子類對象
        Zi z = new Zi();
        //調用子類中的show()方法
        z.show();
    }
}

演示結果:
Fu num = 5
Zi num2 = 6

成員變量重名

若是子類父類中出現重名的成員變量,這時的訪問是有影響的。代碼以下:this

class Fu {
    //Fu中的成員變量
    int num = 5;
}

class Zi extends Fu {
    //Zi中的成員變量
    int num = 6;
    //Zi中的成員方法
    public void show() {
        //訪問父類中的num
        System.out.println("Fu num = "+num);            
        //訪問子類中的num
        System.out.println("Zi num = "+num);
    }
}

class ExtendsDemo02 {
    public static void main(String[] args) {
        //建立子類對象
        Zi z = new Zi();
        //調用子類中的show()方法
        z.show();
    }
}

演示結果:
Fu num = 6
Zi num = 6

子父類中出現了同名的成員變量時,在子類中須要訪問父類中非私有成員變量時,須要使用 super 關鍵字,修飾父類成員變量,相似於以前學過的 this編碼

使用格式:設計

super.父類成員變量名

子類方法須要修改,代碼以下:

class Zi extends Fu {
    //Zi中的成員變量
    int num = 6;
    //Zi中的成員方法
    public void show() {
        //訪問父類中的num
        System.out.println("Fu num = "+super.num);          
        //訪問子類中的num
        System.out.println("Zi num = "+num);
    }
}

演示結果:
Fu num = 5
Zi num = 5

小貼士:Fu 類中的成員變量是非私有的,子類中能夠直接訪問。若Fu 類中的成員變量私有了,子類是不能直接訪問的。一般編碼時,咱們遵循封裝的原則,使用private修飾成員變量,那麼如何訪問父類的私有成員 變量呢?對!能夠在父類中提供公共的getXxx方法和setXxx方法。

繼承後的特色——成員方法

當類之間產生了關係,其中各種中的成員方法,又產生了哪些影響呢?

成員方法不重名

若是子類父類中出現不重名的成員方法,這時的調用是沒有影響的。對象調用方法時,會先在子類中查找有沒有對應的方法,若子類中存在就會執行子類中的方法,若子類中不存在就會執行父類中相應的方法。代碼以下:

class Fu {
    public void show() {
        System.out.println("Fu類中的show方法執行");
    }
}
class Zi extends Fu {
    public void show2() {
        System.out.println("Zi類中的show2方法執行");
    }
}
public class ExtendsDemo04 {
    public static void main(String[] args) {
        Zi z = new Zi();
        //子類中沒有show方法,可是能夠找到父類方法去執行
        z.show();
        z.show2();
    }
}
演示結果:
Fu類中的show方法執行
Zi類中的show2方法執行

成員方法重名——重寫(Override)

若是子類父類中出現重名的成員方法,這時的訪問是一種特殊狀況,叫作方法重寫 (Override)。

  • 方法重寫 :子類中出現與父類如出一轍的方法時(返回值類型,方法名和參數列表都相同),會出現覆蓋效果,也稱爲重寫或者複寫。聲明不變,從新實現

代碼以下:

class Fu {
    public void show() {
        System.out.println("Fu類中的show方法執行");
    }
}
class Zi extends Fu {
    public void show() {
        System.out.println("Zi類中的show方法執行");
    }
}
public class ExtendsDemo04 {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.show();
    }
}
演示結果:
Zi類中的show方法執行

重寫的應用

子類能夠根據須要,定義特定於本身的行爲。既沿襲了父類的功能名稱,又根據子類的須要從新實現父類方法,從而進行擴展加強。

super.父類成員方法,表示調用父類的成員方法

注意事項

  1. 子類方法覆蓋父類方法,必需要保證權限大於等於父類權限。
  2. 子類方法覆蓋父類方法,返回值類型、函數名和參數列表都要如出一轍。

繼承後的特色——構造方法

當類之間產生了關係,其中各種中的構造方法,又產生了哪些影響呢?

首先咱們要回憶兩個事情,構造方法的定義格式和做用。

  1. 構造方法的名字是與類名一致的。因此子類是沒法繼承父類構造方法的。
  2. 構造方法的做用是初始化成員變量的。因此子類的初始化過程當中,必須先執行父類的初始化動做。子類的構造方法中默認有一個 super() ,表示調用父類的構造方法,父類成員變量初始化後,才能夠給子類使用。代碼以下:

    class Fu {
    private int n;
    Fu() {
    System.out.println("Fu()");
    }
    }
    class Zi extends Fu{
    Zi() {
    super(); //調用父類構造方法
    System.out.println("Zi()");
    }
    }
    public class ExtendsDemo07 {
    public static void main(String args) {
    Zi zi = new Zi();
    }
    }
    輸出結果:
    Fu()
    Zi()

super和this

父類空間優先於子類對象產生

在每次建立子類對象時,先初始化父類空間,再建立其子類對象自己。目的在於子類對象中包含了其對應的父類空間,即可以包含其父類的成員,若是父類成員非private修飾,則子類能夠隨意使用父類成員。代碼體如今子類的構造方法調用時,必定先調用父類的構造方法。理解圖解以下:

super和this的含義

  • super :表明父類的存儲空間標識(能夠理解爲父親的引用)。
  • this :表明當前對象的引用(誰調用就表明誰)。

super和this的用法

  1. 訪問成員

    this.成員變量    --  本類的
     super.成員變量   --  父類的
    
     this.成員方法名()    --  本類的
     super.成員方法名()   --  父類的
  2. 訪問構造方法

    this(...)    --  本類的構造方法
     super(...)   --  父類的構造方法

子類的每一個構造方法中均有默認的super(),調用父類的空參構造。手動調用父類構造會覆蓋默認的super()。 super() 和 this() 都必須是在構造方法的第一行,因此不能同時出現。

繼承的特色

  1. Java只支持單繼承,不支持多繼承。

    //一個類只能有一個父類,不能夠有多個父類。
     class C extends A{}     //ok
     class C exteds A,B...   //error
  2. Java支持多層繼承(繼承體系)。

    class A{}
     class B extends A{}
     class C extends B{}

頂層父類是Object類。全部的類默認繼承Object做爲父類。

  1. 子類和父類是一種相對的概念。

抽象類

概述

由來

父類中的方法,被它的子類們重寫,子類各自的實現都不盡相同。那麼父類的方法聲明和方法主體,只有聲明還有意義,而方法主體則沒有存在的意義了。咱們把沒有方法主體的方法稱爲抽象方法。Java語法規定,包含抽象方法的類就是抽象類

定義

  • 抽象方法 : 沒有方法體的方法。
  • 抽象類:包含抽象方法的類。

abstract使用格式

抽象方法

使用 abstract 關鍵字修飾方法,該方法就成了抽象方法,抽象方法只包含一個方法名,而沒有方法體。

定義格式:

修飾符 abstract 返回值類型 方法名 (參數列表);

代碼舉例:

public abstract void run();

抽象類

若是一個類包含抽象方法,那麼該類必須是抽象類。

定義格式:

abstract class 類名字 {

}

代碼舉例:

public abstract class Animal {
    public abstract void run();
}

抽象的使用

繼承抽象類的子類必須重寫父類全部的抽象方法。不然,該子類也必須聲明爲抽象類。最終,必須有子類實現該父類的抽象方法,不然,從最初的父類到最終的子類都不能建立對象,失去意義。

代碼舉例:

public class Cat extends Animal {
    public void run() {
        System.out.println("小貓在牆頭走");
    }
}
public class CatTest {
    public static void main(String[] args) {
        Cat c = new Cat();
        c.run();
    }
}
輸出結果:
小貓在牆頭走

此時的方法重寫,,是子類對父類抽象方法的完成實現,咱們將這種方法重寫的操做,也叫作實現方法

注意事項

關於抽象類的使用,如下爲語法上要注意的細節,雖然條目較多,但若理解了抽象的本質,無需死記硬背。

1.抽象類不能建立對象,若是建立,編譯沒法經過而報錯。只能建立其非抽象子類的對象。

理解:假設建立了抽象類的對象,調用抽象的方法,而抽象方法沒有具體的方法體,沒有意義。

2.抽象類中,能夠有構造方法,是供子類建立對象時,初始化父類成員使用的。

理解:子類的構造方法中,有默認的super(),須要訪問父類構造方法。

3.抽象類中,不必定包含抽象方法,可是有抽象方法的類一定是抽象類。

理解:未包含抽象方法的抽象類,目的就是不想讓調用者建立該類對象,一般用於某些特殊的類結構設計。

4.抽象類的子類,必須重寫抽象父類中全部的抽象方法,不然,編譯沒法經過而報錯。除非該g子類也是抽象類。

理解:假設不重寫全部抽象方法,則類中可能包含抽象方法。那麼建立對象後,調用抽象的方法,沒有意義。

繼承的綜合案例

羣主發普通紅包。某羣有多名成員,羣主給成員發普通紅包。普通紅包的規則:

  1. 羣主的一筆金額,從羣主餘額中扣除,平均分紅n等份,讓成員領取。
  2. 成員領取紅包後,保存到成員餘額中。

請根據描述,完成案例中全部類的定義以及指定類之間的繼承關係,並完成發紅包的操做。

根據描述分析,獲得以下繼承體系

案例實現

定義用戶類:

public class User {
    private String username;    //用戶名
    private double leftMoney;   //餘額

    //構造方法
    public User(){}

    public User(String username, double leftMoney) {
        this.username = username;
        this.leftMoney = leftMoney;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public double getLeftMoney() {
        return leftMoney;
    }

    public void setLeftMoney(double leftMoney) {
        this.leftMoney = leftMoney;
    }

    public void show() {
        System.out.println("用戶名:"+ username + ", 餘額爲:" + leftMoney + "元");
    }
}

定義羣主類:

public class Qunzhu extends User {

    public Qunzhu(){}

    public Qunzhu(String username, double leftMoney) {
        super(username, leftMoney);
    }
/*
    羣主發紅包,就是把一個浮點數的金額分紅若干份。
    1.獲取羣主金額,是否夠發紅包 不夠則返回null並提示 夠則繼續
    2.修改羣主餘額
    3.拆分成包
        3.1 若是能被整除就平均分
        3.2 若是不能 那麼就把餘數分給最後一份
 */
    public ArrayList<Double> send(double money, int count) {
        double leftMoney = getLeftMoney();
        if (money > leftMoney) {
            return null;
        }
        //修改羣主餘額
        setLeftMoney(leftMoney - money);
        //建立一個集合,保存等份金額
        ArrayList<Double> list = new ArrayList<>();
        //擴大100倍,至關於折算成‘分’爲單位,避免小數運算損失精度
        money = money * 100;
        //每份的金額
        double m = money / count;
        //不能整除的餘數
        double l = money % count;

        for(int i = 0;i < count - 1; i++) {
            list.add(m / 100.0);
        }
        if(l == 0) {
            //能整除,最後一份金額與以前每份金額一致
            list.add(m / 100.0);
        } else {
            //不能整除,最後一份金額爲以前每份的金額+餘數金額
            list.add((m + 1) / 100.0);
        }

        return list;
    }
}

定義成員類:

public class Member extends User{
    public Member() {}

    public Member(String username, double leftMoney) {
        super(username, leftMoney);
    }
    // 打開紅包,就是從集合中隨機取出一份保存到本身的餘額中
    public void openHongbao(ArrayList<Double> list) {
        Random r = new Random();
        int index = r.nextInt(list.size());
        Double money = list.remove(index);
        setLeftMoney(money);
    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        Qunzhu qz = new Qunzhu("羣主", 200);
        Scanner sc = new Scanner(System.in);
        System.out.println("請輸入金額:");
        double money = sc.nextDouble();
        System.out.println("請輸入個數:");
        int count = sc.nextInt();

        //發送紅包
        ArrayList<Double> sendList = qz.send(money,count);

        if(sendList == null) {
            System.out.println("餘額不足...");
            return;
        }

        Member m1 = new Member();
        Member m2 = new Member();
        Member m3 = new Member();

        m1.openHongbao(sendList);
        m2.openHongbao(sendList);
        m3.openHongbao(sendList);

        qz.show();
        m1.show();
        m2.show();
        m3.show();
    }
}

接口

概述

接口,是Java語言中一種引用類型,是方法的集合,若是說類的內部封裝了成員變量、構造方法和成員方法,那麼接口的內部主要就是封裝了方法,包含抽象方法(JDK 7及之前),默認方法和靜態方法(JDK 8),私有方法(JDK 9)。

接口的定義,它與定義類方式類似,可是使用 interface 關鍵字。它也會被編譯成.class文件,但必定要明確它並非類,而是另一種引用數據類型。

引用數據類型:數組、類、接口。

接口的使用,它不能建立對象,可是能夠被實現( implements ,相似於被繼承)。一個實現接口的類(能夠看做是接口的子類),須要實現接口中全部的抽象方法,建立該類對象,就能夠調用方法了,不然它必須是一個抽象類。

定義格式

public interface 接口名稱 {
    //抽象方法
    //默認方法
    //靜態方法
    //私有方法
}

含有抽象方法

抽象方法:使用 abstract 關鍵字修飾,能夠省略,沒有方法體。該方法供子類實現使用。

代碼以下:

public interface InterFaceName {
    public abstract void method();
}

含有默認方法和靜態方法

默認方法:使用 default 修飾,不可省略,供子類調用或者子類重寫。

靜態方法:使用 static 修飾,供接口直接調用。

代碼以下:

public interface InterFaceName {
    public default void method() {
        //執行語句
    }
    public static void method2() {
        //執行語句
    }
}

含有私有方法和私有靜態方法

私有方法:使用 private 修飾,供接口中的默認方法或者靜態方法調用。

代碼以下:

public interface InterFaceName {
    private void method() {
        //執行語句
    }
}

基本的實現

實現的概述

類與接口的關係爲實現關係,即類實現接口,該類能夠稱爲接口的實現類,也能夠稱爲接口的子類。實現的動做相似繼承,格式相仿,只是關鍵字不一樣,實現使用 implements 關鍵字。

非抽象子類實現接口:

  1. 必須重寫接口中全部抽象方法。
  2. 繼承了接口的默認方法,便可以直接調用,也能夠重寫。

實現格式:

class 類名 implements 接口名 {
    // 重寫接口中抽象方法【必須】
    // 重寫接口中默認方法【可選】
}

抽象方法的使用

必須所有實現,代碼以下:

定義接口:

public interface LivaAble {
    //定義抽象方法
    public abstract void eat();
    public abstract void sleep();
}

定義實現類:

public class Animal implements LiveAble {
    @Override
    public void eat() {
        System.out.println("吃東西");
    }
    @Override
    public void sleep() {
        System.out.println("晚上睡");
    }
}

測試類:

public class InterfaceDemo {
    public static void main(String[] args) {
        //建立子類對象
        Animal a = new Animal();
        //調用實現後的方法
        a.eat();
        a.sleep();
    }
}
輸出結果:
吃東西
晚上睡

默認方法的使用

能夠繼承,能夠重寫,二選一,可是隻能經過實現類的對象來調用。

1.默認繼承方法,代碼以下:

定義接口:

public interface LiveAble {
    public default void fly() {
        System.out.println("天上飛");
    }
}

定義實現類:

public class Animal implements LiveAble {
    //繼承,什麼都不用謝,直接調用
}

測試類:

public class InterfaceDemo {
    public static void main(String[] args) {
        //建立子類對象
        Animal a = new Animal();
        //調用默認方法
        a.fly();
    }
}
輸出結果:
天上飛

2.重寫默認方法,代碼以下:

定義接口:

public interface LiveAble {
    public default void fly() {
        System.out.println("天上飛");
    }
}

定義實現類:

public class Animal implements LiveAble {
    @Override
    public void fly() {
        System.out.println("自由自在的飛");
    }
}

測試類:

public class InterfaceDemo {
    public static void main(String[] args) {
        //建立子類對象
        Animal a = new Animal();
        //調用重寫方法
        a.fly();
    }
}
輸出結果:
自由自在的飛

靜態方法的使用

靜態與.class 文件相關,只能使用接口名調用,不能夠經過實現類的類名或者實現類的對象調用,代碼以下:

定義接口:

public interface LiveAble {
    public static void run() {
        System.out.println("地上跑");
    }
}

定義實現類:

public class Animale implements LiveAble {
    //沒法重寫靜態方法
}

測試類:

public class InterfaceDemo {
    public static void main(String[] args) {
        // Animal.run(); //【錯誤】沒法繼承方法,也沒法調用
        LiveAble.run();
    }
}
輸出結果:
地上跑

私有方法的使用

  • 私有方法:只有默認方法能夠調用
  • 私有靜態方法:默認方法和靜態方法能夠調用。

若是一個接口中有多個默認方法,而且方法中有重複的內容,那麼能夠抽取出來,封裝到私有方法中,供默認方法去調用。從設計的角度講,私有的方法是對默認方法和靜態方法的輔助。

定義接口:

public interface LiveAble {
    default void func() {
        func1();
        func2();
    }
    private void func1(){
        System.out.println("func1~~");
    }
    private void func2(){
        System.out.println("func2~~");
    }
}

接口的多實現

以前學過,在繼承體系中,一個類只能繼承一個父類。而對於接口而言,一個類是能夠實現多個接口的,這叫作接口的多實現。而且,一個類能繼承一個父類,同時實現多個接口。

實現格式:

class 類名 [extends 父類名] implements 接口名1,接口名2,接口名3... {
    //重寫接口中的抽象方法【必須】
    //重寫接口中的默認方法【不重名時可選】
}

抽象方法

接口中,有多個抽象方法時,實現類必須重寫全部抽象方法。若是抽象方法有重名的,只須要重寫一次。代碼以下:

定義多個接口:

interface A {
    public abstract void showA();
    public abstract void show();
}
interface B {
    public abstract void showB();
    public abstract void show();
}

定義實現類:

public class C implements A,B{
    @Override
    public void showA() {
        System.out.println("showA");
    }
    @Override
    public void showB() {
        System.out.println("showB");
    }
    @Override
    public void show() {
        System.out.println("show");
    }
}

默認方法

接口中,有多個默認方法時,實現類均可繼承使用。若是默認方法有重名的,必須重寫一次。代碼以下:

定義多個接口:

interface A {
    public default void methodA(){}
    public default void method(){}
}
interface B {
    public default void methodB(){}
    public default void method(){}
}

定義實現類:

public class C implements A,B {
    @Override
    public void method() {
        System.out.println("method");   
    }
}

靜態方法

接口中,存在同名的靜態方法並不會衝突,緣由是隻能經過各自接口名訪問靜態方法。

優先級的問題

當一個類,既繼承一個父類,又實現若干個接口時,父類中的成員方法與接口中的默認方法重名,子類就近選擇執行父類的成員方法。代碼以下:

定義接口:

interface A {
    public default void methodA(){
        System.out.println("AAA");
    }
}

定義父類:

class B {
    public void methodA(){
        System.out.println("BBB");
    }
}

定義子類:

class C extends D implements A {
    //未重寫methodA方法
}

測試類:

public class Test {
    public static void main(String[] args) {
        C c = new C();
        c.methodA();
    }
}
輸出結果:
BBB

接口的多繼承【瞭解】

一個接口能繼承另外一個或者多個接口,這和類之間的繼承比較類似。接口的繼承使用 extends 關鍵字,子接口繼承父接口的方法。若是父接口中的默認方法有重名的,那麼子接口須要重寫一次。代碼以下:

定義父接口:

interface A {
    public default void method(){
        System.out.println("AAA");
    }
}
interface B {
    public default void method(){
        System.out.println("BBB");
    }
}

定義子接口:

interface D extends A,B {
    @Override
    public default void method() {
        System.out.println("DD");
    }
}

小貼士:
子接口重寫默認方法時,default關鍵字能夠保留。子類重寫默認方法時,default關鍵字不能夠保留。

其餘成員特色

  • 接口中,沒法定義成員變量,可是能夠定義常量,其值不能夠改變,默認使用public static final修飾。
  • 接口中,沒有構造方法,不能建立對象。
  • 接口中,沒有靜態代碼塊。

多態

概述

引入

多態是繼封裝、繼承以後,面向對象的第三大特性。

生活中,好比跑的動做,小貓、小狗和大象,跑起來是不同的。再好比飛的動做,昆蟲、鳥類和飛機,飛起來也是不同的。可見,同一行爲經過不一樣的事物,能夠體現出來的不一樣的形態。多態描述的就是這樣的狀態。

定義

  • 多態: 是指同一行爲,具備多個不一樣表現形式。

前提【重點】

  1. 繼承或者實現【二選一】
  2. 方法的重寫【意義體現:不重寫,無心義】
  3. 父類引用指向子類對象【格式體現】

多態的體現

多態體現的格式:

父類類型 變量名 = new 子類對象;
變量名.方法名();

父類類型:指子類對象繼承的父類類型,或者實現的父接口類型。

代碼以下:

Fu f = new Zi();
f.method();

當使用多態方式調用方法時,首先檢查父類中是否有該方法,若是沒有,則編譯錯誤;若是有,執行的是子類重寫後方法。

代碼以下:

定義父類:

public abstract class Animal {
    public abstract void eat();
}

定義子類:

public Cat extends Animal {
    public void eat() {
        System.out.println("吃魚");
    }
}
public Dog extends Animal {
    public void eat() {
        System.out.println("吃骨頭");
    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        //多態形式,建立對象
        Animal a1 = new Cat();
        //調用的是Cat的eat
        a1.eat();
        //多態形式,建立對象
        Animal a2 = new Dog();
        //調用的是Dog的eat
        a2.eat();
    }
}
輸出結果:
吃魚
吃骨頭

多態的好處

實際開發的過程當中,父類類型做爲方法形式參數,傳遞子類對象給方法,進行方法的調用,更能體現出多態的擴展性與便利。代碼以下:

定義父類:

public abstract class Animal {
    public abstract void eat();
}

定義子類:

public Cat extends Animal {
    public void eat() {
        System.out.println("吃魚");
    }
}
public Dog extends Animal {
    public void eat() {
        System.out.println("吃骨頭");
    }
}

測試類:

public class Test_1 {
    public static void main(String[] args) {
        Cat c = new Cat();
        Dog d = new Dog();

        showCatEat(c);
        showDogEat(d);

        /*
        以上兩個方法都可以被showAnimalEat(Animal a)方法所替代,且執行效果一致
        */
        showAnimalEat(c);
        showAnimalEat(d);

    }
    public static void showCatEat(Cat c){
        c.eat();
    }
    public static void showDogEat(Dog d){
        d.eat();
    }
    public static void showAnimalEat(Animal a){
        a.eat();
    }
}

因爲多態特性的支持,showAnimalEat方法的Animal類型,是Cat和Dog的父類類型,父類類型接收子類對象,固然能夠把Cat對象和Dog對象,傳遞給方法。

當eat方法執行時,多態規定,執行的是子類重寫的方法,那麼效果天然與showCatEat、showDogEat方法一致,因此showAnimalEat徹底能夠替代以上兩方法。

不只僅是替代,在擴展性方面,不管以後再多的子類出現,咱們都不須要編寫showXxxEat方法了,直接使用showAnimalEat均可以完成。

因此,多態的好處,體如今,可使程序編寫的更簡單,並有良好的擴展。

引用類型轉換

多態的轉型分爲向上轉型與向下轉型兩種:

向上轉型

  • 向上轉型:多態自己是子類類型向父類類型向上轉換的過程,這個過程是默認的。

當父類引用指向一個子類對象時,即是向上轉型。

使用格式:

父類類型 變量名 = new 子類類型();
如 Animal a = new Cat();

向下轉型

  • 向下轉型:父類類型向子類類型向下轉換的過程,這個過程是強制的。

一個已經向上轉型的子類對象,將父類引用轉爲子類引用,可使用強制類型轉換的格式,即是向下轉型。

使用格式:

子類類型 變量名 = (子類類型) 父類變量名;
如 Cat c = (Cat) a;

爲何要轉型

當使用多態方式調用方法時,首先檢查父類中是否有該方法,若是沒有,則編譯錯誤。也就是說,不能調用子類擁有,而父類沒有的方法。編譯都錯誤,更別說運行了。這也是多態給咱們帶來的一點"小麻煩"。因此,想要調用子類特有的方法,必須作向下轉型。

轉型演示,代碼以下:

定義類:

abstract class Animal {
    abstract void eat();
}

class Cat extends Animal {
    public void eat() {
        System.out.println("吃魚");
    }
    public void catchMouse() {
        Sysetm.out.println("抓老鼠");
    }   
}

class Dog extends Animal {
    public void eat() {
        System.out.println("吃骨頭");
    }
    public void watchHouse() {
        System.out.println("看家");
    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        //向上轉型
        Animal a = new Cat();
        a.eat();    //調用的是Cat的eat
        //向下轉型
        Cat c = (Cat) a;
        c.catchMouse();
    }
}

轉型的異常

轉型的過程當中,一不當心就會遇到這樣的問題,請看以下代碼:

public class Test {
    public static void main(String[] args) {
        //向上轉型
        Animal a = new Cat();
        a.eat();
        //向下轉型
        Dog d = (Dog)a;
        d.watchHouse(); //調用的是Dog的watchHouse 【運行報錯】
    }
}

這段代碼能夠經過編譯,可是運行時,卻報出了 ClassCastException ,類型轉換異常!這是由於,明明建立了Cat類型對象,運行時,固然不能轉換成Dog對象的。這兩個類型並無任何繼承關係,不符合類型轉換的定義。

爲了不ClassCastException的發生,Java提供了 instanceof 關鍵字,給引用變量作類型的校驗,格式以下:

變量名 instanceof 數據類型  
若是變量屬於該數據類型,返回true。 
若是變量不屬於該數據類型,返回false。

因此,轉換前,咱們好先作一個判斷,代碼以下:

public class Test {
    public static void main(String[] args) {
        //向上轉型
        Animal a = new Cat();
        a.eat();
        //向下轉型
        if (a instanceof Cat) {
            Cat c = (Cat) a;
            c.catchMouse();
        } else if (a instanceof Dog) {
            Dog d = (Dog)a;
            d.watchHouse();
        }
    }
}

接口多態的綜合案例

筆記本電腦(laptop)一般具有使用USB設備的功能。在生產時,筆記本都預留了能夠插入USB設備的USB接口,但具體是什麼USB設備,筆記本廠商並不關心,只要符合USB規格的設備均可以。

定義USB接口,具有基本的開啓功能和關閉功能。鼠標和鍵盤要想能在電腦上使用,那麼鼠標和鍵盤也必須遵照USB規範,實現USB接口,不然鼠標和鍵盤的生產出來也沒法使用。

案例分析

進行描述筆記本類,實現筆記本使用USB鼠標、USB鍵盤

  • USB接口,包含開啓功能、關閉功能
  • 筆記本類,包含運行功能、關機功能、使用USB設備功能
  • 鼠標類,要實現USB接口,並具有點擊的方法
  • 鍵盤類,要實現USB接口,具有敲擊的方法

案例實現

定義USB接口:

public interface USB {
    void open(); //開啓功能
    void close(); //關閉功能
}

定義鼠標類:

public class Mouse implements USB {
    @Override
    public void open() {
        System.out.println("鼠標開啓,紅燈亮");
    }

    @Override
    public void close() {
        System.out.println("鼠標關閉,紅燈滅");
    }

    public void click() {
        System.out.println("鼠標點擊");
    }
}

定義鍵盤類:

public class KeyBoard implements USB {
    @Override
    public void open() {
        System.out.println("鍵盤開啓,綠燈亮");
    }

    @Override
    public void close() {
        System.out.println("鍵盤關閉,綠燈滅");
    }

    public void type() {
        System.out.println("鍵盤打字");
    }   
}

定義筆記本類:

class Laptop {
    public void run() {
        System.out.println("筆記本運行");
    }

    //筆記本使用usb設備,當筆記本對象調用這個功能時必須給其傳遞一個符合USB規則的USB設備
    public void useUSB(USB usb) {
        if (usb != null) {
            usb.open();
            if (usb instanceof Mouse){
                Mouse m = (Mouse) usb;
                m.click();
            } else if (usb instanceof KeyBoard) {
                KeyBoard kb = (KeyBoard)usb;
                kb.type();
            }
            usb.close();
        }
    }

    public void shutDown() {
        System.out.println("筆記本關閉");
    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        Laptop lt = new Laptop();
        lt.run();

        //建立鼠標實體對象
        USB u = new Mouse();
        //筆記本使用鼠標
        lt.useUSB(u);
        //建立鍵盤實體對象
        USB kb = new KeyBoard();
        //筆記本使用鍵盤
        lt.useUSB(kb);

        lt.shutDown();
    }
}

final關鍵字

概述

學習了繼承後,咱們知道,子類能夠在父類的基礎上改寫父類內容,好比,方法重寫。那麼咱們能不能隨意的繼承API中提供的類,改寫其內容呢?顯然這是不合適的。爲了不這種隨意改寫的狀況,Java提供了 final 關鍵字,用於修飾不可改變內容。

  • final: 不可改變。能夠用於修飾類、方法和變量。
    • 類:被修飾的類,不能被繼承。
    • 方法:被修飾的方法,不能被重寫。
    • 變量:被修飾的變量,不能被從新賦值。

使用方式

修飾類

格式以下:

final class 類名 {

}

查詢API發現像 public final class Stringpublic final class Mathpublic final class Scanner等,不少咱們學習過的類,都是被final修飾的,目的就是供咱們使用,而不讓咱們因此改變其內容。

修飾方法

格式以下:

修飾符 final 返回值類型 方法名(參數列表) {
    //方法體
}

重寫被 final 修飾的方法,編譯時就會報錯。

修飾變量

1.局部變量——基本類型

基本類型的局部變量,被final修飾後,只能賦值一次,不能再更改。

2.局部變量——引用類型

引用類型的局部變量,被final修飾後,只能指向一個對象,地址不能再更改。可是不影響對象內部的成員變量值的修改,代碼以下:

public class FinalDemo2 {
    public static void main(String[] args) {
        //建立User對象
        final User u = new User();
        //建立另外一個User對象
        u = new User(); //報錯,指向了新的對象,地址值改變。
        //調用setName方法
        u.setName("張三"); //能夠修改
    }
}

3.成員變量

成員變量涉及到初始化的問題,初始化方式有兩種,只能二選一:

  • 顯示初始化:

    public class User {
    final String USERNAME = "張三";
    private int age;
    }

  • 構造方法初始化:

    public class User {
    final String USERNAME;
    private int age;
    public User(String username, int age) {
    this.USERNAME = username;
    this.age = age;
    }
    }

被final修飾的常量名稱,通常都有書寫規範,全部字母都大寫。

權限修飾符

概述

在Java中提供了四種訪問權限,使用不一樣的訪問權限修飾符修飾時,被修飾的內容會有不一樣的訪問權限。

  • public:公共的
  • protected:受保護的
  • default:默認的
  • private:私有的

不一樣權限的訪問能力

public protected defalut(空的) private
同一類中
同一包中(子類與無關類)
不一樣包的子類
不一樣包中的無關類

可見,public具備最大權限。private則是最小權限。

編寫代碼時,若是沒有特殊的考慮,建議這樣使用權限:

  • 成員變量使用 private ,隱藏細節。
  • 構造方法使用 public ,方便建立對象。
  • 成員方法使用 public ,方便調用方法。

小貼士:不加權限修飾符,其訪問能力與default修飾符相同

內部類

概述

什麼是內部類

將一個類A定義在另外一個類B裏面,裏面的那個類A就稱爲內部類,B則稱爲外部類

成員內部類

  • 成員內部類 :定義在類中方法外的類。

定義格式:

class 外部類 {
    class 內部類 {

    }
}

在描述事物時,若一個事物內部還包含其餘事物,就可使用內部類這種結構。好比,汽車類 Car 中包含發動機類 Engine ,這時, Engine 就可使用內部類來描述,定義在成員位置。

代碼舉例:

class Car { //外部類
    class Engine {  //內部類

    }
}

訪問特色

  • 內部類能夠直接訪問外部類的成員,包括私有成員。
  • 外部類要訪問內部類的成員,必需要創建內部類的對象。

建立內部類對象格式:

外部類名.內部類名 對象名 = new 外部類型().new 內部類型();

訪問演示代碼以下:

定義類:

public class Person {
    private boolean live = true;
    class Heart {
        public void jump() {
            //直接訪問外部類成員
            if (live) {
                System.out.println("心臟在跳動");
            } else {
                Sysetm.out.println("心臟不跳了");
            }
        }
    }

    public boolean isLive() {
        return live;
    }
    public void setLive(boolean live) {
        this.live = live;
    }
}

測試類:

public class InnerDemo {
    public static void main(String[] args) {
        //建立外部類對象
        Person p = new Person();
        //建立內部類對象
        Heart heart = p.new Heart();

        //調用內部類方法
        heart.jump();
        //調用外部類方法
        p.setLive(false);
        //調用內部類方法
        heart.jump;
    }
}
輸出結果:
心臟在跳動
心臟不跳了

內部類仍然是一個獨立的類,在編譯以後會內部類會被編譯成獨立的.class文件,可是前面冠之外部類的類名和$符號 。
好比,Person$Heart.class

匿名內部類【重點】

  • 匿名內部類 :是內部類的簡化寫法。它的本質是一個 帶具體實現的 父類或者父接口的 匿名的 子類對象
    開發中,最經常使用到的內部類就是匿名內部類了。以接口舉例,當你使用一個接口時,彷佛得作以下幾步操做。
  1. 定義子類
  2. 重寫接口中的方法
  3. 建立子類對象
  4. 調用重寫後的方法

咱們的目的,最終只是爲了調用方法,那麼能不能簡化一下,把以上四步合成一步呢?匿名內部類就是作這樣的快捷方式。

前提

匿名內部類必須繼承一個父類或者實現一個父接口

格式

new 父類名或者接口名() {
    //方法重寫
    @Override
    public void method() {
        //執行語句
    }
};

使用方式

以接口爲例,匿名內部類的使用,代碼以下:

定義接口:

public abstract class FlyAble{
    public abstract void fly();
}

建立匿名內部類並調用:

public class InnerDemo {
    public static void main(String[] args) {
        /*
        1.等號右邊:是匿名內部類,定義並建立該接口的子類對象 
        2.等號左邊:是多態賦值,接口類型引用指向子類對象   
        */
        FlyAble f = new FlyAble() {
            public void fly() {
                System.out.println("fly~");
            }
        };
        
        //調用fly方法,執行重寫後的方法
        f.fly();
    }
}

一般在方法的形式參數是接口或者抽象類時,也能夠將匿名內部類做爲參數傳遞。代碼以下:

public class InnerDemo2 {
    public static void main(String[] args) {
        /*         
        1.等號右邊:定義並建立該接口的子類對象         
        2.等號左邊:是多態,接口類型引用指向子類對象        
        */ 
        FlyAble f = new FlyAble() {
            public void fly() {
                System.out.println("fly~~");
            }
        };
        //將f傳遞給showFly方法中
        showFly(f);
    }
    public static void showFly(FlyAble f) {
        f.fly();
    }
}

以上兩步,也能夠簡化爲一步,代碼以下:

public class InnerDemo3 {
    public static void main(String[] args) {
        /*         
        建立匿名內部類,直接傳遞給showFly(FlyAble f)           
        */
        showFly(new FlyAble(){
            public void fly() {
                System.out.println("fly~~");
            }
        }); 
    }
    public static void showFly(FlyAble f) {
        f.fly();
    }
}

引用類型用法總結

實際的開發中,引用類型的使用很是重要,也是很是廣泛的。咱們能夠在理解基本類型的使用方式基礎上,進一步去掌握引用類型的使用方式。基本類型能夠做爲成員變量、做爲方法的參數、做爲方法的返回值,那麼固然引用類型也是能夠的。

class做爲成員變量

在定義一個類Role(遊戲角色)時,代碼以下:

class Role {
    int id; //角色id
    int blood; //生命值
    String name; //角色名稱
}

使用 int 類型表示 角色id和生命值,使用 String 類型表示姓名。此時, String 自己就是引用類型,因爲使用的方式相似常量,因此每每忽略了它是引用類型的存在。若是咱們繼續豐富這個類的定義,給 Role 增長武器,穿戴裝備等屬性,咱們將如何編寫呢?

定義武器類,將增長攻擊能力:

class Weapon { 
    String name; //武器名稱
    int hurt; //傷害值
}

定義穿戴盔甲類,將增長防護能力,也就是提高生命值:

class Armour {
    String name; //裝備名稱
    int protect; //防護值
}

定義角色類:

class Role {
    int id;
    int blood;
    String name;
    //添加武器屬性
    Weapon wp;
    //添加盔甲屬性
    Armour ar;

    //提供get/set方法
    public Weapon getWp() {
        return wp;
    }
    public void setWeapon(Weapon wp) {
        this.wp = wp;
    }
    public Armour getArmour() {
        return ar;
    }
    public void setArmour(Armour ar) {
        this.ar = ar;
    }
    //攻擊方法
    public void attack() {
        System.out.println("使用"+wp.getName() + ", 形成"+wp.getHurt()+"點傷害");
    }
    //穿戴盔甲
    public void wear() {
        //增長防護就是增長blood值
        this.blood += ar.getProtect();
        System.out.println("穿上"+ar.getName()+", 生命值增長"+ar.getProtect());
    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        //建立Weapon對象
        Weapon wp = new Weapon("屠龍寶刀", 99999);
        //建立Armour對象
        Armour ar = new Armour("麒麟甲",10000);
        //建立Role對象
        Role r = new Role();
        //設置屬性
        r.setWeapon(wp);
        r.setArmour(ar);
        r.attack();
        r.wear();
    }
}
輸出結果:
使用屠龍寶刀,形成99999點傷害
穿上麒麟甲,生命值增長10000

類做爲成員變量時,對它進行賦值的操做,其實是賦給它該類的一個對象。

interface做爲成員變量

接口是對方法的封裝,對應遊戲當中,能夠看做是擴展遊戲角色的技能。因此,若是想擴展更強大技能,咱們在Role中,能夠增長接口做爲成員變量,來設置不一樣的技能。

定義接口:

//法術攻擊
public interface FaShuSkill {
    public abstract void FaShuAttack();
}

定義角色類:

public class Role {
    FaShuSkill fs;
    public void setFaShuSkill(FaShuSkill fs) {
        this.fs = fs;
    }
    //法術攻擊
    public void FaShuSkillAttack() {
        System.out.print("發動法術攻擊:");
        fa.FaShuAttack();
        System.out.println("攻擊完畢");
    }
}

測試類:

public class Test {
    public static void main(String[] args) {
        //建立遊戲角色
        Role role = new Role();
        //設置角色法術技能
        role.setFaShuSkill(new FaShuSkill(){
            @Override
            public void FaShuAttack() {
                System.out.println("縱橫天下");
            }
        });
        //發動法術攻擊
        role.FaShuSkillAttack();
        //更換技能
        role.setFaShuSkill(new FaShuSkill() {
            @Override
            public void FaShuAttack() {
                System.out.println("逆轉乾坤");
            }
        });
        //發動法術攻擊
        role.FaShuSkillAttack();
    }
}
輸出結果:
發動法術攻擊:縱橫天下 
攻擊完畢 
發動法術攻擊:逆轉乾坤 
攻擊完畢

咱們使用一個接口,做爲成員變量,以便隨時更換技能,這樣的設計更爲靈活,加強了程序的擴展性。

接口做爲成員變量時,對它進行賦值的操做,實際上,是賦給它該接口的一個子類對象。

interface做爲方法參數和返回值類型

當接口做爲方法的參數時,須要傳遞什麼呢?當接口做爲方法的返回值類型時,須要返回什麼呢?對,其實都是它的子類對象。 ArrayList 類咱們並不陌生,查看API咱們發現,實際上,它是 java.util.List 接口的實現類。因此,當咱們看見 List 接口做爲參數或者返回值類型時,固然能夠將 ArrayList 的對象進行傳遞或返回。

請觀察以下方法:獲取某集合中全部的偶數

定義方法:

public static List<Integer> getEvenNum(List<Integer> list){
    //建立保存偶數的集合
    ArrayList<Integer> evenList = new ArrayList<>();
    for (int i = 0; i < list.size(); i++){
        Integer integer = list.get(i);
        if(integer % 2 == 0) {
            evenList.add(integer);
        }
    }
    /*
    返回偶數集合
    由於getEvenNum方法的返回值類型是List,而ArrayList是List的子類,因此evenList能夠返回
     */
    return evenList;
}

調用方法:

public static void main(String[] args) {
    //建立ArrayList集合,並添加數字
    ArrayList<Integer> srcList = new ArrayList<>();
    for(int i = 0; i < 10; i++) {
        srcList.add(i);
    }
    /*        
    獲取偶數集合          
    由於getEvenNum方法的參數是List,而ArrayList是List的子類,          
    因此srcList能夠傳遞          
    */
    List list = getEvenNum(srcList);
    System.out.println(list);
}

接口做爲參數時,傳遞它的子類對象。

接口做爲返回值類型時,返回它的子類對象。

相關文章
相關標籤/搜索