1. 程式人生 > >java抽象類、多型、介面

java抽象類、多型、介面

抽象類

抽象類的產生

 

當編寫一個類時,我們往往會為該類定義一些方法,這些方法是用來描述該類的功能具體實現方式,那麼這些方法都有具體的方法體。

但是有的時候,某個父類只是知道子類應該包含怎麼樣的方法,但是無法準確知道子類如何實現這些方法。比如一個圖形類應該有一個求周長的方法,但是不同的圖形求周長的演算法不一樣。那該怎麼辦呢?

分析事物時,發現了共性內容,就出現向上抽取。會有這樣一種特殊情況,就是方法功能宣告相同,但方法功能主體不同。那麼這時也可以抽取,但只抽取方法宣告,不抽取方法主體。那麼此方法就是一個抽象方法。

描述JavaEE工程師:行為:工作。

描述Android工程師:行為:工作。

JavaEE工程師和Android工程師之間有共性,可以進行向上抽取。抽取它們的所屬共性型別:研發部員工。由於JavaEE工程師和Android工程師都具有工作功能,但是他們具體工作內容卻不一樣。這時在描述研發部員工時,發現了有些功能(工作)不具體,這些不具體的功能,需要在類中標識出來,通過java中的關鍵字abstract(抽象)。

當定義了抽象函式的類也必須被abstract關鍵字修飾,被abstract關鍵字修飾的類是抽象類。

抽象類、抽象方法的定義

抽象類定義的格式:

abstract class 類名 {
}

抽象方法定義的格式:

public abstract
返回值型別 方法名(引數);

如下程式碼

//研發部員工 
abstract class Developer {
    public abstract void work();//抽象函式。需要abstract修飾,並分號;結束
}

//JavaEE工程師
class JavaEE extends Developer{
    public void work() {
        System.out.println("正在研發淘寶網站");
    }
}

//Android工程師
class Android extends Developer {
    public
void work() { System.out.println("正在研發淘寶手機客戶端軟體"); } }
View Code

抽象類的特點

1、抽象類和抽象方法都需要被abstract修飾。抽象方法一定要定義在抽象類中。

2、抽象類不可以直接建立物件,原因:呼叫抽象方法沒有意義。

3、只有覆蓋了抽象類中所有的抽象方法後,其子類才可以建立物件。否則該子類還是一個抽象類。

之所以繼承抽象類,更多的是在思想,是面對共性型別操作會更簡單。

抽象類的細節問題:

1、抽象類一定是個父類?

                  是的,因為不斷抽取而來的。

2、抽象類中是否可以不定義抽象方法。

                 是可以的,那這個抽象類的存在到底有什麼意義呢?不讓該類建立物件,方法可以直接讓子類去使用

3. abstract不能和哪些關鍵字共存

 abstract 和static

被abstract修飾的方法沒有方法體

被static修飾的可以用類名.呼叫,但是類名.呼叫抽象方法是沒有意義的

abstract和final

被abstract修飾的方法強制子類重寫

被final修飾的不讓子類重寫,所以他們是矛盾

abstract和private

被abstract修飾的是為了讓子類看到並強制重寫
被private修飾不讓子類訪問,所以他兩是矛盾

介面

概念:

介面是功能的集合,同樣可看做是一種資料型別,是比抽象類更為抽象的”類”。

介面只描述所應該具備的方法,並沒有具體實現,具體的實現由介面的實現類(相當於介面的子類)來完成。這樣將功能的定義與實現分離,優化了程式設計。

請記住:一切事物均有功能,即一切事物均有介面。

定義:

與定義類的class不同,介面定義時需要使用interface關鍵字。

定義介面所在的仍為.java檔案,雖然宣告時使用的為interface關鍵字的編譯後仍然會產生.class檔案。這點可以讓我們將介面看做是一種只包含了功能宣告的特殊類。

定義格式:

public interface 介面名 {
抽象方法1;
抽象方法2;
抽象方法3;
}

使用interface代替了原來的class,其他步驟與定義類相同:

  •   介面中的方法均為公共訪問的抽象方法
  •   介面中無法定義普通的成員變數

類實現介面

類與介面的關係為實現關係,即類實現介面。實現的動作類似繼承,只是關鍵字不同,實現使用implements。

其他類(實現類)實現介面後,就相當於宣告:”我應該具備這個介面中的功能”。實現類仍然需要重寫方法以實現具體的功能。

格式:

classimplements 介面 {
    重寫介面中方法
} 

在類實現介面後,該類就會將介面中的抽象方法繼承過來,此時該類需要重寫該抽象方法,完成具體的邏輯。

  •   介面中定義功能,當需要具有該功能時,可以讓類實現該介面,只聲明瞭應該具備該方法,是功能的宣告。
  •   在具體實現類中重寫方法,實現功能,是方法的具體實現。

於是,通過以上兩個動作將功能的宣告與實現便分開了。( 類是現實事物的描述,介面是功能的集合。)

介面中成員的特點

1、介面中可以定義變數,但是變數必須有固定的修飾符修飾,public static final 所以介面中的變數也稱之為常量,其值不能改變。

2、介面中可以定義方法,方法也有固定的修飾符,public abstract

3、介面不可以建立物件。

4、子類必須覆蓋掉介面中所有的抽象方法後,子類才可以例項化。否則子類是一個抽象類。

interface Demo { ///定義一個名稱為Demo的介面。
    public static final int NUM = 3;// NUM的值不能改變
    public abstract void show1();
    public abstract void show2();
}

//定義子類去覆蓋介面中的方法。類與介面之間的關係是 實現。通過 關鍵字 implements
class DemoImpl implements Demo { //子類實現Demo介面。
    //重寫介面中的方法。
    public void show1(){}
    public void show2(){}
}

介面的多實現

介面最重要的體現:解決多繼承的弊端。將多繼承這種機制在java中通過多實現完成了。

interface Fu1
{
    void show1();
}
interface Fu2
{
    void show2();
}
class Zi implements Fu1,Fu2// 多實現。同時實現多個介面。
{
    public void show1(){}
    public void show2(){}
}
View Code

怎麼解決多繼承的弊端呢?

弊端:多繼承時,當多個父類中有相同功能時,子類呼叫會產生不確定性。

其實核心原因就是在於多繼承父類中功能有主體,而導致呼叫執行時,不確定執行哪個主體內容。

為什麼多實現能解決了呢?

因為介面中的功能都沒有方法體,由子類來明確。

類繼承類同時實現介面

介面和類之間可以通過實現產生關係,同時也學習了類與類之間可以通過繼承產生關係。當一個類已經繼承了一個父類,它又需要擴充套件額外的功能,這時介面就派上用場了。

子類通過繼承父類擴充套件功能,通過繼承擴充套件的功能都是子類應該具備的基礎功能。如果子類想要繼續擴充套件其他類中的功能呢?這時通過實現介面來完成。

class Fu {
    public void show(){}
}
interface Inter {
    pulbic abstract void show1();
}
class Zi extends Fu implements Inter {
    public void show1() {
    }
}
View Code

介面的出現避免了單繼承的侷限性。父類中定義的事物的基本功能。介面中定義的事物的擴充套件功能。

介面的多繼承

類與類之間可以通過繼承產生關係,介面和類之間可以通過實現產生關係,那麼介面與介面之間會有什麼關係。

多個介面之間可以使用extends進行繼承。

interface Fu1{
    void show();
}
interface Fu2{
    void show1();
}
interface Fu3{
    void show2();
}
interface Zi extends Fu1,Fu2,Fu3{
    void show3();
}
View Code

在開發中如果多個介面中存在相同方法,這時若有個類實現了這些介面,那麼就要實現介面中的方法,由於介面中的方法是抽象方法,子類實現後也不會發生呼叫的不確定性。

 介面的思想

舉例:我們都知道電腦上留有很多個插口,而這些插口可以插入相應的裝置,這些裝置為什麼能插在上面呢?主要原因是這些裝置在生產的時候符合了這個插口的使用規則,否則將無法插入介面中,更無法使用。發現這個插口的出現讓我們使用更多的裝置。

總結:介面在開發中的它好處

1、介面的出現擴充套件了功能。

2、介面其實就是暴漏出來的規則。

3、介面的出現降低了耦合性,即裝置與裝置之間實現瞭解耦。

介面的出現方便後期使用和維護,一方是在使用介面(如電腦),一方在實現介面(插在插口上的裝置)。例如:筆記本使用這個規則(介面),電腦外圍裝置實現這個規則(介面)。

 介面和抽象的區別

通過例項進行分析和程式碼演示抽象類和介面的用法。

1、舉例:

犬:

     行為:

               吼叫;

               吃飯;

緝毒犬:

     行為:

              吼叫;

              吃飯;

              緝毒;

程式碼如下

interface 緝毒{
    public abstract void 緝毒();
}
//定義犬科的這個提醒的共性功能
abstract class 犬科{
public abstract void 吃飯();
public abstract void 吼叫();
}
// 緝毒犬屬於犬科一種,讓其繼承犬科,獲取的犬科的特性,
//由於緝毒犬具有緝毒功能,那麼它只要實現緝毒介面即可,這樣即保證緝毒犬具備犬科的特性,也擁有了緝毒的功能
class 緝毒犬 extends 犬科 implements 緝毒{

    public void 緝毒() {
    }
    void 吃飯() {
    }
    void 吼叫() {
    }
}
class 緝毒豬 implements 緝毒{
    public void 緝毒() {
    }
}
View Code

通過上面的例子總結介面和抽象類的區別:

相同點:

  •  都位於繼承的頂端,用於被其他類實現或繼承;
  •   都不能直接例項化物件;
  •  都包含抽象方法,其子類都必須覆寫這些抽象方法;

區別:

  •   抽象類為部分方法提供實現,避免子類重複實現這些方法,提高程式碼重用性;介面只能包含抽象方法;
  • 一個類只能繼承一個直接父類(可能是抽象類),卻可以實現多個介面;(介面彌補了Java的單繼承)
  • 抽象類是這個事物中應該具備的你內容, 繼承體系是一種 is..a關係
  •   介面是這個事物中的額外內容,繼承體系是一種 like..a關係

二者的選用:

  •   優先選用介面,儘量少用抽象類;
  •   需要定義子類的行為,又要為子類提供共性功能時才選用抽象類;

多型

概述:

多型是繼封裝、繼承之後,面向物件的第三大特性。

現實事物經常會體現出多種形態,如學生,學生是人的一種,則一個具體的同學張三既是學生也是人,即出現兩種形態。                                                                                                                

Java作為面向物件的語言,同樣可以描述一個事物的多種形態。如Student類繼承了Person類,一個Student的物件便既是Student,又是Person。

Java中多型的程式碼體現在一個子類物件(實現類物件)既可以給這個子類(實現類物件)引用變數賦值,又可以給這個子類(實現類物件)的父類(介面)變數賦值。

如Student類可以為Person類的子類。那麼一個Student物件既可以賦值給一個Student型別的引用,也可以賦值給一個Person型別的引用。

最終多型體現為父類引用變數可以指向子類物件。

多型的前提是必須有子父類關係或者類實現介面關係,否則無法完成多型。

在使用多型後的父類引用變數呼叫方法時,會呼叫子類重寫後的方法。

定義和使用格式

多型的定義格式:就是父類的引用變數指向子類物件

父類型別  變數名 = new 子類型別();
變數名.方法名();
  •   普通類多型定義的格式
父類 變數名 = new 子類();
如:    class Fu {}
    class Zi extends Fu {}
    //類的多型使用
Fu f = new Zi();
  •   抽象類多型定義的格式
抽象類 變數名 = new 抽象類子類();
如:    abstract class Fu {
         public abstract void method();
   }
class Zi extends Fu {
public void method(){
              System.out.println(“重寫父類抽象方法”);
}
}
//類的多型使用
Fu fu= new Zi();
  •   介面多型定義的格式
介面 變數名 = new 介面實現類();
如: interface Fu {
             public abstract void method();
}
class Zi implements Fu {
             public void method(){
              System.out.println(“重寫介面抽象方法”);
}
}
//介面的多型使用
Fu fu = new Zi();
  •  注意事項

同一個父類的方法會被不同的子類重寫。在呼叫方法時,呼叫的為各個子類重寫後的方法。

如 Person p1 = new Student();
   Person p2 = new Teacher();
   p1.work(); //p1會呼叫Student類中重寫的work方法
   p2.work(); //p2會呼叫Teacher類中重寫的work方法

多型成員的特點

掌握了多型的基本使用後,那麼多型出現後類的成員有啥變化呢?前面學習繼承時,我們知道子父類之間成員變數有了自己的特定變化,那麼當多型出現後,成員變數在使用上有沒有變化呢?

多型出現後會導致子父類中的成員變數有微弱的變化。看如下程式碼

class Fu {
    int num = 4;
}
class Zi extends Fu {
    int num = 5;
}
class Demo {
    public static void main(String[] args)     {
        Fu f = new Zi();
        System.out.println(f.num);
        Zi z = new Zi();
        System.out.println(z.num);
    }
}
View Code
  •   多型成員變數

當子父類中出現同名的成員變數時,多型呼叫該變數時:

編譯時期:參考的是引用型變數所屬的類中是否有被呼叫的成員變數。沒有,編譯失敗。

執行時期:也是呼叫引用型變數所屬的類中的成員變數。

簡單記:編譯和執行都參考等號的左邊。編譯執行看左邊。

多型出現後會導致子父類中的成員方法有微弱的變化。看如下程式碼

class Fu {
    int num = 4;
    void show()    {
        System.out.println("Fu show num");
    }
}
class Zi extends Fu {
    int num = 5;
    void show()    {
        System.out.println("Zi show num");
    }
}
class Demo {
    public static void main(String[] args)     {
        Fu f = new Zi();
        f.show();
    }
}
View Code
  •   多型成員方法

編譯時期:參考引用變數所屬的類,如果沒有類中沒有呼叫的方法,編譯失敗。

執行時期:參考引用變數所指的物件所屬的類,並執行物件所屬類中的成員方法。

簡而言之:編譯看左邊,執行看右邊。

instanceof關鍵字

我們可以通過instanceof關鍵字來判斷某個物件是否屬於某種資料型別。如學生的物件屬於學生類,學生的物件也屬於人類。

使用格式:

boolean  b  = 物件  instanceof  資料型別;

Person p1 = new Student(); // 前提條件,學生類已經繼承了人類
boolean flag = p1 instanceof Student; //flag結果為true
boolean flag2 = p2 instanceof Teacher; //flag結果為false

多型的轉型

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

  •  向上轉型:當有子類物件賦值給一個父類引用時,便是向上轉型,多型本身就是向上轉型的過程。

          使用格式:

父類型別  變數名 = new 子類型別();

如:Person p = new Student();

  • 向下轉型:一個已經向上轉型的子類物件可以使用強制型別轉換的格式,將父類引用轉為子類引用,這個過程是向下轉型。如果是直接建立父類物件,是無法向下轉型的!

            使用格式:

子類型別 變數名 = (子類型別) 父類型別的變數;

如:Student stu = (Student) p;  //變數p 實際上指向Student物件

 好處與弊端

當父類的引用指向子類物件時,就發生了向上轉型,即把子類型別物件轉成了父類型別。向上轉型的好處是隱藏了子類型別,提高了程式碼的擴充套件性。

但向上轉型也有弊端,只能使用父類共性的內容,而無法使用子類特有功能,功能有限制。看如下程式碼

//描述動物類,並抽取共性eat方法
abstract class Animal {
    abstract void eat();
}
 
// 描述狗類,繼承動物類,重寫eat方法,增加lookHome方法
class Dog extends Animal {
    void eat() {
        System.out.println("啃骨頭");
    }

    void lookHome() {
        System.out.println("看家");
    }
}
// 描述貓類,繼承動物類,重寫eat方法,增加catchMouse方法
class Cat extends Animal {
    void eat() {
        System.out.println("吃魚");
    }

    void catchMouse() {
        System.out.println("抓老鼠");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog(); //多型形式,建立一個狗物件
        a.eat(); // 呼叫物件中的方法,會執行狗類中的eat方法
        // a.lookHome();//使用Dog類特有的方法,需要向下轉型,不能直接使用
        
        // 為了使用狗類的lookHome方法,需要向下轉型
// 向下轉型過程中,可能會發生型別轉換的錯誤,即ClassCastException異常
        // 那麼,在轉之前需要做健壯性判斷 
        if( !a instanceof Dog){ // 判斷當前物件是否是Dog型別
                 System.out.println("型別不匹配,不能轉換"); 
                 return; 
        } 
        Dog d = (Dog) a; //向下轉型
        d.lookHome();//呼叫狗類的lookHome方法
    }
}
View Code

總結:

 什麼時候使用向上轉型?

當不需要面對子類型別時,通過提高擴充套件性,或者使用父類的功能就能完成相應的操作,這時就可以使用向上轉型。

  什麼時候使用向下轉型?

當要使用子類特有功能時,就需要使用向下轉型。

  向下轉型的好處:可以使用子類特有功能。

  弊端是:需要面對具體的子類物件;在向下轉型時容易發生ClassCastException型別轉換異常。在轉換之前必須做型別判斷。

如:if( !a instanceof Dog){…}

例子

/*
描述畢老師和畢姥爺,
畢老師擁有講課和看電影功能
畢姥爺擁有講課和釣魚功能
*/
class 畢姥爺 {
    void 講課() {
        System.out.println("政治");
    }

    void 釣魚() {
        System.out.println("釣魚");
    }
}

// 畢老師繼承了畢姥爺,就有擁有了畢姥爺的講課和釣魚的功能,
// 但畢老師和畢姥爺的講課內容不一樣,因此畢老師要覆蓋畢姥爺的講課功能
class 畢老師 extends 畢姥爺 {
    void 講課() {
        System.out.println("Java");
    }

    void 看電影() {
        System.out.println("看電影");
    }
}

public class Test {
    public static void main(String[] args) {
        // 多型形式
        畢姥爺 a = new 畢老師(); // 向上轉型
        a.講課(); // 這裡表象是畢姥爺,其實真正講課的仍然是畢老師,因此呼叫的也是畢老師的講課功能
        a.釣魚(); // 這裡表象是畢姥爺,但物件其實是畢老師,而畢老師繼承了畢姥爺,即畢老師也具有釣魚功能

        // 當要呼叫畢老師特有的看電影功能時,就必須進行型別轉換
        畢老師 b = (畢老師) a; // 向下轉型
        b.看電影();
    }
}
View Code

學習到這裡,面向物件的三大特徵學習完了。

總結下封裝、繼承、多型的作用:

  封裝:把物件的屬性與方法的實現細節隱藏,僅對外提供一些公共的訪問方式

 繼承:子類會自動擁有父類所有可繼承的屬性和方法。

 多型:配合繼承與方法重寫提高了程式碼的複用性與擴充套件性;如果沒有方法重寫,則多型同樣沒有意義。