什么是抽象類和接口
使用 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 protected 和 default,但不能是 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ǔ)類。
比如 Serializable 和 Cloneable 這樣常見的接口,一個(gè)類實(shí)現(xiàn)后就表示有這些能力,它可以被當(dāng)做 Serializable 和 Cloneable 進(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):
通過構(gòu)造函數(shù)傳遞依賴對(duì)象 比如在構(gòu)造函數(shù)中的需要傳遞的參數(shù)是抽象類或接口的方式實(shí)現(xiàn)
通過 setter 方法傳遞依賴對(duì)象 即在我們?cè)O(shè)置的 setXXX 方法中的參數(shù)為抽象類或接口,來實(shí)現(xiàn)傳遞依賴對(duì)象
接口聲明實(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)主要有以下三種方式:
接口實(shí)現(xiàn)
繼承父類重寫方法
同一類中進(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)