Java總結-抽象類與介面

期待l發表於2019-01-11
  • 本文是自己對抽象類和介面的理解,如果不對請指正,謝謝

抽象類的簡介

  • 抽象?抽象是什麼意思?之前在我的 封裝繼承多型 一文中提到了一個杯子的概念,簡單概括一下就是嘴說出來的是一個抽象的概念,因為並不知道這個杯子的具體引數,比如顏色之類的特點,所以抽象也就是將一個事物的大體結構提取出來,比如我的杯子有蓋子,是保溫的等,然而蓋子是彈射開的還是擰開的以及保溫材料的使用一概不知,所以對應到Java中的抽象類,那麼這個 抽象類也就是對一個事物的概括,(只是嘴說出來的)
  • 之前提到的is-ahas-a在這看來,抽象類更符合is-a的關係,抽象類可以提供方法實現,也可以不提供,但是其被稱為抽象類的話,那麼必定在類描述上有abstract關鍵字,而其中的方法完全可以沒有抽象方法的定義
  • 方法提供實現與否即是否是抽象方法,就像是你看中一款杯子,但是杯子的提供商拿不準每個人的手型,所以在你購買這個杯子的時候,需要自己選擇杯子柄的形狀,這是強制的,對應到Java抽象類中就是抽象方法,即必須由子類提供實現
  • 而杯子的其他特點已經是大眾認可了,比如杯子口是圓的,所以提供商就在你不指定的情況下預設這個形狀了,對應到Java抽象類中就是非抽象方法,當然你也可以定製杯口形狀,對應到Java抽象類中就是子類重寫父類方法了
  • 上面提到了杯柄的強制指定,所以在你不指定杯柄的情況下,杯子提供商是不知道你的意思的,因此就無法為你生成一個合適你自己的杯子,那麼對應到抽象類中就是強制子類去實現這個抽象方法,所以在這就可以看到抽象類是不提供建立抽象物件的操作的,因為這是風險的,如果你不指定實現,那麼它就不知道怎麼做,做什麼,換句話說就是抽象類就是為了被繼承而存在的
  • 總結:抽象類是對一個事物的概括,屬於is-a,並且由abstract關鍵字進行修飾,其中的內在方法可以有方法實現也可以沒有,沒有方法實現的,子類必須重寫,有方法實現的,子類可以沿用父類的實現,或者再進行重寫定製

抽象類的語法

  • 上面提到了重寫,那麼就必然涉及到繼承,所以在抽象類中, 方法不可以是private abstract,因為這些限定符就使得子類獲取不到父類的方法了,違背抽象類的使用原則,所以方法的修飾符就只能是publicprotected,預設為public
  • abstract修飾類,表示只能被繼承,修飾方法,表明必須由子類實現重寫.而final修飾的類不能被繼承,final修飾的方法不能被重寫,所以final和abstract不能同時修飾類和方法
  • static與abstract不能同時修飾某個方法,即沒有所謂的類抽象方法.但是可以同時修飾內部類,
  • 如果有一個子類繼承了抽象類,抽象類其中有抽象方法,如果子類也不實現父類中的抽象方法,那麼這個子類也必須定義為抽象類,原因很簡單:因為子類也拿不準主意,所以還需要其他類提供實現,因此一個子類如果繼承了抽象類,必須實現抽象類中定義的所有抽象方法
  • 抽象類因為是類,也是class修飾,所以它的子類需要繼承抽象類的時候,也是採用extends
  • 抽象方法不能有方法體,必須由abstract修飾
  • 抽象類可以包含成員變數,方法,構造器,初始化塊,內部類.抽象類的構造器不能用於建立例項,主要是用於被其子類呼叫
  • 總結:上面都需要記住

抽象類的使用

  • 拿得準的實現,通用的實現寫到抽象類中,否則你就定義抽象方法,由於類是單繼承了,所以只能實現一個抽象類,就不存在抽象方法衝突了
  • 下面是一個簡單實現.

    public abstract class Animals {
        abstract void say();
    }
    class Cat extends Animals{
        @Override
        void say() {
            System.out.println("mm");
        }
    }
    class Dog extends Animals{
        @Override
        void say() {
            System.out.println("ww");
        }
    }
    class Tests {
        public static void main(String[] args) {
            Animals cat = new Cat();
            Animals dog = new Dog();
            cat.say();
            dog.say();
        }
    }
  • 上面僅僅是實現了父類中的方法,那麼跟下面這種有啥區別呢?

    public abstract class Animals {
    }
    class Cat extends Animals{
        void say() {
            System.out.println("mm");
        }
    }
    class Dog extends Animals{
        void say() {
            System.out.println("ww");
        }
    }
  • 上面程式碼沒有錯誤,但是當執行多型去編寫程式碼的時候就會出錯了,因為父類Animals中並沒有say方法,雖然程式執行邏輯看子類但是父類總的先定義一下,所以抽象類的存在就使我們可以更加方便的執行多型,多型其好處是一旦需求有改動的時候,修改起來靈活,變化起來容易,不用修改過多的程式碼
  • 抽象類就是為繼承而存在的,繼承是複用程式碼的一個重要的機制,所以抽象類可以將一些事物的預設實現儘量的在類中進行實現,以減少子類程式碼的書寫
  • 總結:存在繼承關係在在抽象類,抽象類使多型運用的更加靈活,不足的就是單繼承的限制

介面的簡介

  • 介面是啥?我可能直接想到的就是網線介面,USB介面,所以這就給了我們很好的啟發了,當需要用USB的時候,我們就插上,不需要就拔下來相當靈活,所以對應到Java中介面主要也是類似的作用,比抽象類更加的靈活,因為介面可以多實現,需要一個功能我們就可以實現一個介面
  • 使用USB你會發現插在那個主機上都可以使用,所以這裡面存在一個USB協議,大家都遵守這個規定,所以USB可以到處插拔,介面的第二個作用就是在這了,即定義協議,一切按協議走,方便你我他
  • 在Java中的介面的定義是使用interface修飾符的

介面的演進和語法

  • 在JDK8之前,介面只能是有抽象方法,就是跟抽象類中的抽象方法一樣的作用,必須由子類實現,而且介面沒有實現,所以在之前介面比抽象類還抽象
  • 但是在JDK8的更新中,加入了Stream,Lmabda等一系列功能以及函數語言程式設計的支援,所以新增了一個概念叫做:函式式介面,該介面依舊是interface定義,不同的是其中只允許有一個抽象方法的定義,並且標有@FunctionalInterface註解,這些功能有一部分是對當前的集合類進行操作,但是之前的集合類介面上都是抽象方法,怎麼才能直接對介面進行操作呢,他們沒實現啊,所以為了這個要求JDK8加入了default修飾符在介面中,比如

    public interface Eat {
        default void say(){
            System.out.println("say");
        }
    }
    class Animals implements Eat{}
    class Tests{
        public static void main(String[] args) {
            Eat e = new Animals();
            e.say();
        }
    }
  • 如上程式碼是正確的,所以你要知道JDK8的介面中可以有預設實現了,一些集合類中把他的子類的相同操作邏輯提取到了預設方法,所以才可以直接對介面中的方法進行操作,比如List介面中的替換方法

    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
  • 還加入了static修飾的方法

    public interface Eat {
        static void st(){
            System.out.println("st");
        }
    }
    class Animals implements Eat{}
    class Tests{
        public static void main(String[] args) {
            Eat.st();
            //error Static method may be invoked on containing interface class only
            Animals.st();
        }
    }
  • 上面說明在介面定義的靜態方法,只可以interfaceName.method呼叫
  • 在JDK9中還加入了private修飾方法,JDK8中不支援,但是後來發現如果沒有這個private修飾的方法,會造成介面中的實現會有重複程式碼,所以引入了private,如下

    public interface SSSS {
        private static void st(){
            System.out.println("st");
        }
        private void sts(){
            System.out.println("sts");
        }
        static void impl(){
            st();
            sts(); //error 靜態不能引用例項
        }
    }
    class Testss{
        public static void main(String[] args) {
            SSSS.impl();
        }
    }
  • 如上如果將那個error去掉,程式碼就是正確的,但是介面中依舊是不可以對普通方法提供實現的,因為這是抽象方法
  • 下面是函式式介面的一個例子

    @FunctionalInterface
    public interface SSSS {
        void say();
        static void sta(){
        }
    }
  • 上面是正確的,有且僅有一個抽象方法的介面可以被標註為@FunctionalInterface,如果你還不是很瞭解函式式介面,可以去看一下我的 Java8學習專輯,這是非常有用的
  • 一些細枝末節:

    • 由於介面定義的是一種規範,因此介面裡不能包含構造器和初始化塊定義,可以包含常量(靜態常量),方法(只能是抽象例項方法,類方法,預設和私有方法),內部類定義
    • 介面中的靜態常量是介面相關的.因此預設增加static和final修飾符.所以在介面中的成員變數總是使用public static final修飾,並且只能在定義時指定初始值
    • 介面中如果不是定義預設方法類方法或私有方法,系統自動為普通方法增加abstract修飾符.介面中的普通方法總是使用public abstract修飾
    • 介面中定義的內部類,內部介面,內部列舉預設都採用public static兩個修飾符,不管定義時是否指定,系統會為其自動修飾
    • 類方法總是public修飾,可以直接使用介面名來呼叫
    • 預設方法都是採用default修飾,不能使用static修飾,並且總是public修飾.需要使用介面的實現類例項來呼叫這些方法
  • 對了,如果一個類實現的兩個介面中有相同的預設方法,那麼必須在子類中進行重新實現

    interface SSSS {
        default void say(){
            System.out.println("ss");
        }
    }
    interface AAAA {
        default void say(){
            System.out.println("aa");
        }
    }
    class Demo implements AAAA,SSSS{
        @Override
        public void say() {
    
        }
    }

抽象類與介面語法對比

  • 介面裡只能包含抽象方法,靜態方法,預設方法和私有方法.不能為普通方法提供實現,抽象類完全可以
  • 介面裡只能定義靜態常量,不能定義普通成員變數,抽象類都可以
  • 介面裡不包含構造器;抽象類裡可以包含構造器,抽象類裡的構造器並不是用於建立物件.而是讓其子類呼叫這些構造器來完成屬於抽象類的初始化操作
  • 介面裡不能包含初始化塊,但抽象類則完全可以包含
  • 一個類最多隻能有一個直接父類,包括抽象類.但一個類可以實現多個介面,通過實現多個介面可以彌補java單繼承的不足

抽象類與介面設計對比

  • 並沒有程式碼,現在有一個門的物件Door,熟知Door有一個開門以及關門的功能,這是一個門的最基本的功能,那麼我們如果在寫完程式碼後再次修改門物件的定義,需要新增一個報警功能,那麼我們該怎麼辦,如果在抽象類中直接新增報警功能,如果是抽象方法,就必須重寫,如果是父類已經實現的方法,子類如果在細化實現的話,那麼也要重寫,並且你父類中的實現可能會影響到子類的其他方法,這是一種方法,但是如果一直有改動或者方法很多的話,那麼這個抽象類將變得相當麻煩,第二種方法就是:在不更改抽象類的情況下,可以編寫一個報警的介面,用子類來實現他,那麼子類就必須去實現此方法,這樣就可以達到不做抽象類的更改並新增了報警功能.
  • 抽象類的編寫就是基於子類的共同特性的,它是描述一個類的大致情況的,外貌輪廓,介面則是行為形式,描述是具體幹什麼的,如果一個工廠有什麼部門,那麼如果按照第一種方法,再去每個部門新增部門具體是做什麼的,那麼不僅僅影響到了繼承他的子類,而且使程式碼變的不太容易維護,雜亂,採用第二種,可以避免這種情況,子類需要什麼功能就實現什麼介面,更加的靈活

總結

抽象類的實現就是基於子類的共同特性的,它是描述一個類的大致情況的,外貌輪廓,介面則是行為形式,描述是具體幹什麼的,在使用的時候,我們可以將相同子類的共同特性抽檢出一個抽象類來作為其子類的大致輪廓,具體實現細節,可以編寫介面並實現即可、一個類繼承一個抽象類.抽象類規定了子類的大概輪廓,其具體實現的方法,可以使用抽象類中的,也可以通過實現介面,重寫介面中的方法來實現子類的細化、可以利用抽象類和藉口配合使類更具體,即抽象類和介面的配合可以生成一個獨一無二的定製類


相關文章