Java面向對象進階篇(抽象類和介面)

来源:https://www.cnblogs.com/yumiaoxia/archive/2018/05/09/9012050.html
-Advertisement-
Play Games

一.抽象類 在某些情況下,父類知道其子類應該包含哪些方法,但是無法確定這些子類如何實現這些方法。這種有方法簽名但是沒有具體實現細節的方法就是抽象方法。有抽象方法的類只能被定義成抽象類,抽象方法和抽象類必須使用abstract修飾。抽象類里可以沒有抽象方法。 1.1 抽象類和抽象方法 抽象類和抽象方法 ...


一.抽象類

在某些情況下,父類知道其子類應該包含哪些方法,但是無法確定這些子類如何實現這些方法。這種有方法簽名但是沒有具體實現細節的方法就是抽象方法。有抽象方法的類只能被定義成抽象類,抽象方法和抽象類必須使用abstract修飾。抽象類里可以沒有抽象方法。

1.1 抽象類和抽象方法

      抽象類和抽象方法的規則如下:

      1.抽象類和抽象方法都必須使用abstract修飾符修飾,抽象方法不能有方法體

      2.抽象類不能被實例化,無法使用new關鍵字來調用抽象類的構造器創建抽象類的實例。即使這個抽象類里不包含抽象方法。

      3.抽象類可以包含成員變數,方法(普通方法、抽象方法),構造器,初始化塊,內部類(介面,枚舉類)5中成分。抽象類的構造器不能用於創建實例,主要是用於被子類調用。

      4.含有抽象方法的類(直接定義了一個抽象方法;繼承了一個父類,沒有完全實現父類包含的抽象方法;或實現一個介面,但沒有完全實現介面包含的抽象方法)只能被定義成抽象類。

    定義抽象方法只需在普通方法上加上abstract修飾符,並把普通方法的方法體全部去掉,併在方法後增加分號即可

    定義抽象類只需在普通類上增加abstract修飾符即可。

   抽象類不能用於創建實例,只能當作父類被其他子類繼承。

package com.company2;

abstract class Shape{
    private String color;
    //定義一個計算周長的方法
    public abstract  double calPerimeter();
    //定義一個返回形狀的方法
    public abstract  String getType();

    public Shape(String color) {
        this.color = color;
    }

    public Shape() {
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

public class Triangle extends Shape{
    
    private double a;
    private double b;
    private double c;

    public Triangle(String color, double a, double b, double c) {
        super(color);
        setSides(a,b,c);
    }
    
    public void setSides(double a, double b, double c)
    {
        if(a+b > c || a+c > b || b+c > a){
            this.a = a;
            this.b = b;
            this.c = c;
        }
        System.out.println("三角形兩邊之和必須大於第三邊");
    }
    @Override
    public double calPerimeter() {
        return a+b+c;
    }

    @Override
    public String getType() {
        return "三角形";
    }
}

        當使用abstract修飾類時,表明這個類只能被繼承;當使用abstract修飾方法時,表明這個方法必須有子類提供實現(重寫)。而final修飾的方法不能被重寫,final修飾的類不能被繼承。因此final

和abstract永遠不能同時使用。static和abstract不能同時修飾某個方法,沒有類抽象方法的說法,但可以同時修飾內部類。abstract也不能修飾變數和構造器。

       利用抽象類和抽象方法的優勢,可以更好地發揮多態的優勢,使得程式更加靈活。

1.2 抽象類的作用

    從語義的角度來看,抽象類是從多個具體類中抽象出來的父類,它具有更高層次的抽象,它體現的是一種模板模式的設計。抽象類作為多個子類的通用模板,子類在抽象類的基礎上進行

拓展,改造,避免了子類設計的隨意性。

抽象類的普通方法可依賴與抽象方法,抽象方法則推遲到子類中提供實現

package com.company2;

abstract class Speedmeter
{
    private double turnRate;//轉速

    public Speedmeter() {
    }
    //把返回車輪半徑的方法定義成抽象方法
    public abstract double getRadius();
    public void setTurnRate(double turnRate){
        this.turnRate = turnRate;
    }
    public double getSpeed()
    {
        return Math.PI*2*getRadius()*turnRate;
    }
}
public class CarSpeedmeter extends Speedmeter{

    @Override
    public double getRadius() {
        return 0.28;
    }

    public static void main(String[] args)
    {
        CarSpeedmeter csm = new CarSpeedmeter();
        csm.setTurnRate(15);
        System.out.println(csm.getSpeed());
    }
}

模板模式在面向對象的軟體中很常用,其原理簡單,實現也簡單。下麵是使用模板模式的一些簡單規則:

1.抽象父類可以只定義需要使用的某些方法,把不能實現的部分抽象成抽象方法,留給其子類實現。

2.父類中可能包含需要調用其他系列方法的方法。這些被調用方法既可以由父類實現,也可以由子類實現。父類提供的方法只是定義了一個通用演算法,其實現也許並不完全由自身實現,必須

   依賴於其子類的幫助。

二. Java 8改進的介面(interface)

2.1 介面的概念

      介面是多個相似類中抽象出來的一組公共行為規範,介面不提供任何實現,它體現的是規範和實現分離的哲學。

     規範和實現分離正是介面的好處,它讓軟體系統各組件之間面向介面耦合,可以為軟體系統提供很好的松耦合設計,從而降低個模塊間的耦合,為系統提供更好的可拓展性和可維護性。

     介面可以有多個直接父介面,但介面只能繼承介面,不能繼承類

2.2 介面的定義規則

      1.由於介面定義的是一種規範,用interface跟class區分,因此介面里不能包含構造器和初始化塊定義。介面里可以包含成員變數(只能是靜態常量),方法(只能是抽象實例方法,類方法或

         預設方法), 內部類(內部接口,枚舉)定義

       2.因為介面定義的是多個類共同的公共行為規範。因此介面的所有成員(包括常量,方法,內部類)都是public訪問許可權。只能指定public修飾符,也可以省略。

       3.介面定義的成員變數只能在定義時指定預設值,預設使用public static final修飾符修飾。可以省略不寫。

       4.介面定義的方法只能是抽象方法,類方法和預設方法。類方法和預設方法都必須有方法實現(方法體)。

下麵定義一個介面

package com.company2;

public interface Output {
    //定義的成員變數只能是常量,預設public static final修飾
    int MAX_CACHE_LINE = 50;
    
    //介面里定義的普通方法只能是public的抽象方法,沒有方法體
    void out();
    void getData(String msg);
    //介面里定義的預設方法,需要使用default修飾,預設public修飾,有方法體
    default void print(String... msgs)
    {
        for(String msg:msgs)
        {
            System.out.println(msg);
        }
    }
    
    default void test()
    {
        System.out.println("預設的test()方法");
    }
    //介面里定義的類方法,需要使用static修飾,預設public修飾,有方法體
    static String staticTest()
    {
        return "介面里的類方法";
    }
}

不同包下的另一個類訪問介面里的成員變數。如下

package com.company;

import com.company2.Output;

public class OutputFIeldTest {
    public static void main(String[] args)
    {
        System.out.println(Output.MAX_CACHE_LINE);
        System.out.println(Output.staticTest());
        
    }
}

2.3 介面的繼承

介面的繼承和類繼承不一樣,介面完全支持多繼承,即一個介面可以有多個直接父介面。和類繼承相似,子介面拓展某個父介面,將會獲得父介面里定義的所有抽象方法、常量

一個介面繼承多個父介面時,多個父介面排在extends關鍵字之後,多個父介面之間以逗號隔開。

2.4 使用介面

 介面的主要用途

 1.定義變數,也可用於強制類型轉換。介面聲明引用類型變數

 2.調用介面中定義的常量

 3.被其他類實現

       類可以使用implements關鍵字實現一個或多個介面,這也是Java為單繼承靈活性不足所做的補充。實現介面與繼承父類相似,一樣可以獲得所實現介面里定義的常量,方法。

       一個類可以繼承一個父類,並同時實現多個介面,implements部分必須在extends部分之後。

       一個類實現了一個或多個介面之後,這個類必須完全實現這些介面里所定義的全部抽象方法(也就是重寫這些抽象方法);否則該類將保留從父介面那裡繼承得到的抽象方法,該類也必須

定義成抽象類。

       一個類實現某個介面時,該類將會獲得介面中定義的常量,方法等。因此可以把實現介面理解為一種特殊的繼承,相當於實現類完全繼承了一個徹底抽象的類。

      介面不能顯式繼承任何類,但所有介面類型的引用變數都可以直接賦給Object類型的引用變數。

三. 介面和抽象類

3.1 介面與抽象類的相似特征

     1.介面和抽象類都不能被實例化,用於被其他類實現和繼承

     2.介面和抽象類都包含抽象方法,實現介面和繼承抽象類的普通子類都必須實現這些抽象方法

3.2 介面與抽象類的設計目的差別

     介面作為系統與外界交互的視窗,介面體現的是一種規範。對於介面的實現者而言,介面規定了實現者必須向外提供哪些服務(以方法的形式);對於介面的調用者而言,介面規定了調用者

可以調用哪些服務,以及如何調用這些服務(就是如何調用方法)。當一個程式中使用介面時,介面是多個模塊間的耦合標準;在多個應用程式之間使用介面時,介面是多個程式之間的通訊標準。

     從某種程度上來看,介面類似於整個系統的總綱,它制定了各模塊應該遵循的標準,因此一個系統中的介面不應該經常改變。一但介面發生改變,其影響是輻射性的,導致系統中的大部分都需

 要重寫。

     抽象類作為系統中多個子類的共同父類,它所體現的是一種模板式設計。抽象類作為多個子類的抽象父類,可以被當成系統實現過程中的中間產品,這個中間產品已經實現了系統的部分功能(那

些已經提供實現的方法),但這個產品依然不能當成最終產品,必須有進一步的改善。

3.3 介面與抽象類的用法差別

    1.介面里只能包含抽象方法,靜態方法,預設方法,不能為普通方法提供方法實現。抽象類則可以完全包含普通方法。

     2.介面里只能定義靜態常量,不能定義普通成員變數。抽象類里即可以定義靜態常量,也可以定義普通成員變數。

     3.介面里不包含構造器。抽象類里可以包含構造器,抽象類里的構造器並不是用於創建對象,而是讓其子類調用這些構造器來完成屬於抽象類的初始化操作。

     4.介面里不能包含初始化塊。抽象類則可以完全包含初始化塊

     5.一個類最多只能有一個直接父類,包括抽象類。但一個類可以直接實現多個介面,通過實現多個介面可以彌補Java單繼承的不足。

四. 面向介面編程

很多軟體架構設計理論都倡導面向介面編程來降低程式的耦合。下麵介紹兩種常用的場景來示範介面編程的優勢

4.1 簡單工廠模式

假設程式中有個Computer類需要組合一個輸出設備。有兩種選擇,讓Computer類組合一個Printer對象,或者讓Computer類組合一個Output。

假設讓Computer類組合一個Printer對象,假如有一天系統需要重構,需要BetterPrinter來代替Printer,這就需要打開Computer類源代碼進行修改,假如系統中有100個類組合了Printer,甚至1000個、

10000個...,這是多麼大的工作量啊

為了避免這個問題,工廠模式建議讓Computer類組合一個Output類型的對象,將Computer類和Printer類完全分離。Computer對象實際組合的是Printer對象還是BetterPrinter對象,對Computer而言

完全透明。當Printer對象切換到BetterPrinter對象時,系統完全不受影響。

package com.company2;

public interface Output {
    //定義的成員變數只能是常量,預設public static final修飾
    int MAX_CACHE_LINE = 50;

    //介面里定義的普通方法只能是public的抽象方法,沒有方法體
    void out();
    void getData(String msg);
    //介面里定義的預設方法,需要使用default修飾,預設public修飾,有方法體
    default void print(String... msgs)
    {
        for(String msg:msgs)
        {
            System.out.println(msg);
        }
    }

    default void test()
    {
        System.out.println("預設的test()方法");
    }
    //介面里定義的類方法,需要使用static修飾,預設public修飾,有方法體
    static String staticTest()
    {
        return "介面里的類方法";
    }
}

Output介面定義了一系列列印行為

package com.company2;

interface Product
{
    int getProduceTime();
}
public class Printer implements Output,Product
{
    private String[] printData = new String[MAX_CACHE_LINE];
  //用以記錄當前需列印的作業數
    private int dataNum = 0;

    @Override
    public void out()
    {
        //只要還有作業就繼續列印
        while (dataNum > 0) {
            System.out.println("告訴印表機正在列印" + printData[0]);
            //把作業隊列整體前移一位,並將剩下的作業數減1
            System.arraycopy(printData, 1, printData, 0, --dataNum);
        }
    }

    @Override
    public void getData(String msg) {
        if(dataNum >= MAX_CACHE_LINE)
        {
            System.out.println("輸出隊列已滿,添加失敗");
        }
        else{
            //把列印數據添加到隊列里,已保存數據的數量加1
            printData[dataNum++] = msg;
        }
    }

    @Override
    public int getProduceTime() {
        return 45;
    }
}

Printer實現了Output介面

package com.company2;

public class Computer {
    private Output out;

    public Computer(Output out) {
        this.out = out;
    }
    public void keyIn(String msg)
    {
        out.getData(msg);
    }

    public void print()
    {
        out.out();
    }
}

Computer類完全與Printer類分離,只是與Output介面耦合。Computer不再負責創建Outputer對象,系統提供一個Ouputer工廠來負責生產Output對象。

package com.company2;

public class OutputFactory {
    public Output getOutput()
    {
        return new Printer();
    }

   public static void main(String[] args)
   {
       OutputFactory of = new OutputFactory();
       Computer c = new Computer(of.getOutput());
       c.keyIn("輕量級Java EE企業應用實戰");
       c.keyIn("瘋狂Java講義");
       c.print();
   }


}

在該OutputFactory類中包含了一個getOutput()方法,返回Output實現類的實例,該方法負責創建Output實例

package com.company2;

public class BetterPrinter implements Output {
    private String[] printData = new String[MAX_CACHE_LINE*2];

    private int dataNum = 0;

    @Override
    public void out() {
        while(dataNum>0)
        {
            System.out.println("告訴印表機正在列印"+printData[0]);
            System.arraycopy(printData,1,printData,0,--dataNum);
        }
    }

    @Override
    public void getData(String msg) {
        if(dataNum >= MAX_CACHE_LINE * 2)
        {
            System.out.println("輸出隊列已滿,添加失敗");
        }
        else{
            printData[dataNum++] = msg;
        }
    }
}

BetterPrinter類也實現了Output介面,因此也可當成Output對象使用,只要把OutputFactory工廠類的getOutput()方法中的下劃線部分改為如下代碼

return new BetterPrinter();

 

4.2 命令模式

假設一個方法需要遍歷某個數組的數組元素,但無法確定在遍曆數組元素時如何處理這些元素,需要在調用該方法時指定具體的處理行為。

對於這樣的一個需求,必須把處理行為作為參數傳入該方法,這個“處理行為”用編程來實現就是一段代碼。

可以考慮使用一個Command介面定義一個方法,用這個方法來封裝“處理行為”,但這個方法沒有方法體,因為現在還無法確定這個處理行為

public interface Command
{
void process(int[] target);
}

需要創建一個數組的處理類,在這個處理類包含一個process方法,這個方法還無法確定處理數組的處理行為,所以定義該方法時使用了一個Command參數,這個Command參數負責

對數組的處理行為

public class ProcessArray
{
public void process(int[] target,Command cmd)
{
  cmd.process(target);
}
}

通過Command介面,就實現了讓ProcessArray類和具體“處理行為”的分離,程式使用Command介面代表了對數組的處理行為。Command介面也沒提供真正的處理,只有等到需要

調用ProcessArray對象的process()方法時,才真正傳入一個Command對象,才確定對數組的處理行為。

下麵代碼示範了對數組的兩種處理方式

public class CommandTest
{
public static void main(String[] args)
{
ProcessArray pa = new ProcessArray();
int[] target = [3,-4,6,4];
//第一次處理數組,具體處理行為取決於PrintCommand
pa.process(target,new PrintCommand());
//第二次處理數組,具體處理行為取決於AddCommand
pa.process(target,new AddCommand());
}
} 

兩次不同的處理行為通過PrintCommand類和AddCommand類提供

package com.company2;

public class PrintCommand implements Command{
    public void process(int[] target)
    {
        for(int tmp:target)
        {
            System.out.println("迭代輸出數組的元素"+tmp);
        }
    }
}

 

package com.company2;

public class AddCommand implements Command{
    public void process(int[] target)
    {
        int sum = 0;
        for(int tmp:target)
        {
            sum += tmp;
        }
        System.out.println("數組的元素總和為"+sum);
    }
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.對象 1.1 語法 對象可以通過兩種形式定義:聲明(文字)形式和構造形式。 對象的文字語法: 對象的構造語法: 1.2 類型 對象是JavaScript的基礎。在JavaScript中一共有六種主要類型(術語是“語言類型”): string number boolean null undefin ...
  • 在react項目開發中,input標簽使用onChange方法獲取輸入值改變state: 但是,在IE9下發現 e.target.value 取值一直為undefined。在IE中,e.target 指的是window,查閱React文檔發現: 解決方法: ...
  • 頁面佈局,或者是在頁面上做些小效果的時候經常會用到 display,position和float 屬性,如果對它們不是很瞭解的話,很容易出現一些莫名其妙的效果,痛定思痛讀了《CSS Mastery》後總結一下。 讓我們從基礎的CSS知識談起,相信很多初學者和小弟一樣不明白CSS原理,一味追求效果,結 ...
  • 關鍵詞出現在網站哪些地方符合SEO?進行網站的SEO時,關鍵詞需要出現在整個網站的適當地方。下麵列出幾個重要的關鍵詞擺放的地方。以下列出的10個地方希望能夠幫助到大家。 1、網站Title部分。 2、網站Meta Keywords部分。 3、網站Meta Description部分。 4、 關鍵詞出 ...
  • 一個前端在調試本地頁面時,總會有些稀奇古怪的需求,比如產品立刻要看你的頁面效果,而此時有沒有上線環境折騰給他看,那此時通過內網穿透的方式,實時把你的項目生成一個線上鏈接丟給他,讓他去找那一像素的bug! ...
  • 由於最近在vue-cli生成的webpack模板項目的基礎上寫一個小東西,開發過程中需要改動到build和config裡面一些相關的配置,所以剛好趁此機會將所有配置文件看一遍,理一理思路,也便於以後修改配置的時候不會“太折騰”。 一、文件結構 本文主要分析開發(dev)和構建(build)兩個過程涉 ...
  • 1. 下載並安裝 node.js 參照 Less 的用法 2. 新建一個項目 3. 控制台進入該項目目錄,初始化 根目錄中自動生成了package.json 4、全局安裝webpack和webpack-cli 5、在項目中安裝webpack和webpack-cli 6. 安裝babel 7. 編輯 ...
  • 模塊化原則倡導利用集中和分解等手法創建高內聚、低耦合的抽象。 為了理解模塊化的含義及其很重要的原因,來看看一本書的極端情況。假設一本書像講一個長故事一樣闡述其中的內容,中間沒有任何停頓,也沒有章節。試問面對這樣的圖書,讀者將作何反應呢?我估計心中一定有千萬隻草泥馬在崩騰吧。如果這本書根據內容分為不同 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...