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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...