抽象類和接口

什么是抽象類和接口

使用 abstract 關(guān)鍵字修飾的方法被稱為 抽象方法 ,即只有聲明沒有方法體的方法。如下:

public abstract void function();

抽象類 就是包含抽象方法的類。

如果一個(gè)類包含一個(gè)或者多個(gè)抽象方法,該類必須被限定為抽象的。抽象類可以不包含抽象方法。

public abstract class BaseActivity {
    private final String TAG = this.getClass().getSimpleName(); //抽象類可以有成員

    void log(String msg){   //抽象類可以有具體方法
        System.out.println(msg);
    }

//    abstract void initView(); //抽象類也可以沒有抽象方法
}

接口 是抽象類的一種特殊形式,使用 interface 修飾。
public interface OnClickListener {
void onClick(View v);
}

抽象類的特點(diǎn):

抽象類是對(duì)類的抽象,具體實(shí)現(xiàn)是不確定的,所以不允許直接創(chuàng)建實(shí)例。

  • 抽象類是由子類具有相同的一類特征抽象而來,也可以說是其基類或者父類
  • 抽象方法必須為 public 或者 protected修飾(因?yàn)槿绻麨?private,則不能被子類繼承,子類便無法實(shí)現(xiàn)該方法),默認(rèn)是 public
  • 抽象類不能用來創(chuàng)建對(duì)象
  • 抽象方法必須由子類來實(shí)現(xiàn)
  • 如果一個(gè)類繼承于一個(gè)抽象類,則子類必須實(shí)現(xiàn)父類的抽象方法,如果子類沒有實(shí)現(xiàn)父類的抽象方法,則必須將子類也定義為抽象類
接口的特點(diǎn):

在java中為了保證數(shù)據(jù)安全性是不能多繼承的,一個(gè)類只能有一個(gè)父類。但是接口不同,一個(gè)類可以同時(shí)實(shí)現(xiàn)多個(gè)接口,不管這些接口之間有沒有關(guān)系,所以接口彌補(bǔ)了抽象類不能多繼承的缺陷。接口是抽象類的延伸,它可以定義沒有方法體的方法,要求實(shí)現(xiàn)者去實(shí)現(xiàn)。

  • 接口的所有方法訪問權(quán)限自動(dòng)被聲明為 public
  • 接口中可以定義“成員變量”,會(huì)自動(dòng)變?yōu)?public static final 修飾的靜態(tài)常量
  • 可以通過類命名直接訪問:ImplementClass.name
  • 不推薦使用接口創(chuàng)建常量類
  • 實(shí)現(xiàn)接口的非抽象類必須實(shí)現(xiàn)接口中所有方法,抽象類可以不用全部實(shí)現(xiàn)
  • 接口不能創(chuàng)建對(duì)象,但可以申明一個(gè)接口變量,方便調(diào)用
  • 完全解耦,可以編寫可復(fù)用性更好的代碼

如果我們的一個(gè)項(xiàng)目,需要寫大量的 Activity,這些 Activity 會(huì)有一些通用的屬性和方法,我們會(huì)創(chuàng)建一個(gè)基類,把這些通用的方法放進(jìn)去:它的作用就是:封裝重復(fù)的內(nèi)容。在 BaseActivity 里創(chuàng)建了一些抽象方法,要求子類必須實(shí)現(xiàn)。

public abstract class BaseActivity extends Activity {
    private final String TAG = this.getClass().getSimpleName();

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getContentViewLayoutId());

        initView(); //這里初始化布局
        loadData(); //這里加載數(shù)據(jù)
    }

    /**
     * 需要子類實(shí)現(xiàn)的方法
     * @return
     */
    protected abstract int getContentViewLayoutId();
    protected abstract void initView();
    protected abstract void loadData();

    void toast(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }
}

定義的抽象方法訪問權(quán)限修飾符可以是 public protecteddefault,但不能是 private,因?yàn)檫@樣子類就無法實(shí)現(xiàn)了。

這時(shí) BaseActivity 因?yàn)橛辛顺橄蠓椒?,變成了一個(gè)抽象類。它的作用就是:定義規(guī)范,強(qiáng)制子類符合標(biāo)準(zhǔn);如果有調(diào)用抽象方法,也會(huì)制定執(zhí)行順序的規(guī)則。

繼承 BaseActivity 的類只要實(shí)現(xiàn)這些方法,同時(shí)為父類提供需要的內(nèi)容,就可以和父類一樣保證代碼的整潔性。

public class MainActivity extends BaseActivity{

    private TextView mTitleTv;

    @Override
    protected int getContentViewLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    void initView() {
        mTitleTv = (TextView) findViewById(R.id.main_title_tv);
        mTitleTv.setOnClickListener(this);
    }

    @Override
    protected void loadData() {
        //這里加載數(shù)據(jù)
    }
}

以后如果發(fā)現(xiàn)有某些功能在不同 Activity 中重復(fù)出現(xiàn)的次數(shù)比較多,就可以把這個(gè)功能的實(shí)現(xiàn)提到 BaseActivity 中。注意不要輕易添加抽象方法,因?yàn)檫@會(huì)影響到之前的子類。

很多頁面都有根據(jù)定位信息改變而需要重新請(qǐng)求數(shù)據(jù)的情況,為了方便管理,我們要把這些代碼都放到 BaseActivity? 但是這樣一來,那些不需要定位相關(guān)的代碼就會(huì)冗余,邏輯太多。 BaseActivity 也會(huì)變得很復(fù)雜。我們想要把位置相關(guān)的放到另一個(gè)類,但是 Java 只有單繼承,這時(shí)就可以使用接口了。

我們創(chuàng)建一個(gè)接口表示對(duì)地理位置的監(jiān)聽:

interface OnLocationChangeListener {
    void onLocationUpdate(String locationInfo);
}

接口默認(rèn)是 public,不能使用其他修飾符。

這樣我們?cè)谛枰ㄎ坏捻撁胬飳?shí)現(xiàn)這個(gè)接口:

public class MainActivity extends BaseActivity implements View.OnClickListener,
        OnLocationChangeListener {

    private TextView mTitleTv;

    @Override
    protected int getContentViewLayoutId() {
        return R.layout.activity_main;
    }

    @Override
    public void onClick(final View v) {
        int id = v.getId();
        if (id == R.id.main_title_tv) {
            toast("你點(diǎn)擊了 title");
        }
    }

    @Override
    void initView() {
        mTitleTv = (TextView) findViewById(R.id.main_title_tv);
        mTitleTv.setOnClickListener(this);
    }

    @Override
    protected void loadData() {
        //這里加載數(shù)據(jù)
    }

    @Override
    public void onLocationUpdate(final String locationInfo) {
        mTitleTv.setText("現(xiàn)在位置是:" + locationInfo);
    }
}

這樣 MainActivity 就具有了監(jiān)聽位置改變的能力。如果 MainActivity 中需要添加其他功能,可以再創(chuàng)建對(duì)應(yīng)的接口,然后予以實(shí)現(xiàn)。

抽象類和接口的不同:
  • 抽象層次不同

  • 抽象類是對(duì)類抽象,而接口是對(duì)行為的抽象

  • 抽象類是對(duì)整個(gè)類整體進(jìn)行抽象,包括屬性、行為,但是接口卻是對(duì)類局部行為進(jìn)行抽象

  • 跨域不同

  • 抽象類所跨域的是具有相似特點(diǎn)的類,而接口卻可以跨域不同的類

  • 抽象類所體現(xiàn)的是一種繼承關(guān)系,考慮的是子類與父類本質(zhì)“是不是”同一類的關(guān)系

  • 而接口并不要求實(shí)現(xiàn)的類與接口是同一本質(zhì),它們之間只存在“有沒有這個(gè)能力”的關(guān)系

  • 設(shè)計(jì)層次不同

  • 抽象類是自下而上的設(shè)計(jì),在子類中重復(fù)出現(xiàn)的工作,抽象到抽象類中

  • 接口是自上而下,定義行為和規(guī)范

抽象類定義了“是什么”,可以有非抽象的屬性和方法;接口是更純的抽象類,在 Java 中可以實(shí)現(xiàn)多個(gè)接口,因此接口表示“具有什么能力”。在進(jìn)行選擇時(shí),可以參考以下幾點(diǎn):

  • 若使用接口,我們可以同時(shí)獲得抽象類以及接口的好處

  • 所以假如想創(chuàng)建的基類沒有任何方法定義或者成員變量,那么無論如何都愿意使用接口,而不要選擇抽象類

  • 如果事先知道某種東西會(huì)成為基礎(chǔ)類,那么第一個(gè)選擇就是把它變成一個(gè)接口

  • 只有在必須使用方法定義或者成員變量的時(shí)候,才應(yīng)考慮采用抽象類

使用接口最重要的一個(gè)原因:實(shí)現(xiàn)接口可以使一個(gè)類向上轉(zhuǎn)型至多個(gè)基礎(chǔ)類。

比如 SerializableCloneable 這樣常見的接口,一個(gè)類實(shí)現(xiàn)后就表示有這些能力,它可以被當(dāng)做 SerializableCloneable 進(jìn)行處理。

推薦接口和抽象類同時(shí)使用,這樣既保證了數(shù)據(jù)的安全性又可以實(shí)現(xiàn)多繼承。

面向接口編程

我們?cè)趯懘a時(shí)追求的是“以不變應(yīng)萬變”,在需求變更時(shí),盡可能少地修改代碼就可以實(shí)現(xiàn)。

需要模塊之間依賴時(shí),最好都只依賴對(duì)方給的抽象接口,而不是具體實(shí)現(xiàn)。

在設(shè)計(jì)模式里這就是“依賴倒置原則”,依賴倒置有三種方式來實(shí)現(xiàn):

  1. 通過構(gòu)造函數(shù)傳遞依賴對(duì)象 比如在構(gòu)造函數(shù)中的需要傳遞的參數(shù)是抽象類或接口的方式實(shí)現(xiàn)

  2. 通過 setter 方法傳遞依賴對(duì)象 即在我們?cè)O(shè)置的 setXXX 方法中的參數(shù)為抽象類或接口,來實(shí)現(xiàn)傳遞依賴對(duì)象

  3. 接口聲明實(shí)現(xiàn)依賴對(duì)象,也叫接口注入 即在函數(shù)聲明中參數(shù)為抽象類或接口,來實(shí)現(xiàn)傳遞依賴對(duì)象,從而達(dá)到直接使用依賴對(duì)象的目的。

可以看到,“面向接口編程”說的“接口”也包括抽象類,其實(shí)說的是基類,越簡(jiǎn)單越好。

多態(tài)

多態(tài)指的是編譯期只知道是個(gè)人,具體是什么樣的人需要在運(yùn)行時(shí)能確定,同樣的參數(shù)有可能會(huì)有不同的實(shí)現(xiàn)。

通過抽象建立規(guī)范,在運(yùn)行時(shí)替換成具體的對(duì)象,保證系統(tǒng)的擴(kuò)展性、靈活性。

實(shí)現(xiàn)多態(tài)主要有以下三種方式:

  1. 接口實(shí)現(xiàn)

  2. 繼承父類重寫方法

  3. 同一類中進(jìn)行方法重載

不論哪種實(shí)現(xiàn)方式,調(diào)用者持有的都是基類,不同的實(shí)現(xiàn)在他看來都是基類,使用時(shí)也當(dāng)基類用。

這就是“向上轉(zhuǎn)型”,即:子類在被調(diào)用過程中由繼承關(guān)系的下方轉(zhuǎn)變成上面的角色。

向上轉(zhuǎn)型是能力減少的過程,編譯器可以幫我們實(shí)現(xiàn);但 “向下轉(zhuǎn)型”是能力變強(qiáng)的過程,需要進(jìn)行強(qiáng)轉(zhuǎn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容