2.6Java繼承(抽象類,接口,final)

1.繼承:extends
2.好處:

1.提高了代碼的複用性
2.讓類與類之間產生關係,給第三個特徵多態提供了前提。沒繼承沒多態。

開發的核心內容:創建,使用對象,維護對象之間的關係。

3.舉例:

//繼承extends
class Person  //父類,超類,基類...
{
    String name;
    int age;
}

class Student extends Person    //子類
{

    void study()
    {
            System.out.println(name + "...Student study." + age);
    }
}

class Worker extends Person
{

    void work()
    {
        System.out.println("Worker work.");
    }
}

class Test
{
    public static void main(String[] args)
    {
        Student s = new Student();
        s.name = "zhangsan";
        s.age = 22;
        s.study();
    }
}

4.單繼承,多繼承:
1)Java中支持單繼承,不直接支持多繼承,但多C++中的多繼承機制進行了改良。
2)單繼承:一個子類只能有一個直接父類。
多繼承:一個子類可以有多個直接父類。(Java中不允許,進行改良)
不直接支持,因爲多個父類中有相同成員,會產生調用不確定性。
在Java中是通過“多實現”的方式來體現。

class A{
    void show(){
        syso("a");
    }
}
class B{
    void show(){
        syso("a");
    }
}
class C extends A,B{}  //多繼承出現調用不確定性,所以Java不支持

5.Java支持多重(多層)繼承:
C繼承B,B繼承A
就會出現繼承體系。

當要使用一個繼承體系(集合,io也一樣)時:
1.查看該體系的頂層類,瞭解該體系的基本功能。
2.創建體系中的最子類對象,完成功能的使用。

6.注意

1.不要爲了代碼複用性而去使用繼承,繼承是依賴關係的。
2.什麼時候定義繼承:
    當類與類之間存在所屬關係的時候,就定義繼承。xxx是yyy的一種。
    所屬關係is a

7.在子父類中,成員的特點體現:

    1.成員變量
    2.成員函數
    3.構造函數

1)成員變量:

    當本類的成員變量和局部變量同名用this區分。
    當子父類中的成員變量同名用super區分父類.

    thissuper的用法很相似。

    this:代表一個本類對象的引用
    super:代表一個父類的空間。
class Person
{
    int num = 4;
}

class Student extends Person   //存在super=父類的空間(存儲在子類堆中)。
{
    int num = 5;         //在開發中幾乎沒有,父類中定的共性內容,在子類中無需定義。但是面試的時候多見。
    void show()
    {
        System.out.println(this.num + "..." +super.num);   //同名時要寫super,不同名則默認添加
                                        //子類不能直接訪問父類的私有內容,可以在父類中定義接口函數set,get讓子類可以間接訪問私有內容。
    }
}

class Worker extends Person
{

}

class Test
{
    public static void main(String[] args)
    {
        Student s = new Student();
        s.show();
    }
}

內存分析:
父類先加載到方法區,父類沒有對象,它的成員變量存儲在子類堆中,和子類成員分開存儲。
super內存分析

子類不能直接訪問父類的私有內容,可以在父類中定義接口函數setget讓子類可以間接訪問私有內容。

2)成員函數:
覆蓋:

    當子父類中出現成員函數一模一樣的情況,會運行子類的函數。
    這種現象,稱爲覆蓋操作,這是函數在子父類中的特性。
    一模一樣:函數聲明一模一樣

    函數有兩個特性:
        1.重載:同一個類中。和返回值無關
        2.覆蓋:子類中。也稱重寫,覆寫。

覆蓋注意事項:
1.子類方法覆蓋父類方法時,子類權限必須大於等於父類的權限。(public default private)
2.當父類方法設置爲私有,則不叫覆蓋操作。因爲父類方法就不能用。就是子類自己的新方法。
3.靜態只能覆蓋靜態,或是被靜態覆蓋。
什麼時候使用覆蓋:
當對一個類進行子類的擴展時,子類需要保留父類的功能聲明,但是要對定義子類中該功能的特有功能時,就使用覆蓋操作完成。

例子:

class Phone
{
    void show
    {
        System.out.println("number");
    }
}

class NewPhone extends Phone
{
    void show()     //覆蓋
    {
        System.out.println("name");
        System.out.println("pic");
        super.show();   //代碼複用
    }
}

3)構造函數:

在子類構造對象時,發現,訪問子類構造函數時,父類也運行了。
原因:
    在子類的構造函數中第一句有一個默認的隱式語句:super();
    當構造函數調用了其他初始化構造函數的語句,必須寫在第一行。
子類的實例化過程:
    子類中所有的構造函數默認都會訪問父類中的空參數的構造函數。
class Fu
{
    fu(int x)
    {
        System.out.println("fu run");
    }
}
class Zi extends Fu
{
    Zi()   
    {
        //super();       //隱式語句,調用父類中的空參數的構造函數,所以這時父類空參必須存在。除非自己調用父類有參構造函數
        super(4);
        System.out.println("zi run");
    }
}

class Test
{
    public static void main(String[] args)
    {
        Student s = new Student();
        s.show();   
    }
}

爲什麼子類實例化的時候必須訪問父類中的構造函數:

因爲子類繼承了父類,獲取到了父類中的內容(屬性),所以在使用父類內容之前,要先看父類是如何對自己的內容進行初始化的。

爲了完成這必須的動作,就在子類的構造函數中就入了super()語句。

如果父類中沒有定義空參數構造函數,那麼子類的構造函數必須用super明確要調用父類的哪個構造函數。

注意:super語句必須要定義在子類構造函數的第一行。因爲父類的初始化要先完成。

同時子類構造函數中如果使用this調用了本類的構造函數時,那麼super就沒有了,因爲superthis都只能定義在第一行。所以只能有一個。但是可以保證的是,子類中肯定會有其他構造函數訪問父類的構造函數。

例子:

class Fu
{
    fu(int x)
    {
        System.out.println("fu run");
    }
}
class Zi extends Fu
{
    Zi()   
    {
        //super(); 
        super(4);   //調用父類構造方法
        System.out.println("zi run");
    }
    Zi(int x)
    {
        this();   //此時沒有默認添加super(); 但是在調用的Zi()函數裏還是會調用super(),其實父類的構造方法至少會被調用一次。
        System.out.println("zi(x) run");
    }
}

8.Object類:是所有的類的直接或者間接父類,是根類。

class Demo //extends Objects
{
/*
    Demo();
    return;
*/
}

9.構造函數-子類的實例化過程 圖解:

重點:子類構造方法向父類進行構造方法的調用,將一些共性初始化內容放在父類的構造函數中。

過程:
Person p = new Person();

1.JVM讀取指定路徑下的person.class文件,並加載進內存,並會先加載Person的父類(如果有直接父類的情況下)。
2.在堆內存中開闢空間,分配內存地址
3.並在對象空間中,對對象中的屬性進行默認初始化。
4.調用對應的構造函數進行初始化。
5.在構造函數中,第一行先調用父類中構造函數進行初始化。
6.父類初始化完畢後,再對子類的屬性進行顯示初始化。
7.再進行子類構造函數的特定初始化。
8.初始化完畢後,將地址值賦值給引用變量。

子類成員變量 默認 初始化(int 爲 0)後,調用子類構造器(this)進棧,由第一行的super(),到了父類構造器(子類this)進棧,父類構造器初始化完畢出棧,子類成員變量再完成顯示初始化,然後子類構造器完成特定初始化,將地址值賦值給引用變量。

簡單點:

子類構造器加載,父類構造器加載完成,子類成員變量顯示初始化,子類加載器繼續完成。

class Fu
{
    Fu()
    {   //super();
        (this.)show();  //this指向的是子類
    }
    void show()
    {
        System.out.println("fu show");
    }
}

class Zi extends Fu
{
    int num = 8;     
    Zi()   //分水嶺
    {
        //super();   // 通過super初始化父類內容時,子類的成員變量並未初始化。等super()父類初始化完畢後,才進行子類的成員變量顯示初始化。
        System.out.println(num)
        //return;
    }
    void show()
    {
        System.out.println("zi show..." + num);
    }
}

class ExtendsDemo
{
    public static void main(String[] args)
    {
        Zi z = new Zi();  //輸出zi show...0,因爲此時父類構造器還沒有結束,而num的顯示初始化過程是再父類構造器初始化結束後進行的,所num的默認值爲0,然後在父類初始化的過程中,自帶的子類this回調了子類的show()函數,如果子類沒有show()函數,則再找父類的show()函數。
        z.show(); //輸出:
        //8      //父類構造器初始化完畢後,然後進行num的顯示初始化,再繼續執行子類的構造器,輸出num爲8
        //zi...8    對象創建完畢,輸出特有數據num爲8

    }
}

子類實例化過程

10.關鍵字final: 修飾符,最終化。
繼承的弊端:打破了封裝性。

1.final可以修飾類,方法,變量
2.final修飾的類不可以繼承
3.final修飾的方法不可以被覆蓋

4.final修飾的變量是一個常量,只能賦值一次。
  ①使用final和直接使用9區別在於,可以取常量名,加強閱讀性。
  ②final只固定變量的顯示初始化值,所以必須顯示賦值。
  ③一般都會加靜態static。如果再加publicpublic static final int x = 4;   這稱爲全局常量。
爲什麼使用final:
    其實程序如果一個數值是固定的,那麼直接使用這個數據就可以了,但是這樣閱讀性差,所以它該給數據起個名稱。而這個變量名稱的值不能變化,所以加上final固定。
命名規範:
1.變量名:首個單詞小寫,後面的每個單詞的首字母大寫
2.函數名:首個單詞小寫,後面的每個單詞的首字母大寫()
3.類名:每個單詞的首字母都大寫。
4.常量名:每個字母都大寫,單詞之間用下劃線分開。
final class Fu   //不能被繼承
{
    final void method()
    {
        //調用了底層系統的資源。
    }
}

class Zi extends Fu
{
    void method()
    {
        final int x = 9;  //之後無法再賦值,使用final和直接使用9區別在於,可以取常量名,加強閱讀性。
        System.out.println("haha");// 打破了父類的封裝性
    }
}

11.抽象類:通過子類繼承抽象類,去調用裏面的非抽象方法。
抽象:籠統,模糊,看不懂。不具體。

特點:
1. 方法只有聲明沒有實現時,該方法就是抽象方法,需要被abstract修飾
    抽象方法必須定義再抽象類中,該類必須被abstract修飾。
2.抽象類不可以被實例化,因爲調用抽象方法沒有意義。
3.抽象類必須有其子類覆蓋了所有的抽象方法後,子類纔可以實例化。
    否者這個子類還是抽象類。

細節:
1.抽象類中的構造函數用於給子類對象進行初始化。
2.抽象類中可以不定義抽象方法。少見,目的就是不讓該類創建對象。AWT的適配器對象就是這種類。
    通常這個類中的方法有方法體,但是沒有內容。

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

3. private 不可以和abstract組合:因爲子類要覆蓋抽象類中的所有方法。
    static 不可以和 abstract組合:沒有任何意義
    final  不可以和 abstract組合:final不可以被覆蓋。

4.抽象類和一般類的異同點:
    相同點:都是用來描述事物,都在內定義了成員。
    不同點:
        1.一般類有足夠的信息描述事物。
          抽象類描述事物的信息可能不足。
        2.一般類中不能定義抽象方法,只能定義非抽象方法。
          抽象類都可以。
        3.一般類可以被實例化。
          抽象類不可以被實例化。

5.抽象類一定是個父類,因爲需要子類覆蓋其方法後纔可以對子類實例化。

例子:

/*抽象類練習:

僱員示例:

需求:公司中程序員有姓名,工號,薪水,工作內容。
            項目經理除了有姓名,工號,薪水,工作內容,還有獎金。

對給出需求進行數據建模。

分析:
找出設計的對象:
程序員:
            屬性:姓名,工號,薪水
            行爲:工作內容

經理:
            屬性:姓名,工號,薪水,獎金
            行爲:工作內容

程序員和經理不存在着直接繼承關係,但具有共性內容,可以進行抽取,都是公司的僱員。

可以將程序員和經理進行抽取,建立體系。
*/

//描述僱員

abstract class Employee
{
    private String name;
    private String id;
    private double pay;
    Employee(String name,String id, double pay)
    {
            this.name = name;
            this.id = id;
            this.pay = pay;
    }
    public void tell()
    {
            System.out.println(name + " : " + id + " " + pay + " RMB/month " );
    }

     public abstract void work();
}


//描述程序員

class Programmer extends Employee
{
    Programmer(String name,String id, double pay)
    {
        super(name,id,pay);   //父類構造函數調用。
    }
    public void work()
    {
            System.out.println("code...");
    }
}

//描述經理
class Manager extends Employee
{
    private int bonus;
    Manager(String name,String id, double pay,int bonus)
    {
            super(name,id,pay);
            this.bonus = bonus;
    }

    public void work()
    {
            System.out.println("manage");
    }
}




class AbstractDemo2
{
    public static void main(String[] args)
    {
            Programmer p = new Programmer("czq","001",20000);
            p.tell();
            p.work();
    }
}

12.接口interface:不通過繼承,通過實現 implements。

1.類與類之間是繼承關係。
2.類與接口之間是實現關係。
3.接口與接口之間是繼承關係,而且接口可以多繼承,多繼承的關鍵是有沒有方法體。

1.當一個抽象類中的方法都是抽象的時候,這時可以將該抽象類用另一種形式給定義和表示,就是接口 interface。

2.注意點:

1.定義接口使用的關鍵字不是class,是interface

2.對於接口當中常見的成員,而且這些成員都有固定的修飾符(可以不寫,但是閱讀性極差):
    1.全局常量  public static final 
    2.抽象方法  public abstract 

接口中的成員都是公共的權限。

3.接口不可以實例化。
    只能由實現了接口的子類並覆蓋了接口中所有的抽象方法後,該子類纔可以實例化。
    否則,這個子類就是一個抽象類。

4.在Java中不直接支持多繼承,因爲會出現調用的不確定性。
所以Java將多繼承繼承進行改良,在java中變成了多實現。
一個類可以實現多個接口。

5.一個類在繼承另一個類的同時,還可以實現多個類。
接口的出現避免了單繼承的侷限性。
6.接口與接口之間是繼承關係,而且接口可以多繼承,多繼承的關鍵是有沒有方法體。
interface Demo
{
    public static final int NUM = 4;

    public abstract void show1();
    public abstract void show2();
}

class DemoImpl implements Demo
{
        public  void show1()
    {

        }
        public void show2()
    {

        }
}


/*
在Java中不直接支持多繼承,因爲會出現調用的不確定性。
所以Java將多繼承繼承進行改良,在java中變成了多實現。

一個類可以實現多個接口。
*/

//多實現解決了多繼承的不確定性

interface A
{
    public void show();
}

interface Z
{
    public void show();   //如果改成int,出現漏洞
}

class Test implements A,Z
{
    public void show()    //同時覆蓋,不存在繼承的不確定性
    {

    }
}


/*
        一個類在繼承另一個類的同時,還可以實現多個類。

        接口的出現避免了單繼承的侷限性。
*/

class   Q
{
    public void method()
        {

    }
}

class Test2 extends Q implements A,Z
{

}




class InterfaceDemo
{
    public static void main(String[] arfs)
    {
            DemoImpl d = new DemoImpl();
            System.out.println(d.NUM);
            System.out.println(DemoImpl.NUM);
            System.out.println(Demo.NUM);

    }
}

5.接口和抽象類的區別:

相同點:都是不斷向上抽取而來的。

不同點:
1.抽象類需要被繼承,而且只能單繼承
接口需要被實現,而且可以多實現。
2.抽象類中可以定義抽象方法和非抽象方法,子類繼承後,可以直接使用非抽象方法。
接口中只能定義抽象方法,必須由子類去實現。
3.抽象類的繼承是 is a 關係,在定義該體系的基本共性內容。
接口的實現是 like a 關係,在定義體系的額外功能。
例子:

犬按功能分:導盲犬,搜爆犬

abstract class 犬   //基本功能用類定義
{
    abstract void 吼叫();
}
interface 導盲   //導盲是額外功能,用接口
{
    abstract void 導盲();
}

class 導盲犬 extendsimplements 導盲
{
    public void 吼叫(){}
    public void 導盲(){}
}

4.接口的特點:

①接口是對外暴露的規則。
②接口是程序的功能擴展。
③接口的出現降低耦合性。

接口可以用來多實現。
類與接口之間是實現關係,而且類可以繼承一個類的同時實現多個接口。
接口和接口之間可以有繼承關係,可以多繼承

5.接口的應用:
接口:
1.定義規則,使用規則。
2.實現規則

/*
筆記本電腦使用。
爲了擴展筆記本的功能,但日後出現什麼功能設備不知道。

定義一個規則,只要然後出現的設備都符合這個規則就可以了。
規則在Java中就是接口。
*/

interface USB   //定義規則,①暴露的規則
{
    public void open();
    public void close();
}

class BookPC
{
    public static void main(String[] args)
    {
        useUSB(new UPan());      //②功能擴展,插入U盤,並使用,
        useUSB(new UsbMouse());    
    }
    public static void useUSB(USB u) //**使用規則,傳入一個接口類型的引用變量,用於接收(指向)接口的子類對象。**      //多態的形式。
                                        {
        u.open();
        u.close();
    }
}

//一年後。
//③這些設備和電腦的耦合性降低了,解耦
class UPan implements USB   //實現規則,U盤要符合USB規則
{
    public void open()
    {
         System.out.println("upan open");
    };
    public void close()
    {
        System.out.println("upan close");
    };
}





/*
//不知道是什麼設備,所以應該用接口來實現。
class Mouse
{   
}

class BookPC
{
    public static void main(String[] args)
    {
    }
    public static void useMouse(Mouse m)
    {
        m.open();
    }
}*/
發佈了36 篇原創文章 · 獲贊 0 · 訪問量 4586
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章