一、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就是需要強制實現的了
六、接口和抽象類選哪一個
1、基本都是接口
2、什么時候用抽象類?
- 需要定義子類的行為,有要為子類提供基礎性功能時
- 做一個封裝,比如適配器模式等
比較 | 抽象類 | 接口 |
---|---|---|
關鍵字 | abstract | interface |
定義 | 包括一個抽象方法 | 都是抽象方法和全局靜態常量 |
組成 | 抽象方法,普通方法,變量,常量,構造 | 抽象方法,全局靜態常量 |
權限 | 不能private | 只能是public |
使用 | 通過extents繼承 | 通過implement實現 |
局限 | 抽象類只能單繼承 | --- |
順序 | 先繼承后實現,多個接口都好隔開 | 先繼承后實現,多個接口都好隔開 |
設計模式 | 模板模式等 | 工廠模式,代理模式等 |
結合 | 可以一起做個設配器模式 | 可以一起做個設配器模式 |
兩者都是依靠多態性,通過子類進行實例化。