1. 程式人生 > >Java開發知識之Java的繼承多型跟介面*

Java開發知識之Java的繼承多型跟介面*

          Java開發知識之Java的繼承多型跟介面

一丶繼承

  1.繼承的寫法

  在Java中繼承的 關鍵字是 extends 代表一個類繼承另一個類.

繼承的含義以及作用: 繼承就是基於某個父類的擴充套件.制定出來的一個新的子類.子類可以繼承父類原有的屬性跟方法. 也可以自己增加特有的屬性跟方法.

程式碼例如下:

public class Anmail {
    public void eat() {
        System.out.println("父類吃");
    }

}
父類程式碼

子類程式碼:

public class Dog extends
Anmail { public void eat() { System.out.println("子類吃"); } }

通過上面程式碼.我們可以看到.子類 Dog類. 繼承了父類. 使用了關鍵字 extends

並且子類重寫了父類的方法.

  2.子類訪問父類的方法

  上面說了子類繼承父類.那麼子類也可以呼叫父類的方法. 我們學過this關鍵字. this可以區分區域性變數跟成員變數.也可以在構造中呼叫其它建構函式.

那麼我們還提供了一個關鍵字. super() super關鍵字可以訪問父類.

程式碼如下:

父類程式碼一樣.之帖子類程式碼.

public class Dog extends Anmail {
    public  Dog() {
        super(); //呼叫父類構造 呼叫構造的時候必須放在最上面.
        super.eat();//呼叫父類方法.
    }
    public void eat() {
        System.out.println("子類吃");
    }
}

建立子類物件.並且輸出.

可以看到.在呼叫構造的時候.他會先訪問父類的構造.但因為父類構造我們並沒有輸出內容.所以沒有輸出內容,子類繼續呼叫父類的eat方法. eat方法我們輸出了.

就是父類吃. 所以在子類構造的時候.會呼叫父類構造以及父類的方法.

  super()的關鍵字用法限制. super關鍵字只能呼叫父類中 公共許可權(public) 以及保護全選(protected)的方法

   3.重寫的概念

  子類可以重寫父類的方法. 什麼是重寫.就是子類跟父類的方法是一模一樣的. 也就是說,重寫是在子類跟父類中才會出現的. 返回值一樣. 方法名一樣. 引數一樣.

在J2SE 5.0 以上.支援了新的功能.也就是說返回值可以不一樣.但是 方法名 跟 引數必須一樣.

  JAVA 類編譯的流程. java中.建立子類的時候.會自動呼叫父類的構造方法進行初始化. 我們可以做個例子. 並且重寫一個方法.

public class Anmail {
    public Anmail() {
        System.out.println("父類構造方法");
    }
    public void eat() {
        System.out.println("父類吃");
    }

}

子類.

public class Dog extends Anmail {
    public  Dog() {
        
    }
    public void eat() {                //重寫父類方法
        System.out.println("子類吃");
    }
}

建立物件.

通過例項可以終結出. 1. 子類重寫了父類方法.輸出的內容是自己的"子類吃" 2.在給子類例項化的時候.會自動呼叫父類進行例項化操作.也就是說父類也會被初始化.

PS: 子類例項化的時候.會呼叫父類的無參構造進行例項化父類.但是並不會自動呼叫父類的有參構造.這個我們需要使用super關鍵字才可以.

二丶Object 類

  object類是一個比較特殊類的. 位於java.lang.包中. 它是所有類的父類.比如我們以前學習過字串類. String類. String類中 我們比較兩個物件是否相等就是用.

equleas()方法. 這個就是object類中的.只不過字串進行了重寫. 我們自定義的類也是繼承自object類.只不過是預設繼承. 所以任何類都可以重寫父類object中的方法.

在object類中 加了final類的方法是不能被重寫的. 例如 getClass() notify() notifyAll() wait()等等.

object類的方法介紹.

  1.getClass()方法

    getClass()方法會返回指定是的Class例項. 然後可以使用此時呼叫getName()獲得這個類的名稱. 

    getClass().getName(); 也可以配合toString()方法使用.

  2.toString()方法

    toString()方法就是返回一串字串.在object類中,就是講一個物件返回為字串形式.實際應用中就是重寫這個字串.返回什麼是你自定的.

  3.equals()方法;

    equals()方法就是比較.當時說過區別. 就是 == 與 equals()的區別. == 是比較地址. equals()是比較你自定的內容.也就是物件的實際內容.通常也是重寫.

比較什麼你自己定. 如果你寫了一個類有一個成員變數是 a; 我們重寫equals() 就判斷 a 跟 比較物件的a即可. 就是 a 跟 a比較.

三丶物件型別轉換.

    物件型別轉換.包括向上轉型.以及向下轉型. 通俗理解就是 強轉對應的資料型別.  但是你在強轉的時候要判斷一下是否是這個資料型別.這個就是轉型.

向上轉型以及向下轉型就是說 類我們強轉為父類.  也可以父類強轉為子類.

1.向上轉型

   子類物件賦值給父類物件稱為向上轉型.  Anmail a = new Dog(): 這個就是向上轉型.

比如我們有動物物件.  跟 狗物件.  狗物件可以看做是動物物件的一個子類. 

還比如 四邊形類 跟 平行四邊形類.  平行四邊形 物件可以看做是 四邊形類的一個物件.

如下圖:

  

常規的繼承圖都是父類在上. 子類在下.例如上圖.  所以我們將子類看做是父類物件的時候成為向上轉型.  也就是平行四邊形物件看做是四邊形類的物件的時候.

向上轉型是具體的類像抽象的類進行的轉換.所以它總是安全的. 我們可以說平行四邊形是特殊的四邊形. 但是不能是四邊形是平行四變形.

因為程式碼寫法:  四邊形 a = new 平行四變形(); 所以很多人就會說 a就是平行四邊形. 其實是錯的. a是四邊形. 我們只能說a平行四邊形是一個特殊的四邊形.

  如果在C++ 中.記憶體分配就是 父類佔一小塊記憶體. 子類上半部分是父類記憶體.下半部分是子類特有的成員變數開闢的記憶體. 子類轉為父類. 就是不要子類下邊的記憶體了.

所以總是安全的. 我只要上面的哪塊記憶體.也就是父類的記憶體.

所以在上邊. 子類轉為父類. 父類呼叫方法的時候.並不會呼叫到子類特有成員變數.

2.向下轉型

  向下轉型就是 抽象的類轉為具體的類. 比如 動物是鳥.  動物是抽象的.不能說他是鳥.所以不和邏輯.而且會出現問題.

比如父類 Anmail a = new Dog();

    Dog b = a;  這樣是錯誤的. 我們不能這樣賦值.原因就是不能說 動物是狗.

    Dog c = (Dog)a; 這樣可以.強轉為子型別.寫法是正確的.

站在C++的角度:

  為什麼上面向下轉型是錯誤的. 原因是 a 會有一塊記憶體. 我們可以假定為0x20個位元組大小. b是Dog也就是子類物件.他繼承了父類.有自己特有的成員方法

以及成員變數. 所以它的頭0x20個位元組是父類的記憶體.下面多出的記憶體是自己了.假設是0x30個位元組. 所以我們子類轉為父類(向上轉型)

其實就是把30個位元組的記憶體轉為20個位元組的記憶體.所以不會出問題. 但是 0x20個位元組.也就是父類轉為子類. 就會出為題. 意思就是說 0x20個位元組轉為0x30個位元組.

首先我們並不知道是轉為0x30個位元組.這樣記憶體訪問就會出錯了.但是如果我們強轉了.相當於就是父類在強轉為子類的時候.按照子類的記憶體強轉.這樣就不會有問題了.

也就是上面的我們的 Dog c  c的頭0x20個位元組是父類. 下面的0x10個位元組就是自己特有的所以不會出錯.

3.使用關鍵字判斷物件型別

    上面我們的父類轉為子類的時候.必須加上子類的資料型別才可以強轉. 原因就是轉為子類的時候.記憶體會按照子類的大小進行擴大.這樣就不會出現問題了.

但是我們怎麼知道 我們的子類.是否是這個父類的子類. 所以有了運算子  instanceof()來判斷.

就是判斷 子類是否是父類的. 父類中有沒有這個子類.如果有就進行轉換.

語法:

  Myobject instance ExampleClass

1.Myobject 就是某個類的物件引用. 可以理解為是父類填寫的是父類引用.

2.ExampleClass 某各類.                 可以理解為子類. 填寫類名.

例如:

  Anmail a = new Anmail();

if (a instanceof Dog)

{

    判斷父類中是否有子類Dog,如果有我就進行轉換.

    Dog d = (Dog) a;
    d.xxxx;
}

 

四丶方法過載.

  1.過載介紹.

  過載的含義. 過載就是可以有多個相同函式.過載的引數確定是否過載.

我們寫過有參構造跟無參構造. 方法名是一樣.不一樣的就是引數列表不同.

  過載的構成:

    1.過載的構成是方法名字一樣,

    2. 引數列表個數不同 

    3.  引數型別不同 

    4.引數列表順序不同

例如:

  public void eat(); 

  public void eat(String,int); 過載eat,引數是String,int

  public void eat(int,String); 引數順序不同.構成過載.

  public int eat(int,int);   返回值是int. 引數列表是兩個int值. 返回值也可以為void不影響過載.

PS: 方法的返回值並不會影響過載.真正影響的是引數列表. 你有兩個成員方法.方法命一樣.引數列表一樣.型別一樣.返回值不同.不能構成過載.

特殊的過載:

  Java中可以定義不定長的引數類表.

語法如下:

    返回值 方法名(引數資料型別 . . . 引數名稱) 主要是三個...

其實不定長引數.就是一個一維陣列.我們可以當做陣列去操作引數. 

程式碼如下:

  

    public void eat(int ... a) {
        for (int i = 0; i < a.length;i++)
        {
            .......
        }
    }

5.多型的概念

  在上面我們學過向上轉型.就是子類物件可以當做父類物件去使用. 其意思就是我可以當做父類物件去使用. 那麼就可以使用子類跟父類的共有的方法跟屬性了.

因為我們站在記憶體的角度上也說了.我們用的都是父類的記憶體.所以我們可以呼叫父類的方法.跟成員去使用. 但是多型是什麼意思.多型就是呼叫父類的方法的時候.

因為子類重寫了父類的方法.所以呼叫時會呼叫子類的特有方法.

例如:

  Anmail a = new Dog();

  a.eat()

輸出結果:  子類吃.  按理說應該輸出父類吃.不是說向上轉型了.我們用的都是父類記憶體了.子類就不該會被呼叫. 願意你是這樣了.如果站在C++角度來說.

我們首先會new一個子類的物件例項. 而new子類物件的時候.會先初始化父類. 在初始化自己. 為什麼說一下流程.原因是父類有虛表.也就是有一個表.儲存著

自己的方法.而子類在例項化的時候.父類的虛表先初始化. 初始化完了之後.子類的再初始化.它會先把父類的虛表拷貝過來.然後覆蓋他. 這樣我們a呼叫方法的時候

其實呼叫的是子類方法.原因就是子類的虛表已經覆蓋了父類虛表.

Java中的原理. java中其實也是一樣的.只不過給你隱藏了這個步驟了.不用理解的這麼複雜.我們只要知道.向上轉型之後.呼叫子類跟父類共有的方法.就能實現多型.

注意: 子類重寫了父類方法.那麼呼叫的時候才是子類的方法.原因是子類重寫了.才會覆蓋父類.

程式碼例子:

 

 多型的用法: 多型的好處就是程式設計師不同定義相同的方法了.避免了相同的大量重複程式碼的開發.只要例項化一個子類物件.維護這個方法即可.

再舉個例子;

我們手機. 有一代手機 二代手機 三代手機 四代手機 ...n代手機.

1代手機 只支援打電話

2代手機 可以發信息了.

3.手機   可以上網了

4.手機  可以拍照了.

此時我們只需要二代手機繼承1代手機對1代手機擴充套件功能. 比如增加發資訊的方法. 打電話的方法進行重寫.可以打電話.也可以錄音了. 而一代手機根本不用動方法.

此時我們的二代手機就可以出手了.第三代手機同樣繼承第二代手機.增加擴充套件功能.重寫維護的方法. 而不用修改二代的程式碼.

這樣不管我們有第幾代手機.只需要繼承上一代的類.進行擴充套件.以及維護即可.

 

6.抽象類

  抽象類就是說一個不可以被存在的類. 比如我們有動物 跟 狗. 而動物是不能被例項化物件的. 狗是一個具體的生物.我們可以例項化.

所以抽象就是指 動物. 也就是說我不能讓你被例項化.原因就是 動物泛指萬千.不能具體為一個動物.

定義抽象類的關鍵字

abstract

   使用abstract 修飾的類稱為抽象類.是不能例項化的.

  使用 abstract 修飾的方法.稱為抽象方法.是不能實現的.比如子類重寫. 修飾的方法沒有方法體.

 反過來說.如果一個類中修飾了成員方法.那麼就必須定義這個類為抽象類.

抽象類跟普通類一樣.只不過就是不能例項化. 必須要有子類繼承.如果有抽象方法.子類必須重寫. 

抽象類的繼承圖:

程式碼寫法,需要將我們的Anmail類寫成抽象類. 並且方法改為抽象方法

//public abstract class Anmail
abstract public   class  Anmail {

    abstract public void eat() ;
    public abstract void  play();
}

abstract 解除安裝許可權修飾符的前邊或者後邊都可以.不影響.例如類上面加了一行註釋.我們也可以寫成上面的寫法.以及抽象方法.也是兩個不同順序來舉例子

public class Dog extends Anmail {

    @Override
    public void eat() {
        // TODO 自動生成的方法存根
        System.out.println("狗在吃東西");
    }

    @Override
    public void play() {
        // TODO 自動生成的方法存根
        System.out.println("狗在玩耍");
    }

}

使用的時候.可以使用向上轉型.使用多型的方式.  子類可以自己new自己

在C++中.也有抽象類的概念.只不過稱之為純虛類. 他的方式就是這個類中的方法定義為純虛方法(抽象方法)

void eat() = 0; 後面加上 = 0; 跟介面很類似. 一般也是介面

七丶介面的含義

   介面就是抽象類的延伸.上面我們定義了一個抽象類.如果這個抽象類是一個父類.有很多子類.但是我們可以這樣想一下. 如果子類很多.都要實現這個抽象類中的方法.

這樣就造成了程式碼冗餘. 我們有的類完全可以不實現抽象類中的抽象方法啊.

比如  play方法. 我們每個子類都要實現.但是有的動物就不會玩我完全可以不實現了.但是按照抽象類.我們必須實現.所以就程式碼冗餘了.

此時介面就出現了. 介面就是說 . 我介面中的方法都是抽象方法. 你要實現play. 就可以實現我這個介面. 如果不需要玩的話.就不用實現我這個介面

例如下圖:

 

 可以看到我們的N邊型類.並沒有實現介面.我完全可以不用實現Draw方法.雖然我繼承了父類.

 

介面的定義與使用:

  1.介面使用intface關鍵字 修飾. 可以使用許可權修飾符修飾

  2.介面的實現使用關鍵字 implements關鍵字

  3.介面中的方法都是抽象方法.而且都沒有方法體. 且預設加的關鍵字就是abstract 而且許可權必須是public因為要被實現.其它許可權修飾則不被編譯器認可.

  4.在繼承一個類同時.可以實現介面.

  5.在介面中定義的 欄位(成員變數)都是預設修飾符 static + final修飾的. 也就是說一個靜態常量.

程式碼如下:

  定義一個介面

public interface IAnmail {
    
    public abstract void Play(); //定義一個抽象方法
}

介面的實現.

  

public class Dog extends Anmail implements IAnmail{

    @Override
    public void eat() {
        // TODO 自動生成的方法存根
        System.out.println("狗在吃東西");
    }

    @Override
    public void Play() {            //實現了介面必須實現他的方法
        // TODO 自動生成的方法存根
        System.out.println("狗在玩耍");
    }


}

八丶總結

  1.類的繼承

    類的繼承使用 extends關鍵字

    注意的問題:

      1.不能多繼承.

      2.子類繼承父類自動用於父類的方法以及成員變數

      3.子類在構造中可以呼叫父類構造.(無參或者有參) 使用的是 super();關鍵字. 也可以呼叫父類方法 super.xxxx

  2.公共父類Object

    在Java中每個類都繼承了父類Object. Object中提供了常用的方法

  比較:  equeals();

  轉為字串表現形式 toString();

  這些方法都是可以被重寫的.除了加了 final修飾的.

 

  3.物件的型別轉換.

   在Java中有向上轉型跟向下轉型

    1.向上轉型:  子類轉為父類.  Anmail d = new Dog(): 安全的.因為在記憶體角度來說.使用的部分只有父類部分記憶體.

    2.向下轉型: 父類轉為子類  Dog c = (Dog) d; 必須加上型別.父類記憶體轉成子類記憶體,比如給指定子類記憶體大小.

    3.物件型別判斷 使用 instanceof 

    語法  父類引用 instance 子類類名  判斷父類是否有這個子類

    如果是我們就可以進行轉換了.

  4.類中的方法重寫與過載

    重寫: 

      1.返回值相同

      2.方法名相同  

      3.引數列表相同

    滿足以上三點才能是重寫.也就是子類跟父類一模一樣才是重寫.

    過載:

       1.方法名一樣

        2.引數列表個數不同

       3.引數列表型別不同

        4.引數列表順序不同.

    滿足以上四點才能構成過載.

    特殊過載可以使用 三個點定義為可變引數  public void eat(int ...a); 此時a就是一個數組.我們可以遍歷他來獲取引數.

  5.多型

   多型就是父類可以呼叫子類跟父類共有的方法.比如是子類重寫父類才會有不同的結果.

  使用向上轉型

    Anmai a = new Dog(); a.eat() ;  狗在吃東西.

  記憶體角度來說. 就是虛表覆蓋.

  6.抽象類

    抽象類使用 abstract關鍵字進行修飾

    public abstract class 類名{}'

    1.修飾類就是抽象類,不可以被例項化.就是不能建立物件

    2.修飾方法就是抽象方法,沒有方法體.子類繼承比如重寫.

    3.一個類中有抽象方法.則這個類必須定義為抽象類.

    

  7.介面

    介面定義的關鍵字  Intface    public intfact 介面名{}

    實現介面的關鍵字 implements   public class Anmail Implements 介面名

  1.介面中的方法都是抽象方法. 且預設修飾符為 public abstract 許可權必須是public 否則編譯器不認可.

  2.介面中的成員變數都是預設的 static final 修飾的.