Java基礎——抽象abstract和接口

一、abstract和接口初認識

abstract class和interface是Java語言中對于抽象類定義進行支持的兩種機制。abstract class和interface之間在對于抽象類定義的支持方面具有很大的相似性,甚至可以相互替換,因此很多開發者在進行抽象類定義時對于abstract class和interface的選擇顯得比較隨意。其實,兩者之間還是有很大的區別的,對于它們的選擇甚至反映出對于問題領域本質的理解、對于設計意圖的理解是否正確、合理。

abstract 關鍵字可以修飾類或方法。

抽象方法
Java中可以利用abstract關鍵字定義一些不包括方法體的方法,沒有方法體就沒有實現,具體實現交給子類,這樣的方法稱為 抽象方法

抽象類
有抽象方法的類就是抽象類

接口
一個類中如果所有方法都是abstract 方法,那么這個類我們可以利用 interface 定義成接口。

二、 抽象方法

什么是抽象方法
聲明為 abstract 的方法就是抽象方法。

抽象方法的寫法

abstract 返回值類型 方法名(參數列表);

抽象方法的特點

  • 抽象方法只有聲明,沒有實現,抽象方法必須由子類進行重寫實現
    沒有實現就是沒有方法體(方法體就是方法后面的花括號),意味著這是一個沒有完成的方法,抽象的。抽象方法只能由子類去重寫實現

  • abstract 關鍵字不能應用于 static、private 或 final 方法,因為這些方法不能被重寫。

三、 抽象類

什么是抽象類

有抽象方法的類就是抽象類

抽象類的寫法
public abstract class 類名{}

抽象類的特點

  • 1、抽象類不能被實例化,實例化就必須實現全部方法,實現全部方法后這個類就不是抽象類。(實例化沒意義),但可以有構造函數
  • 2、抽象方法必須由子類進行重寫實現
  • 3、子類繼承自抽象類,必須實現抽象類的全部抽象方法,這樣的子類也叫具體類,具體類才可以被實例化(這個實現全部方法方法的子類不是抽象類,抽象類不能實例化)。如果沒有實現全部抽象方法,那么這個子類必須也是抽象類。
  • 4、一個類只要出現了abstract方法,那么這個類就必須是abstract類
  • 5、抽象類中可以有非抽象方法,可以有變量

如果一個類中方法都是抽象方法,那么我們就可以把這個類定義成接口。
(接口是一種特殊的類,接口也是類)

代碼示例

接下看通過簡單的代碼,看一下抽象類和抽象方法

AbsPerson

public abstract class AbsPerson {
    public int number = 16;
    {
        System.out.println("抽象類的代碼塊");
    }
    public AbsPerson(){
        System.out.println("抽象類的構造函數");
    }
    int maxAge = 200;
    public abstract void say(String str);
    public abstract void age();
    
    public void absPersonNormal(){
        System.out.println("抽象類的普通方法,或者叫 非抽象方法");
    }
    
}

.
.
Student

public class Student extends AbsPerson{
    {
        System.out.println("學生類的代碼塊");
    }
    
    public Student() {
        System.out.println("學生類的構造函數");
    }

    @Override
    public void say(String str) {
        System.out.println(str);
    }

    @Override
    public void age() {
        System.out.println("年齡18");
    }
}

.
.
AbsPerson

// 沒實現 AbsPerson 這個抽象類的所有方法,所以這個類也是抽象類
public abstract class Worker extends AbsPerson{
    @Override
    public void say(String str) {
        // TODO Auto-generated method stub
        
    }
}

.
.
TestClass

public class TestClass{
    public static void main(String[] args) {
        Student student = new Student();
        student.say("day day up");// 子類(具體類)調用實現自抽象類的方法
        student.age();// 子類(具體類)調用實現自抽象類的方法
        student.absPersonNormal();// 子類(具體類)調用抽象類的 非抽象方法
        System.out.println("子類調用抽象類的變量:  "+student.number);
    }
}

.
.
運行結果:

抽象類的代碼塊
抽象類的構造函數
學生類的代碼塊
學生類的構造函數
day day up
年齡18
抽象類的普通方法,或者叫 非抽象方法
子類調用抽象類的變量:  16

代碼已經說得很清楚了。

四、接口

如果一個類中方法都是抽象方法,那么我們就可以把這個類定義成接口。
接口的出現讓類不必受限于單一繼承的束縛,可以靈活地繼承一些共有的特性,間接實現類似多繼承的目的。

接口里面只可能有兩種東西

  • 1、抽象方法
  • 2、全局靜態常量
    (接口中沒有變量,默認都是用 public static final標識的,不過在interface中一般不定義屬性成員,只定義抽象方法)

接口的特點:

  • 1、接口的訪問修飾符只能是public,或者不寫* 2、interface中定義的方法和成員變量,默認為public訪問權限,且僅能為public
    (聲明為其他訪問修飾符會報錯)

  • 3、接口中的沒有變量,只有全局靜態常量。
    (看起來像常量,但是依然是靜態全局常量)

  • 4、實現接口的非抽象類必須要實現該接口的所有方法。抽象類可以不用實現。
    (接口中的方法不能是static、final或者private,也好理解,畢竟帶了這些就不能被@Override了)

  • 5、不能使用new操作符實例化一個接口,但可以聲明一個接口變量,該變量必須引用一個實現該接口的類的對象。通過這個做回調接口,這也開發中特別常見的。

  • 6、可以使用 instanceof 檢查一個對象是否實現了某個特定的接口。
    例如:if(anObject instanceof Comparable){}。

  • 7、在java8中,接口里也可以定義默認方法:

注意點:
在實現多接口的時候一定要避免方法名的重復。
(多實現的時候,如果接口重名會比較麻煩,所以起名要有規范)

public interface java8{
    //在接口里定義默認方法
    default void test(){
        System.out.println("java 新特性");
    }
}

基本特點如上,下面通過示例代碼大概看一下:

示例代碼

IStuent

public interface IStuent{
    int minAge = 9; // 默認會加上 public static final ,全局靜態常量
    void iStudentDohomeWord(); // 接口中的方法默認就是 abstract方法
    void iStudentGoToSchool();
}

.
.
IOther

interface IOther {
    void iOtherMethod();
}

.
.
AbsClass

// 抽象類實現接口,可以不復寫接口中的 抽象方法
public abstract class AbsClass implements IStudent{
    // 這里不復寫任何抽象方法沒問題
}

.
.
TestClass

public class TestClass implements IPerson,IStuent{
    public static void main(String[] args) {
        TestClass testClass = new TestClass();
        testClass.iPersonEat();
        testClass.iPersonSleep();
        testClass.iStudentDohomeWord();
        testClass.iStudentGoToSchool();
    
        //minAge = 12; // 會報錯,因為接口中的屬性都是全局靜態常量,不可以重新復制
        System.out.println("訪問接口中的全局靜態常量 "+minAge);
        
        // 判斷一個類是否實現了某個接口
        // 判斷方式1:   isAssignableFrom
         boolean result1 = IPerson.class.isAssignableFrom(TestClass.class);  
         System.out.println("IPerson.class.isAssignableFrom ---- IPerson:  "+result1);
         boolean result2 = IOther.class.isAssignableFrom(TestClass.class);  
         System.out.println("IOther.class.isAssignableFrom ---- IOther:  "+result2);
         
        // 判斷一個類是否實現了某個接口
            // 判斷方式2:  instanceof
         if(testClass instanceof IPerson){
             System.out.println("testClass instanceof IPerson:  true");
         }else{
             System.out.println("testClass instanceof IPerson:  false");
         }
         
         if(testClass instanceof IOther){
             System.out.println("testClass instanceof IOther:  true");
         }else{
             System.out.println("testClass instanceof IOther:  false");
         }
    }

    @Override
    public void iPersonEat() {
        System.out.println("學生是人,會吃東西");
    }

    @Override
    public void iPersonSleep() {
        System.out.println("學生是人,會吃睡覺");
    }

    @Override
    public void iStudentDohomeWord() {
        System.out.println("做作業,學生要做這個");
    }

    @Override
    public void iStudentGoToSchool() {
        System.out.println("上學,學生要做這個");
    }
}

// 這里不能寫public訪問修飾符,因為interface也是類,一個類Java文件中只能有一個public的類
interface IPerson{
    void iPersonEat();
    void iPersonSleep();
}

輸出結果

學生是人,會吃東西
學生是人,會吃睡覺
做作業,學生要做這個
上學,學生要做這個
訪問接口中的全局靜態常量 9
IPerson.class.isAssignableFrom ---- IPerson:  true
IOther.class.isAssignableFrom ---- IOther:  false
testClass instanceof IPerson:  true
testClass instanceof IOther:  false

由上可知
1、interface和class寫在同一個文件,因為class是public,所以inerface本你來只能寫public,但是現在不能寫了。
2、接口里面看起來像普通變量其實是全局靜態常量,不可以重新賦值
3、抽象類可以不用全部實現接口中的抽象方法
4、具體類需要實現接口中的全部抽象方法才可以實例化
5、可以通過isAssignableFrom或者instanceof判斷一個類是否實現了某個接口

五、回調接口

普通回調

我們現在通過簡單代碼演示一下點擊一個按鈕,觸發一些功能的邏輯。

ButtomClass

public class ButtomClass {
    private IBtnClick mBtnClick;
    // 通過對方開放的方法,給調用一個回調接口
    public void setOnClickListen(IBtnClick btnClick){
        mBtnClick = btnClick;
    }
    // 假設是系統按鈕內部的單擊,不讓外部調用
    private void systemClick(){
        System.out.println("系統內邏輯處理中,等待用戶操作");
        if(mBtnClick!=null){
            mBtnClick.onClick();
        }
    }
    // 假設是系統按鈕內部的雙擊,不讓外部調用
    private void systemDoubleClick(){
        System.out.println("系統內邏輯處理中,等待用戶操作");
        if(mBtnClick!=null){
            mBtnClick.onDoubleClick();
        }
    }
    // 假設是系統按鈕內部的長按,不讓外部調用
    private void systemLongClick(){
        System.out.println("系統內邏輯處理中,等待用戶操作");
        if(mBtnClick!=null){
            mBtnClick.onLongClick();
        }
    }
    
    //========= 以下是模擬用戶行為  =========
    // 模擬用戶單擊
    public void userDoClick(){
        systemClick();
    }
    
    // 模擬用戶雙擊
    public void userDoDoubleClick(){
        systemDoubleClick();
    }
    
    // 模擬用戶長按
    public void userDoLongClick(){
        systemLongClick();
    }
}

.
.
IBtnClick

public interface IBtnClick {
    void onClick();
    void onLongClick();
    void onDoubleClick();
}

.
.

TestClass

public class TestClass{
    public static void main(String[] args) {
        ButtomClass buttomClass = new ButtomClass();
        buttomClass.setOnClickListen(new IBtnClick() {
            @Override
            public void onLongClick() {
                System.out.println("外部回調,按鈕 長按 長按");
            }
            
            @Override
            public void onDoubleClick() {
                System.out.println("外部回調,按鈕 雙擊 雙擊");
            }
            
            @Override
            public void onClick() {
                System.out.println("外部回調,按鈕 單擊 單擊");
            }
        });
        buttomClass.userDoClick();
    }
}

假設ButtomClass是一個系統的按鈕控件。
這個按鈕有單擊,雙擊,長按三個事件,這些事件系統的內部處理肯定是對外隱藏的,開發者拿到這個控件的實例后,只需要通過 setOnClickListen 設置一個接口,復寫對應的方法,然后寫上我們點擊之后需要的邏輯,即可將邏輯回調回系統控件ButtomClass。

像上面這么寫,已經模擬完成了。

打印輸出:

系統內邏輯處理中,等待用戶操作
外部回調,按鈕 單擊 單擊

.
.
這一切沒什么問題,但是用的人可能覺得不爽,對一個按鈕來說,最常見是單擊,每次你都讓我復寫三個方法,我肯有可能 雙擊 和 長按 都是放空不寫的,代碼多余長了一些我不喜歡。

好,那現在我們就再優化一下代碼

升級版回調

第一步,新增一個抽象類
起名為FreeClickListen,然后先把方法再這里先復寫了。
相當于這是一個子接口

FreeClickListen

public abstract class FreeClickListen implements IBtnClick{
    @Override
    public void onClick() {
    }

    @Override
    public void onLongClick() {
    }

    @Override
    public void onDoubleClick() {
    }
    
}

.
.
第二步,
TestClass 代碼有小小的改動

public class TestClass{
    public static void main(String[] args) {
        ButtomClass buttomClass = new ButtomClass();
//        buttomClass.setOnClickListen(new IBtnClick() {
//            @Override
//            public void onLongClick() {
//                System.out.println("外部回調,按鈕 長按 長按");
//            }
//            
//            @Override
//            public void onDoubleClick() {
//                System.out.println("外部回調,按鈕 雙擊 雙擊");
//            }
//            
//            @Override
//            public void onClick() {
//                System.out.println("外部回調,按鈕 單擊 單擊");
//            }
//        });
        
        // 通過這種方式我們就可以不用必須復寫三個方法了,也不會覺得有多余的很長的代碼
        buttomClass.setOnClickListen(new FreeClickListen() {
            @Override
            public void onClick() {
                super.onClick();
                System.out.println("外部回調,按鈕 單擊 單擊 現在我可以只復寫我需要的了");
            }
            
        });
        buttomClass.userDoClick();
    }
}

完成。

打印輸出:

系統內邏輯處理中,等待用戶操作
外部回調,按鈕 單擊 單擊 現在我可以只復寫我需要的了

這種方式很常見,比如系統給我們的接口需要復寫很多個方法,通過這種方式,只需要多加一個抽象類,我們就可以在很多地方避免拖著長長的代碼,而在需要比如 長按 這些功能的時候,我們只需要在

buttomClass.setOnClickListen(new FreeClickListen() {
            @Override
            public void onClick() {
                super.onClick();
                System.out.println("外部回調,按鈕 單擊 單擊 現在我可以只復寫我需要的了");
            }
            
        });

里面復寫對應的方法即可。

通過這個可以舉一反三,比如一些網絡請求,我們可以通過類似的方式,調整一下,做統一處理,比如結果碼的統一處理。

省缺設配/選擇性復寫

比如一個interface里面有10個接口,子類每次實現,只有2個方法是必須強制復寫的,剩下都是可選項。
怎么做呢。其實就是根據上面的代碼,哪一個抽象類去實現一個接口,然后抽象類里面只復寫那些非強制的(就是8個非強制的),那么下次我們new 這個抽象類的時候,就必須強制復寫那2個強制的了。另外的8個變成可選項了。

還是來個代碼吧

接口

public interface IPostJsonStringCb {
    void onSuccess(String str);
    void onError(String str);
    void onStart(Request<String, ? extends Request> str);
    void onFinish();
}

.
抽象類

public abstract class  AbsPostJsonStringCb implements IPostJsonStringCb{
    
    // 抽象類里面復寫的方法后面作為 非必選方法。
    @Override
    public void onError(String str) {

    }

    @Override
    public void onStart(Request<String, ? extends Request> str) {

    }
}

.
剩下onSuccess和onFinish就是需要強制實現的了

image.png

六、接口和抽象類選哪一個

1、基本都是接口
2、什么時候用抽象類?

  • 需要定義子類的行為,有要為子類提供基礎性功能時
  • 做一個封裝,比如適配器模式等
比較 抽象類 接口
關鍵字 abstract interface
定義 包括一個抽象方法 都是抽象方法和全局靜態常量
組成 抽象方法,普通方法,變量,常量,構造 抽象方法,全局靜態常量
權限 不能private 只能是public
使用 通過extents繼承 通過implement實現
局限 抽象類只能單繼承 ---
順序 先繼承后實現,多個接口都好隔開 先繼承后實現,多個接口都好隔開
設計模式 模板模式等 工廠模式,代理模式等
結合 可以一起做個設配器模式 可以一起做個設配器模式

兩者都是依靠多態性,通過子類進行實例化。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容

  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,737評論 18 399
  • 你很清楚的知道什么時候用抽象類,什么時候用接口么?p.s. 多文字預警! 1 抽象類和接口簡介 1.1 抽象類 ...
    Sharember閱讀 2,361評論 9 55
  • 1、一個".java"源文件中是否可以包括多個類(不是內部類)?有什么限制?答:可以有多個類,但只能有一個publ...
    岳小川閱讀 957評論 0 2
  • 這個系列面試題主要目的是幫助你拿輕松到offer,同時還能開個好價錢。只要能夠搞明白這個系列的絕大多數題目,在面試...
    獨念白閱讀 350評論 0 3
  • 面向對象主要針對面向過程。 面向過程的基本單元是函數。 什么是對象:EVERYTHING IS OBJECT(萬物...
    sinpi閱讀 1,082評論 0 4