畢向東Java基礎(chǔ)教程-繼承【下】

抽象類

概述

  • 抽象定義
    抽象就是從多個(gè)事物中將共性的、本質(zhì)的內(nèi)容抽取出來(lái)。
    例如:狼和狗共性都是犬科,犬科就是抽象出來(lái)的概念。

  • 抽象類
    Java中可以定義沒(méi)有方法體的方法,該方法的具體實(shí)現(xiàn)由子類完成,該方法稱為抽象方法,包含抽象方法的類就是抽象類。

  • 抽象方法的由來(lái)
    多個(gè)對(duì)象都具備相同的功能,但是功能具體內(nèi)容有所不同,那么在抽取過(guò)程中,只抽取了功能定義,并未抽取功能主體,那么只有功能聲明,沒(méi)有功能主體的方法稱為抽象方法。
    例如:狼和狗都有吼叫的方法,可是吼叫內(nèi)容是不一樣的。所以抽象出來(lái)的犬科雖然有吼叫功能,但是并不明確吼叫的細(xì)節(jié)。

特點(diǎn)

  • 抽象類和抽象方法必須用abstract關(guān)鍵字來(lái)修飾。
  • 抽象方法只有方法聲明,沒(méi)有方法體,定義在抽象類中。
    格式: 修飾符 abstract 返回值類型 函數(shù)名(參數(shù)列表) ;
  • 抽象類不可以被實(shí)例化,也就是不可以用new創(chuàng)建對(duì)象。
    原因如下:
    1. 抽象類是具體事物抽取出來(lái)的,本身是不具體的,沒(méi)有對(duì)應(yīng)的實(shí)例。
      例如:犬科是一個(gè)抽象的概念,真正存在的是狼和狗。
    2. 而且抽象類即使創(chuàng)建了對(duì)象,調(diào)用抽象方法也沒(méi)有意義。
  • 抽象類通過(guò)其子類實(shí)例化,而子類需要覆蓋掉抽象類中所有的抽象方法后才可以創(chuàng)建對(duì)象,否則該子類也是抽象類。

相關(guān)問(wèn)題

  • 抽象類中是否有構(gòu)造函數(shù)?
    有,用于給子類對(duì)象進(jìn)行初始化。

  • 抽象類中可不可以沒(méi)有抽象方法?
    可以,但很少見(jiàn),目的是不讓該類創(chuàng)建對(duì)象。AWT的適配器對(duì)象就是這種類。
    通常這個(gè)類中的方法有方法體,但沒(méi)有內(nèi)容。(沒(méi)有大括號(hào)才是沒(méi)有方法體,有大括號(hào)就表示有方法體)

    abstract class Demo
    {
        void show1()
        {}
        void show2()
        {}
     }
    
  • 抽象關(guān)鍵字abstract不可以和哪些關(guān)鍵字共存?
    private:修飾方法時(shí),子類不知道父類的方法,因此父類的方法不能被子類覆蓋;修飾類時(shí)更不用說(shuō)了。static:修飾方法,可直接用抽象類的類名調(diào)用,不用對(duì)象,但是沒(méi)有方法體,所以沒(méi)有意義。
    final:修飾類時(shí),子類不能繼承父類;修飾方法時(shí),子類不能覆蓋掉父類的方法。

  • 抽象類和一般類的異同點(diǎn)
    相同點(diǎn):抽象類和一般類都是用來(lái)描述事物的,都在內(nèi)部定義了成員。
    不同點(diǎn):
    一般類有足夠的信息描述事物;抽象類描述事物的信息有可能不足。
    一般類中不能定義抽象方法,只能定義非抽象方法;抽象類中可定義抽象方法,同時(shí)也可定義非抽象方法。
    一般類可以被實(shí)例化;抽象類不可以被實(shí)例化。

  • 抽象類一定是個(gè)父類嗎?
    是的。因?yàn)樾枰宇惛采w其方法后才可以對(duì)子類實(shí)例化。

示例代碼

abstract class Employee
{
    private String name;
    private String id;
    private double salary;

    Employee(String name, String id, double salary)
    {
        this.name = name;
        this.id = id;
        this.salary = salary;
    }

    public String getName(){return name;}
    public void setName(String name){this.name = name;}
    public String getId(){return id;}
    public void setId(String id){this.id = id;}
    public double getSalary(){return salary;}
    public void setSalary(double salary){this.salary = salary;}

    public abstract void work();
}

class Programmer extends Employee
{
    Programmer(String name, String id, double salary)
    {
        super(name, id, salary);
    }
    public void work()
    {
        System.out.println("code...");
    }
}

class Manager extends Employee
{
    private double bonus;
    Manager(String name, String id, double salary, double bonus)
    {
        super(name, id, salary);
        this.bonus = bonus;
    }
    public void work()
    {
        System.out.println("manage...");
    }
}

接口

概述

1. 定義
當(dāng)一個(gè)抽象類中的方法都是抽象的時(shí)候,可以將該抽象類用另一種形式定義和表示,就是接口interface(表面上是這樣,但實(shí)質(zhì)卻很不相同)。

abstract class Demo
{
    abstract void show1();
    abstract void show2();
}

2. 格式interface {}

interface Demo
{
    abstract void show1();
    abstract void show2();
}

3. 接口中的成員修飾符是固定的。
成員常量: public static final,成員函數(shù): public abstract

interface Demo
{
    public static final int NUM = 4;
    public abstract void show1();
    public abstract void show2();
}

由于是固定的,所以可以省略,寫(xiě)成下面的形式(但是閱讀性差).

interface Demo
{
    int NUM = 4;
    void show1();
    void show2();
}

4. 接口不可以實(shí)例化。
只能由實(shí)現(xiàn)了接口的子類覆蓋接口中所有的抽象方法后,該子類才可以實(shí)例化,否則這個(gè)子類就是一個(gè)抽象類。

class DemoImpl implements Demo
{
    public void show1()//注意必須寫(xiě)public
    {}
    public void show2()
    {}
}
class InterfaceDemo
{
    DemoImpl d = new DemoImpl();
    System.out.println(d.NUM);
    System.out.println(DemoImpl.NUM);
    System.out.println(Demo.NUM);
    // 以上三種都對(duì),但是不能寫(xiě)d.NUM = 3;
}

5. 接口的出現(xiàn)將“多繼承”通過(guò)另一種形式體現(xiàn)出來(lái),即“多實(shí)現(xiàn)”。
在java中不直接支持多繼承,因?yàn)闀?huì)出現(xiàn)調(diào)用的不確定性,所以java將多繼承機(jī)制進(jìn)行改良,變成了多實(shí)現(xiàn)。
Example1:一個(gè)類可以實(shí)現(xiàn)多個(gè)接口

interface A
{
    public abstract void show();
}
interface B
{
    public abstract void show();
}
class Test implements A,B
{
    public void show()
    {}
}
class Demo
{
    public static void main(String[] args)
    {
        Test t = new Test();
        t.show();//不會(huì)有不確定性,因?yàn)樵诮涌谥械姆椒](méi)有方法體
    }
}

注意,以下這種情況不允許多實(shí)現(xiàn)。因?yàn)槿糁粚?xiě)1,則不能覆蓋到B中的函數(shù),而如果1和2都寫(xiě),則會(huì)沖突。

interface A
{
    public abstract void show();
}
interface B
{
    public abstract int show();
}
class Test implements A,B
{
    /*------1------*/
    public void show()
    {}
    /*------2------*/
    public int show()
    {...}
}

Example2:一個(gè)類在繼承另一個(gè)類的同時(shí),還可以實(shí)現(xiàn)多個(gè)接口
繼承自Q,屬于Q的體系,但通過(guò)實(shí)現(xiàn)接口,擴(kuò)展功能。接口的出現(xiàn)避免了單繼承的局限性。

class Q
{
    public void method()
    {}
}
class Test2 extends Q implements A,B
{}

Example3:接口之間可以多繼承
類與類之間是繼承關(guān)系,類與接口之間是實(shí)現(xiàn)關(guān)系,接口與接口之間是繼承關(guān)系。
多繼承的問(wèn)題主要在于方法體。

interface CC
{
    void show();
}
interface MM
{
    void method();
}
interface QQ extends CC,MM
{
    void function();
}
class WW implements WW
{
    //需要覆蓋三個(gè)方法
    public void show(){}
    public void method(){}
    public void funcion(){}
}

特點(diǎn)

  • 接口是對(duì)外暴露的規(guī)則。
    凡是對(duì)外暴露的內(nèi)容,全都可以理解為接口,不要把接口僅理解為interface。
  • 接口是程序的功能擴(kuò)展。
  • 接口的出現(xiàn)降低耦合性。
  • 接口可以用來(lái)多實(shí)現(xiàn)。
  • 類與接口之間是實(shí)現(xiàn)關(guān)系,而且類可以繼承一個(gè)類的同時(shí)實(shí)現(xiàn)多個(gè)接口。
  • 接口與接口之間可以有繼承關(guān)系。

例如,筆記本的接口(好好體會(huì))
筆記本的接口(如USB接口)是提前定義的規(guī)則【接口】,外圍設(shè)備(如U盤、鼠標(biāo))的生產(chǎn)產(chǎn)商只需按照該規(guī)則生產(chǎn)設(shè)備【實(shí)現(xiàn)類】即可,人們?cè)谑褂脮r(shí)【使用類】,只需插拔不同的設(shè)備(即不同情況使用不同的實(shí)現(xiàn)類),而無(wú)需去更改筆記本內(nèi)部的結(jié)構(gòu),以后若還有其他外圍設(shè)備生產(chǎn),也只用實(shí)現(xiàn)該接口就可。
筆記本的接口在于三部分:定義規(guī)則、實(shí)現(xiàn)規(guī)則、使用規(guī)則。而規(guī)則在java中即是interface。
注意:(引用指向的是對(duì)象)接口型引用指向的是其子類的對(duì)象

接口與抽象類的異同點(diǎn)

相同點(diǎn):都是不斷抽取出來(lái)的抽象的概念。

不同點(diǎn)

  • 抽象類體現(xiàn)繼承關(guān)系,一個(gè)類只能單繼承;
    接口體現(xiàn)實(shí)現(xiàn)關(guān)系,一個(gè)類可以多實(shí)現(xiàn)。
  • 抽象類中可以定義抽象方法和非抽象方法,子類繼承后,可以直接使用非抽象方法;
    接口中只能定義抽象方法,必須由子類實(shí)現(xiàn),接口中的成員都有固定修飾符。
  • 抽象類是繼承,是"is a"關(guān)系,在定義該體系的基本共性內(nèi)容;
    接口是實(shí)現(xiàn),是"like a"關(guān)系,在定義體系額外功能。

多態(tài)

概述

1. 定義:某一類事物的多種存在形態(tài)。
例:動(dòng)物中貓,狗。
貓這個(gè)對(duì)象對(duì)應(yīng)的類型是貓類型:貓 x = new 貓();
同時(shí)貓也是動(dòng)物中的一種,也可以把貓稱為動(dòng)物:動(dòng)物 y = new 貓();(動(dòng)物是貓和狗具體事物中抽取出來(lái)的父類型。)
貓這類事物既具備貓的形態(tài),又具備著動(dòng)物的形態(tài),這就是對(duì)象的多態(tài)性(一個(gè)對(duì)象,兩種形態(tài))。簡(jiǎn)單說(shuō),就是一個(gè)對(duì)象對(duì)應(yīng)著不同類型。
網(wǎng)上的另一種解釋——不同類的對(duì)象對(duì)同一消息作出不同的響應(yīng)就叫做多態(tài)。就像上課鈴響了,上體育課的學(xué)生跑到操場(chǎng)上站好,上語(yǔ)文課的學(xué)生在教室里坐好一樣。

2. 多態(tài)在代碼中的體現(xiàn):父類或者接口的引用指向或者接受自己的子類對(duì)象。

abstract class Animal
{
    abstract void eat();
}
class Dog extends Animal
{
    void eat()
    {
        System.out.println("啃骨頭");
    }
}
class Cat extends Animal
{
    void eat()
    {
        System.out.println("吃魚(yú)");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Cat c = new Cat();
        Dog d = new Dog();

        method(c);
        method(d);
    }
    public static void method(Animal a) //用父類作為參數(shù),這個(gè)函數(shù)可以接收其各個(gè)子類的對(duì)象
    {
        a.eat();
    }
}

3. 多態(tài)的好處:提高了代碼的擴(kuò)展性(重用性)和后期可維護(hù)性,前期定義的代碼可以使用后期的內(nèi)容。
網(wǎng)上的資料——簡(jiǎn)單講多態(tài)的作用就是解耦。再詳細(xì)點(diǎn)講就是,多態(tài)是設(shè)計(jì)模式的基礎(chǔ),不能說(shuō)所有的設(shè)計(jì)模式都使用到了多態(tài),但是23種中的很大一部分,都是基于多態(tài)的。

class Pig extends Animal //新加的一個(gè)Pig類
{
    void eat()
    {
        System.out.println("吃飼料");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Cat c = new Cat();
        Dog d = new Dog();

        method(c);
        method(d);
        method(new Pig()); //新加代碼
    }
    public static void method(Animal a)
    {
        a.eat();
    }
}

4. 多態(tài)的弊端:前期定義的內(nèi)容不能使用(調(diào)用)后期子類的特有內(nèi)容。

abstract class Animal
{
    abstract void eat();
}
class Dog extends Animal
{
    void eat()
    {
        System.out.println("啃骨頭");
    }
    void guardHouse()
    {
        System.out.println("看家");
    }
}
class Cat extends Animal
{
    void eat()
    {
        System.out.println("吃魚(yú)");
    }
    void catchMice()
    {
        System.out.println("抓老鼠");
    }
}
class Pig extends Animal
{
    void eat()
    {
        System.out.println("吃飼料");
    }
    void digEarth()
    {
        System.out.println("拱地");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        /* 第一種方式*/
        Animal a = new Cat();
        a.eat();
        a.catchMice(); //編譯錯(cuò)誤,在類Animal中找不到方法catchMice()

        /* 第二種方式*/
        Cat c = new Cat();
        method(c);
    }
    public static void method(Animal a)
    {
        a.eat();
        a.catchMice(); //編譯錯(cuò)誤,在類Animal中找不到方法catchMice()
    }
}

5. 多態(tài)的前提
1)存在繼承或者實(shí)現(xiàn)關(guān)系
2)子類重寫(xiě)父類方法
3)父類引用指向子類對(duì)象

6. 轉(zhuǎn)型

Animal a = new Cat(); //自動(dòng)類型提升,貓對(duì)象提升到了動(dòng)物類型。
a.eat();

向上轉(zhuǎn)型(自動(dòng)):貓一旦提升成動(dòng)物,訪問(wèn)上就有了局限性,不能再訪問(wèn)貓的特有功能了。其作用就是提高擴(kuò)展性(可以接收不同類型)和限制對(duì)特有功能的訪問(wèn)。
如果還想調(diào)用具體動(dòng)物——貓的特有功能,還可將該對(duì)象進(jìn)行向下轉(zhuǎn)型。

Cat c = (Cat)a;
c.eat();
c.catchMice();

向下轉(zhuǎn)型(需要強(qiáng)轉(zhuǎn)):目的是為了使用子類中的特有方法。

Animal a = new Dog();
Cat c = (Cat)a; //運(yùn)行報(bào)錯(cuò):java.lang.ClassCastException: Dog cannot be cast to Cat
Son s = (Son)new Father(); //注意這種寫(xiě)法也可以

注意,對(duì)于轉(zhuǎn)型,自始至終都是子類對(duì)象在做著類型的變化,一會(huì)變成父類型,一會(huì)變成本類型。

7. instanceof關(guān)鍵字
用于判斷對(duì)象的具體類型,只能用于引用數(shù)據(jù)類型判斷。

public static void method(Animal a)
{
    a.eat();
    if(a instanceof Cat)
    {
        Cat c = (Cat)a;
        c.catchMice();
    }else if(a instanceof Dog)
    {
        Dog d = (Dog)d;
        d.guardHouse();
    }
}

通常在向下轉(zhuǎn)型前使用,增強(qiáng)程序的健壯性(因?yàn)槿绻皇秦埖念愋?,并且程序沒(méi)有對(duì)其判斷的話,直接強(qiáng)轉(zhuǎn),編譯時(shí)不會(huì)報(bào)錯(cuò),但運(yùn)行會(huì)報(bào)錯(cuò))。

8.多態(tài)的分類
1)編譯時(shí)多態(tài),即方法的重載,從JVM的角度來(lái)講,這是一種靜態(tài)分派(static dispatch)【函數(shù)的多態(tài)】
2)運(yùn)行時(shí)多態(tài),即方法的重寫(xiě),從JVM的角度來(lái)講,這是一種動(dòng)態(tài)分派(dynamic dispatch)【對(duì)象的多態(tài)】

特點(diǎn)

1. 成員變量
編譯時(shí)和運(yùn)行時(shí)均只看引用變量所屬的類。
簡(jiǎn)單說(shuō),編譯和運(yùn)行都參考等號(hào)的左邊。

class Father
{
    int num = 3;
}
class Son extends Father
{
    int num = 4;
}
class Demo
{
    public static void main(String[] args)
    {
        Father f = new Son();
        System.out.println(f.num); //輸出3,覆蓋只發(fā)生在函數(shù)上;若Father中沒(méi)有num變量,會(huì)編譯失敗
    }
}

2. 成員函數(shù)(非靜態(tài))
編譯時(shí):要查看引用變量所屬的類中是否有所調(diào)用的成員(有->編譯通過(guò);沒(méi)有->編譯失?。?br> 運(yùn)行時(shí):要查看對(duì)象所屬的類中是否有所調(diào)用的成員。
簡(jiǎn)單說(shuō):編譯看等號(hào)左邊,運(yùn)行看等號(hào)右邊。

class Father
{
    void show()
    {
        System.out.println("father show");
    }
}
class Son extends Father
{
    void show()
    {
        System.out.println("son show");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Father f = new Son();
        f.show();
        //Father和Son中都有show(),調(diào)用Son的方法,輸出son show;
        //Father中沒(méi)有show(),Son中有,編譯失??;
        //Father中有show(),Son中沒(méi)有,調(diào)用Father的方法,輸出father show
    }
}

內(nèi)存圖解:

非靜態(tài)方法需要依賴對(duì)象。
動(dòng)態(tài)綁定:在執(zhí)行期間(非編譯期)判斷所引用對(duì)象的實(shí)際類型,根據(jù)其實(shí)際的類型調(diào)用其相應(yīng)的方法。程序運(yùn)行過(guò)程中,把函數(shù)(或過(guò)程)調(diào)用與響應(yīng)調(diào)用所需要的代碼相結(jié)合的過(guò)程稱為動(dòng)態(tài)綁定。
在Java中重寫(xiě)可以被認(rèn)為是動(dòng)態(tài)綁定的最佳示例,因?yàn)楦割惡妥宇惥哂邢嗤姆椒ā?也就是說(shuō),它不決定要調(diào)用的方法。

3. 靜態(tài)函數(shù)
編譯和運(yùn)行都參考等號(hào)的左邊。

class Father
{
    static void method(){
        System.out.println("father static method");
    }
}
class Son extends Father
{
    static void method(){
        System.out.println("son static method");
    }
}
class Demo
{
    public static void main(String[] args)
    {
        Father f = new Son();
        f.method();//輸出father static method
    }
}

靜態(tài)綁定:static,finalprivate方法的綁定是靜態(tài)綁定,都在編譯時(shí)完成,因?yàn)樗鼈儫o(wú)法被覆蓋,所以將始終由某個(gè)本地類的對(duì)象訪問(wèn),靜態(tài)綁定提供了更好的性能。

以下三種類型的方法是沒(méi)有辦法表現(xiàn)出多態(tài)特性的(因?yàn)椴荒鼙恢貙?xiě)):
1)static方法,因?yàn)楸籹tatic修飾的方法是屬于類的,而不是屬于實(shí)例的。
2)final方法,因?yàn)楸籪inal修飾的方法無(wú)法被子類重寫(xiě)。
3)private方法和protected方法,前者是因?yàn)楸籶rivate修飾的方法對(duì)子類不可見(jiàn),后者是因?yàn)楸M管被protected修飾的方法可以被子類見(jiàn)到,也可以被子類重寫(xiě),但是它是無(wú)法被外部所引用的,一個(gè)不能被外部引用的方法,怎么能談多態(tài)呢。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容