溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中的抽象類和接口怎么區分

發布時間:2022-02-23 14:57:58 來源:億速云 閱讀:120 作者:iii 欄目:開發技術

這篇文章主要介紹了Java中的抽象類和接口怎么區分的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Java中的抽象類和接口怎么區分文章都會有所收獲,下面我們一起來看看吧。

    什么是抽象類和接口? 區別在哪里?

    不同的編程語言對接口和抽象類的定義方式可能有些差別,但是差別并不大。本文使用 Java 語言。

    抽象類

    下面我們通過一個例子來看一個典型的抽象類的使用場景。

    Logger 是一個記錄日志的抽象類,FileLogger 和 MessageQueueLogger 繼承Logger,分別實現兩種不同的日志記錄方式:

    • 記錄日志到文件中

    • 記錄日志到消息隊列中

    FileLogger 和 MessageQueuLogger 兩個子類復用了父類 Logger 中的name、enabled 以及 minPermittedLevel 屬性和 log 方法,但是因為兩個子類寫日志的方式不同,他們又各自重寫了父類中的doLog方法。

    父類

    import java.util.logging.Level;
    
    /**
     * 抽象父類
     * @author yanliang
     * @date 9/27/2020 5:59 PM
     */
    public abstract class Logger {
        private String name;
        private boolean enabled;
        private Level minPermittedLevel;
    
        public Logger(String name, boolean enabled, Level minPermittedLevel) {
            this.name = name;
            this.enabled = enabled;
            this.minPermittedLevel = minPermittedLevel;
        }
    
        public void log(Level level, String message) {
            boolean loggable = enabled && (minPermittedLevel.intValue() <= level.intValue());
            if(!loggable) return;
            doLog(level, message);
        }
    
        protected abstract void doLog(Level level, String message);
    }

    FileLogger

    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.Writer;
    import java.util.logging.Level;
    
    /**
     * 抽象類Logger的子類:輸出日志到文件中
     * @author yanliang
     * @date 9/28/2020 4:44 PM
     */
    public class FileLogger extends Logger {
    
        private Writer fileWriter;
    
        public FileLogger(String name, boolean enabled, Level minPermittedLevel, String filePath) throws IOException {
            super(name, enabled, minPermittedLevel);
            this.fileWriter = new FileWriter(filePath);
        }
    
        @Override
        protected void doLog(Level level, String message) {
            // 格式化level 和 message,輸出到日志文件
            fileWriter.write(...);
        }
    }

    MessageQueuLogger

    import java.util.logging.Level;
    
    /**
     * 抽象類Logger的子類:輸出日志到消息隊列中
     * @author yanliang
     * @date 9/28/2020 6:39 PM
     */
    public class MessageQueueLogger extends Logger {
    
        private MessageQueueClient messageQueueClient;
    
        public MessageQueueLogger(String name, boolean enabled, Level minPermittedLevel, MessageQueueClient messageQueueClient) {
            super(name, enabled, minPermittedLevel);
            this.messageQueueClient = messageQueueClient;
        }
    
        @Override
        protected void doLog(Level level, String message) {
            // 格式化level 和 message,輸出到消息隊列中
            messageQueueClient.send(...)
        }
    }

    通過上面的例子,我們來看下抽象類有哪些特性。

    • 抽象類不能被實例化,只能被繼承。(new 一個抽象類,會報編譯錯誤)

    • 抽象類可以包含屬性和方法。方法既可以包含實現,也可以不包含實現。不包含實現的方法叫做抽象方法

    • 子類繼承抽象類,必須實現抽象類中的所有抽象方法。

    接口

    同樣的,下面我們通過一個例子來看下接口的使用場景。

    /**
     * 過濾器接口
     * @author yanliang
     * @date 9/28/2020 6:46 PM
     */
    public interface Filter {
        void doFilter(RpcRequest req) throws RpcException;
    }
    
    /**
     * 接口實現類:鑒權過濾器
     * @author yanliang
     * @date 9/28/2020 6:48 PM
     */
    public class AuthencationFilter implements Filter {
    
        @Override
        public void doFilter(RpcRequest req) throws RpcException {
            // 鑒權邏輯
        }
    }
    
    /**
     * 接口實現類:限流過濾器
     * @author yanliang
     * @date 9/28/2020 6:48 PM
     */
    public class RateLimitFilter implements Filter{
    
        @Override
        public void doFilter(RpcRequest req) throws RpcException {
            // 限流邏輯
        }
    }
    
    /**
     * 過濾器使用demo
     * @author yanliang
     * @date 9/28/2020 6:48 PM
     */
    public class Application {
        // 過濾器列表
        private List<Filter> filters = new ArrayList<>();
        filters.add(new AuthencationFilter());
        filters.add(new RateLimitFilter());
    
        public void handleRpcRequest(RpcRequest req) {
            try {
                for (Filter filter : filters) {
                    filter.doFilter(req);
                }
            } catch (RpcException e) {
                // 處理過濾結果
            }
            // ...
        }
    }

    上面的案例是一個典型的接口使用場景。通過Java中的 interface 關鍵字定義了一個Filter 接口,AuthencationFilter 和 RetaLimitFilter 是接口的兩個實現類,分別實現了對Rpc請求的鑒權和限流的過濾功能。

    下面我們來看下接口的特性:

    • 接口不能包含屬性(也就是成員變量)

    • 接口只能聲明方法,方法不能包含代碼實現

    • 類實現接口時,必須實現接口中生命的所有方法。

    綜上,從語法上對比,這兩者有比較大的區別,比如抽象類中可以定義屬性、方法的實現,而接口中不能定義屬性,方法也不能包含實現等。

    除了語法特性的不同外,從設計的角度,這兩者也有較大區別。抽象類本質上就是類,只不過是一種特殊的類,這種類不能被實例化,只能被子類繼承。屬于is-a的關系。接口則是 has-a 的關系,表示具有某些功能。對于接口,有一個更形象的叫法:協議(contract)

    抽象類和接口解決了什么問題?

    下面我們先來思考一個問題~

    抽象類的存在意義是為了解決代碼復用的問題(多個子類可以繼承抽象類中定義的屬性哈方法,避免在子類中,重復編寫相同的代碼)。

    那么,既然繼承本身就能達到代碼復用的目的,而且繼承也不一定非要求是抽象類。我們不適用抽象類,貌似也可以實現繼承和復用。從這個角度上講,我們好像并不需要抽象類這種語法呀。那抽象類除了解決代碼復用的問題,還有其他存在的意義嗎?

    這里大家可以先思考一下哈~

    我們還是借用上面Logger的例子,首先對上面的案例實現做一些改造。在改造之后的實現中,Logger不再是抽象類,只是一個普通的父類,刪除了Logger中的兩個方法,新增了 isLoggable()方法。FileLogger 和 MessageQueueLogger 還是繼承Logger父類已達到代碼復用的目的。具體代碼如下:

    /**
     * 父類:非抽象類,就是普通的類
     * @author yanliang
     * @date 9/27/2020 5:59 PM
     */
    public class Logger {
        private String name;
        private boolean enabled;
        private Level minPermittedLevel;
    
        public Logger(String name, boolean enabled, Level minPermittedLevel) {
            this.name = name;
            this.enabled = enabled;
            this.minPermittedLevel = minPermittedLevel;
        }
    
        public boolean isLoggable(Level level) {
            return enabled && (minPermittedLevel.intValue() <= level.intValue());
        }
    
    }
    
    /**
     * 抽象類Logger的子類:輸出日志到文件中
     * @author yanliang
     * @date 9/28/2020 4:44 PM
     */
    public class FileLogger extends Logger {
    
        private Writer fileWriter;
    
        public FileLogger(String name, boolean enabled, Level minPermittedLevel, String filePath) throws IOException {
            super(name, enabled, minPermittedLevel);
            this.fileWriter = new FileWriter(filePath);
        }
    
        protected void log(Level level, String message) {
            if (!isLoggable(level)) return ;
            // 格式化level 和 message,輸出到日志文件
            fileWriter.write(...);
        }
    }
    
    package com.yanliang.note.java.abstract_demo;
    
    import java.util.logging.Level;
    
    /**
     * 抽象類Logger的子類:輸出日志到消息隊列中
     * @author yanliang
     * @date 9/28/2020 6:39 PM
     */
    public class MessageQueueLogger extends Logger {
    
        private MessageQueueClient messageQueueClient;
    
        public MessageQueueLogger(String name, boolean enabled, Level minPermittedLevel, MessageQueueClient messageQueueClient) {
            super(name, enabled, minPermittedLevel);
            this.messageQueueClient = messageQueueClient;
        }
    
        protected void log(Level level, String message) {
            if (!isLoggable(level)) return ;
            // 格式化level 和 message,輸出到消息隊列中
            messageQueueClient.send(...)
        }
    }

    以上實現雖然達到了代碼復用的目的(復用了父類中的屬性),但是卻無法使用多態的特性了。

    像下面這樣編寫代碼就會出現編譯錯誤,因為Logger中并沒有定義log()方法。

    Logger logger = new FileLogger("access-log", true, Level.WARN, "/user/log");
    logger.log(Level.ERROR, "This is a test log message.");

    如果我們在父類中,定義一個空的log()方法,讓子類重寫父類的log()方法,實現自己的記錄日志邏輯。使用這種方式是否能夠解決上面的問題呢? 大家可以先思考下~

    這個思路可以用使用,但是并不優雅,主要有一下幾點原因:

    • 在Logger中定義一個空的方法,會影響代碼的可讀性。如果不熟悉Logger背后的設計思想,又沒有代碼注釋的話,在閱讀Logger代碼時就會感到疑惑(為什么這里會存在一個空的log()方法)

    • 當創建一個新的子類繼承Logger父類時,有時可能會忘記重新實現log方法。之前是基于抽象類的設計思想,編譯器會強制要求子類重寫父類的log方法,否則就會報編譯錯誤。

    • Logger可以被實例化,這也就意味著這個空的log方法有可能會被調用。這就增加了類被誤用的風險。當然,這個問題 可以通過設置私有的構造函數的方式來解決,但是不如抽象類優雅。

    抽象類更多是為了代碼復用,而接口更側重于解耦。接口是對行為的一種抽象,相當于一組協議或者契約(可類比API接口)。調用者只需要關心抽象的接口,不需要了解具體的實現,具體的實現代碼對調用者透明。接口實現了約定和實現相分離,可以降低代碼間的耦合,提高代碼的可擴展性。

    實際上,接口是一個比抽象類應用更加廣泛、更加重要的知識點。比如,我們經常提到的 ”基于接口而非實現編程“ ,就是一條幾乎天天會用到的,并且能極大的提高代碼的靈活性、擴展性的設計思想。

    如何模擬抽象類和接口

    在前面列舉的例子中,我們使用Java的接口實現了Filter過濾器。不過,在 C++ 中只提供了抽象類,并沒有提供接口,那從代碼的角度上說,是不是就無法實現 Filter 的設計思路了呢? 大家可以先思考下 ???? ~

    我們先會議下接口的定義:接口中沒有成員變量,只有方法聲明,沒有方法實現,實現接口的類必須實現接口中的所有方法。主要滿足以上幾點從設計的角度上來說,我們就可以把他叫做接口。

    實際上,要滿足接口的這些特性并不難。下面我們來看下實現:

    class Strategy {
      public: 
        -Strategy();
        virtual void algorithm()=0;
      protected:
        Strategy();
    }

    抽象類 Strategy 沒有定義任何屬性,并且所有的方法都聲明為 virtual 類型(等同于Java中的abstract關鍵字),這樣,所有的方法都不能有代碼實現,并且所有繼承了這個抽象類的子類,都要實現這些方法。從語法特性上看,這個抽象類就相當于一個接口。

    處理用抽象類來模擬接口外,我們還可以用普通類來模擬接口。具體的Java實現如下所示:

    public class MockInterface {
      protected MockInteface();
      public void funcA() {
        throw new MethodUnSupportedException();
      }
    }

    我們知道類中的方法必須包含實現,這個不符合接口的定義。但是,我們可以讓類中的方法拋出 MethodUnSupportedException 異常,來模擬不包含實現的接口,并且強迫子類來繼承這個父類的時候,都主動實現父類的方法,否則就會在運行時拋出異常。

    那又如何避免這個類被實例化呢? 實際上很簡單,我們只需要將這個類的構造函數聲明為 protected 訪問權限就可以了。

    如何決定該用抽象還是接口?

    上面的講解可能偏理論,現在我們就從真實項目開發的角度來看下。在代碼設計/編程時,什么時候該用接口?什么時候該用抽象類?

    實際上,判斷的標準很簡單。如果我們需要一種is-a關系,并且是為了解決代碼復用的問題,就用抽象類。如果我們需要的是一種has-a關系,并且是為了解決抽象而非代碼復用問題,我們就用接口。

    從類的繼承層次來看,抽象類是一種自下而上的設計思路,先有子類的代碼復用,然后再抽象成上層的父類(也就是抽象類)。而接口則相反,它是一種自上而下的設計思路,我們在編程的時候,一般都是先設計接口,再去思考具體實現。

    關于“Java中的抽象類和接口怎么區分”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Java中的抽象類和接口怎么區分”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女