一庐扫、abstract和接口初認(rèn)識(shí)
abstract class和interface是Java語言中對于抽象類定義進(jìn)行支持的兩種機(jī)制。abstract class和interface之間在對于抽象類定義的支持方面具有很大的相似性爹梁,甚至可以相互替換,因此很多開發(fā)者在進(jìn)行抽象類定義時(shí)對于abstract class和interface的選擇顯得比較隨意。其實(shí)同眯,兩者之間還是有很大的區(qū)別的,對于它們的選擇甚至反映出對于問題領(lǐng)域本質(zhì)的理解唯鸭、對于設(shè)計(jì)意圖的理解是否正確须蜗、合理。
abstract 關(guān)鍵字可以修飾類或方法。
抽象方法
Java中可以利用abstract關(guān)鍵字定義一些不包括方法體的方法明肮,沒有方法體就沒有實(shí)現(xiàn)菱农,具體實(shí)現(xiàn)交給子類柿估,這樣的方法稱為 抽象方法
抽象類
有抽象方法的類就是抽象類
接口
一個(gè)類中如果所有方法都是abstract 方法循未,那么這個(gè)類我們可以利用 interface 定義成接口。
二秫舌、 抽象方法
什么是抽象方法
聲明為 abstract 的方法就是抽象方法的妖。
抽象方法的寫法
abstract 返回值類型 方法名(參數(shù)列表);
抽象方法的特點(diǎn)
抽象方法只有聲明,沒有實(shí)現(xiàn)足陨,抽象方法必須由子類進(jìn)行重寫實(shí)現(xiàn)
沒有實(shí)現(xiàn)就是沒有方法體(方法體就是方法后面的花括號(hào))嫂粟,意味著這是一個(gè)沒有完成的方法,抽象的墨缘。抽象方法只能由子類去重寫實(shí)現(xiàn)abstract 關(guān)鍵字不能應(yīng)用于 static赋元、private 或 final 方法,因?yàn)檫@些方法不能被重寫飒房。
三搁凸、 抽象類
什么是抽象類
有抽象方法的類就是抽象類
抽象類的寫法
public abstract class 類名{}
抽象類的特點(diǎn)
- 1、抽象類不能被實(shí)例化,實(shí)例化就必須實(shí)現(xiàn)全部方法狠毯,實(shí)現(xiàn)全部方法后這個(gè)類就不是抽象類护糖。(實(shí)例化沒意義),但可以有構(gòu)造函數(shù)
- 2嚼松、抽象方法必須由子類進(jìn)行重寫實(shí)現(xiàn)
- 3嫡良、子類繼承自抽象類,必須實(shí)現(xiàn)抽象類的全部抽象方法献酗,這樣的子類也叫具體類寝受,具體類才可以被實(shí)例化(這個(gè)實(shí)現(xiàn)全部方法方法的子類不是抽象類,抽象類不能實(shí)例化)罕偎。如果沒有實(shí)現(xiàn)全部抽象方法很澄,那么這個(gè)子類必須也是抽象類。
- 4颜及、一個(gè)類只要出現(xiàn)了abstract方法甩苛,那么這個(gè)類就必須是abstract類
- 5、抽象類中可以有非抽象方法俏站,可以有變量
如果一個(gè)類中方法都是抽象方法讯蒲,那么我們就可以把這個(gè)類定義成接口。
(接口是一種特殊的類肄扎,接口也是類)
代碼示例
接下看通過簡單的代碼墨林,看一下抽象類和抽象方法
AbsPerson
public abstract class AbsPerson {
public int number = 16;
{
System.out.println("抽象類的代碼塊");
}
public AbsPerson(){
System.out.println("抽象類的構(gòu)造函數(shù)");
}
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("學(xué)生類的代碼塊");
}
public Student() {
System.out.println("學(xué)生類的構(gòu)造函數(shù)");
}
@Override
public void say(String str) {
System.out.println(str);
}
@Override
public void age() {
System.out.println("年齡18");
}
}
.
.
AbsPerson
// 沒實(shí)現(xiàn) AbsPerson 這個(gè)抽象類的所有方法,所以這個(gè)類也是抽象類
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");// 子類(具體類)調(diào)用實(shí)現(xiàn)自抽象類的方法
student.age();// 子類(具體類)調(diào)用實(shí)現(xiàn)自抽象類的方法
student.absPersonNormal();// 子類(具體類)調(diào)用抽象類的 非抽象方法
System.out.println("子類調(diào)用抽象類的變量: "+student.number);
}
}
.
.
運(yùn)行結(jié)果:
抽象類的代碼塊
抽象類的構(gòu)造函數(shù)
學(xué)生類的代碼塊
學(xué)生類的構(gòu)造函數(shù)
day day up
年齡18
抽象類的普通方法旭等,或者叫 非抽象方法
子類調(diào)用抽象類的變量: 16
代碼已經(jīng)說得很清楚了赞哗。
四、接口
如果一個(gè)類中方法都是抽象方法辆雾,那么我們就可以把這個(gè)類定義成接口肪笋。
接口的出現(xiàn)讓類不必受限于單一繼承的束縛,可以靈活地繼承一些共有的特性度迂,間接實(shí)現(xiàn)類似多繼承的目的藤乙。
接口里面只可能有兩種東西:
- 1、抽象方法
- 2惭墓、全局靜態(tài)常量
(接口中沒有變量坛梁,默認(rèn)都是用 public static final標(biāo)識(shí)的,不過在interface中一般不定義屬性成員腊凶,只定義抽象方法)
接口的特點(diǎn):
1划咐、接口的訪問修飾符只能是public,或者不寫* 2钧萍、interface中定義的方法和成員變量褐缠,默認(rèn)為public訪問權(quán)限,且僅能為public
(聲明為其他訪問修飾符會(huì)報(bào)錯(cuò))3风瘦、接口中的沒有變量队魏,只有全局靜態(tài)常量。
(看起來像常量万搔,但是依然是靜態(tài)全局常量)4胡桨、實(shí)現(xiàn)接口的非抽象類必須要實(shí)現(xiàn)該接口的所有方法。抽象類可以不用實(shí)現(xiàn)瞬雹。
(接口中的方法不能是static昧谊、final或者private,也好理解酗捌,畢竟帶了這些就不能被@Override了)5、不能使用new操作符實(shí)例化一個(gè)接口意敛,但可以聲明一個(gè)接口變量,該變量必須引用一個(gè)實(shí)現(xiàn)該接口的類的對象草姻。通過這個(gè)做回調(diào)接口撩独,這也開發(fā)中特別常見的敞曹。
6综膀、可以使用 instanceof 檢查一個(gè)對象是否實(shí)現(xiàn)了某個(gè)特定的接口。
例如:if(anObject instanceof Comparable){}橄登。7、在java8中拢锹,接口里也可以定義默認(rèn)方法:
注意點(diǎn):
在實(shí)現(xiàn)多接口的時(shí)候一定要避免方法名的重復(fù)萄喳。
(多實(shí)現(xiàn)的時(shí)候,如果接口重名會(huì)比較麻煩他巨,所以起名要有規(guī)范)
public interface java8{
//在接口里定義默認(rèn)方法
default void test(){
System.out.println("java 新特性");
}
}
基本特點(diǎn)如上,下面通過示例代碼大概看一下:
示例代碼
IStuent
public interface IStuent{
int minAge = 9; // 默認(rèn)會(huì)加上 public static final 染突,全局靜態(tài)常量
void iStudentDohomeWord(); // 接口中的方法默認(rèn)就是 abstract方法
void iStudentGoToSchool();
}
.
.
IOther
interface IOther {
void iOtherMethod();
}
.
.
AbsClass
// 抽象類實(shí)現(xiàn)接口捻爷,可以不復(fù)寫接口中的 抽象方法
public abstract class AbsClass implements IStudent{
// 這里不復(fù)寫任何抽象方法沒問題
}
.
.
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; // 會(huì)報(bào)錯(cuò),因?yàn)榻涌谥械膶傩远际侨朱o態(tài)常量份企,不可以重新復(fù)制
System.out.println("訪問接口中的全局靜態(tài)常量 "+minAge);
// 判斷一個(gè)類是否實(shí)現(xiàn)了某個(gè)接口
// 判斷方式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);
// 判斷一個(gè)類是否實(shí)現(xiàn)了某個(gè)接口
// 判斷方式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("學(xué)生是人役衡,會(huì)吃東西");
}
@Override
public void iPersonSleep() {
System.out.println("學(xué)生是人,會(huì)吃睡覺");
}
@Override
public void iStudentDohomeWord() {
System.out.println("做作業(yè)薪棒,學(xué)生要做這個(gè)");
}
@Override
public void iStudentGoToSchool() {
System.out.println("上學(xué)手蝎,學(xué)生要做這個(gè)");
}
}
// 這里不能寫public訪問修飾符,因?yàn)閕nterface也是類俐芯,一個(gè)類Java文件中只能有一個(gè)public的類
interface IPerson{
void iPersonEat();
void iPersonSleep();
}
輸出結(jié)果
學(xué)生是人棵介,會(huì)吃東西
學(xué)生是人,會(huì)吃睡覺
做作業(yè)吧史,學(xué)生要做這個(gè)
上學(xué)邮辽,學(xué)生要做這個(gè)
訪問接口中的全局靜態(tài)常量 9
IPerson.class.isAssignableFrom ---- IPerson: true
IOther.class.isAssignableFrom ---- IOther: false
testClass instanceof IPerson: true
testClass instanceof IOther: false
由上可知
1、interface和class寫在同一個(gè)文件贸营,因?yàn)閏lass是public吨述,所以inerface本你來只能寫public,但是現(xiàn)在不能寫了钞脂。
2揣云、接口里面看起來像普通變量其實(shí)是全局靜態(tài)常量,不可以重新賦值
3冰啃、抽象類可以不用全部實(shí)現(xiàn)接口中的抽象方法
4邓夕、具體類需要實(shí)現(xiàn)接口中的全部抽象方法才可以實(shí)例化
5刘莹、可以通過isAssignableFrom或者instanceof判斷一個(gè)類是否實(shí)現(xiàn)了某個(gè)接口
五、回調(diào)接口
普通回調(diào)
我們現(xiàn)在通過簡單代碼演示一下點(diǎn)擊一個(gè)按鈕焚刚,觸發(fā)一些功能的邏輯点弯。
ButtomClass
public class ButtomClass {
private IBtnClick mBtnClick;
// 通過對方開放的方法,給調(diào)用一個(gè)回調(diào)接口
public void setOnClickListen(IBtnClick btnClick){
mBtnClick = btnClick;
}
// 假設(shè)是系統(tǒng)按鈕內(nèi)部的單擊矿咕,不讓外部調(diào)用
private void systemClick(){
System.out.println("系統(tǒng)內(nèi)邏輯處理中抢肛,等待用戶操作");
if(mBtnClick!=null){
mBtnClick.onClick();
}
}
// 假設(shè)是系統(tǒng)按鈕內(nèi)部的雙擊,不讓外部調(diào)用
private void systemDoubleClick(){
System.out.println("系統(tǒng)內(nèi)邏輯處理中碳柱,等待用戶操作");
if(mBtnClick!=null){
mBtnClick.onDoubleClick();
}
}
// 假設(shè)是系統(tǒng)按鈕內(nèi)部的長按捡絮,不讓外部調(diào)用
private void systemLongClick(){
System.out.println("系統(tǒng)內(nèi)邏輯處理中锦援,等待用戶操作");
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("外部回調(diào)灵寺,按鈕 長按 長按");
}
@Override
public void onDoubleClick() {
System.out.println("外部回調(diào)略板,按鈕 雙擊 雙擊");
}
@Override
public void onClick() {
System.out.println("外部回調(diào)叮称,按鈕 單擊 單擊");
}
});
buttomClass.userDoClick();
}
}
假設(shè)ButtomClass是一個(gè)系統(tǒng)的按鈕控件瓤檐。
這個(gè)按鈕有單擊挠蛉,雙擊谴古,長按三個(gè)事件掰担,這些事件系統(tǒng)的內(nèi)部處理肯定是對外隱藏的带饱,開發(fā)者拿到這個(gè)控件的實(shí)例后横媚,只需要通過 setOnClickListen 設(shè)置一個(gè)接口灯蝴,復(fù)寫對應(yīng)的方法,然后寫上我們點(diǎn)擊之后需要的邏輯耕肩,即可將邏輯回調(diào)回系統(tǒng)控件ButtomClass问潭。
像上面這么寫,已經(jīng)模擬完成了梳虽。
打印輸出:
系統(tǒng)內(nèi)邏輯處理中窜觉,等待用戶操作
外部回調(diào)禀挫,按鈕 單擊 單擊
.
.
這一切沒什么問題语婴,但是用的人可能覺得不爽驶睦,對一個(gè)按鈕來說场航,最常見是單擊,每次你都讓我復(fù)寫三個(gè)方法酬核,我肯有可能 雙擊 和 長按 都是放空不寫的嫡意,代碼多余長了一些我不喜歡蔬螟。
好旧巾,那現(xiàn)在我們就再優(yōu)化一下代碼
升級(jí)版回調(diào)
第一步,新增一個(gè)抽象類
起名為FreeClickListen坎怪,然后先把方法再這里先復(fù)寫了搅窿。
相當(dāng)于這是一個(gè)子接口
FreeClickListen
public abstract class FreeClickListen implements IBtnClick{
@Override
public void onClick() {
}
@Override
public void onLongClick() {
}
@Override
public void onDoubleClick() {
}
}
.
.
第二步男应,
TestClass 代碼有小小的改動(dòng)
public class TestClass{
public static void main(String[] args) {
ButtomClass buttomClass = new ButtomClass();
// buttomClass.setOnClickListen(new IBtnClick() {
// @Override
// public void onLongClick() {
// System.out.println("外部回調(diào)沐飘,按鈕 長按 長按");
// }
//
// @Override
// public void onDoubleClick() {
// System.out.println("外部回調(diào)耐朴,按鈕 雙擊 雙擊");
// }
//
// @Override
// public void onClick() {
// System.out.println("外部回調(diào)隔箍,按鈕 單擊 單擊");
// }
// });
// 通過這種方式我們就可以不用必須復(fù)寫三個(gè)方法了脚乡,也不會(huì)覺得有多余的很長的代碼
buttomClass.setOnClickListen(new FreeClickListen() {
@Override
public void onClick() {
super.onClick();
System.out.println("外部回調(diào)俯艰,按鈕 單擊 單擊 現(xiàn)在我可以只復(fù)寫我需要的了");
}
});
buttomClass.userDoClick();
}
}
完成竹握。
打印輸出:
系統(tǒng)內(nèi)邏輯處理中辆飘,等待用戶操作
外部回調(diào)蜈项,按鈕 單擊 單擊 現(xiàn)在我可以只復(fù)寫我需要的了
這種方式很常見侥衬,比如系統(tǒng)給我們的接口需要復(fù)寫很多個(gè)方法直颅,通過這種方式功偿,只需要多加一個(gè)抽象類脖含,我們就可以在很多地方避免拖著長長的代碼投蝉,而在需要比如 長按 這些功能的時(shí)候,我們只需要在
buttomClass.setOnClickListen(new FreeClickListen() {
@Override
public void onClick() {
super.onClick();
System.out.println("外部回調(diào)庸娱,按鈕 單擊 單擊 現(xiàn)在我可以只復(fù)寫我需要的了");
}
});
里面復(fù)寫對應(yīng)的方法即可熟尉。
通過這個(gè)可以舉一反三斤儿,比如一些網(wǎng)絡(luò)請求往果,我們可以通過類似的方式,調(diào)整一下肮之,做統(tǒng)一處理戈擒,比如結(jié)果碼的統(tǒng)一處理。
省缺設(shè)配/選擇性復(fù)寫
比如一個(gè)interface里面有10個(gè)接口,子類每次實(shí)現(xiàn),只有2個(gè)方法是必須強(qiáng)制復(fù)寫的诫欠,剩下都是可選項(xiàng)轿偎。
怎么做呢。其實(shí)就是根據(jù)上面的代碼,哪一個(gè)抽象類去實(shí)現(xiàn)一個(gè)接口仓蛆,然后抽象類里面只復(fù)寫那些非強(qiáng)制的(就是8個(gè)非強(qiáng)制的)看疙,那么下次我們new 這個(gè)抽象類的時(shí)候,就必須強(qiáng)制復(fù)寫那2個(gè)強(qiáng)制的了。另外的8個(gè)變成可選項(xiàng)了丰涉。
還是來個(gè)代碼吧
接口
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{
// 抽象類里面復(fù)寫的方法后面作為 非必選方法傻唾。
@Override
public void onError(String str) {
}
@Override
public void onStart(Request<String, ? extends Request> str) {
}
}
.
剩下onSuccess和onFinish就是需要強(qiáng)制實(shí)現(xiàn)的了
六伪煤、接口和抽象類選哪一個(gè)
1、基本都是接口
2蚀之、什么時(shí)候用抽象類?
- 需要定義子類的行為,有要為子類提供基礎(chǔ)性功能時(shí)
- 做一個(gè)封裝,比如適配器模式等
比較 | 抽象類 | 接口 |
---|---|---|
關(guān)鍵字 | abstract | interface |
定義 | 包括一個(gè)抽象方法 | 都是抽象方法和全局靜態(tài)常量 |
組成 | 抽象方法,普通方法,變量劈狐,常量,構(gòu)造 | 抽象方法续膳,全局靜態(tài)常量 |
權(quán)限 | 不能private | 只能是public |
使用 | 通過extents繼承 | 通過implement實(shí)現(xiàn) |
局限 | 抽象類只能單繼承 | --- |
順序 | 先繼承后實(shí)現(xiàn)社付,多個(gè)接口都好隔開 | 先繼承后實(shí)現(xiàn),多個(gè)接口都好隔開 |
設(shè)計(jì)模式 | 模板模式等 | 工廠模式绵咱,代理模式等 |
結(jié)合 | 可以一起做個(gè)設(shè)配器模式 | 可以一起做個(gè)設(shè)配器模式 |
兩者都是依靠多態(tài)性悲伶,通過子類進(jìn)行實(shí)例化。