Java 核心技術點之接口
為什么使用接口
Java中的接口是一組對需求的描述。接口通過聲明接口方法來對外宣布:“要想具有XX功能,就得按我說的做(即實現接口方法)。” 而接口的實現類通過實現相應接口的方法來宣布:“我已經按你說的做了,請賜予我XX功能吧!” 例如,以下是Comparable<T>接口的定義:
public interface Comparable<T> {
int compareTo(T o);
}
Comparable<T>接口中定義了一個compareTo方法,這個方法就是它所描述的需求。也就是說Comparable接口對外宣布:“要想擁有進行比較的功能,得按我說的做(實現compareTo方法)。” 若我們想調用Arrays.sort方法對一個People對象數組進行比較,那么People對象必須是”可比較的”,即People類需要實現Comparable<T>接口。接口的實現類需要實現接口中定義的方法。People類通過實現Comparable<T>接口的方法來宣稱:”我已經按你說的做啦,請讓我變為可比較的吧!“ 也就是說,接口描述了一組需求,而實現一個接口的類就需要實現這個接口所描述的需求。比如我們想要People類對象是可比較的,我們可以這樣:
public class People implements Comparable<People> {
...
public int compareTo(People p) {
//定義具體的比較標準
}
...
}
實際上,Arrays.sort方法之所以要求它所比較的對象需實現了Comparable接口,是因為它在比較對象時調用的對象的compareTo方法(因為它不知道評價一個對象大小的標準,這個標準是由我們來定的)。那么,我們為什么不直接在People類中定義一個compareTo實例方法來定義People對象的比較標準,而是要去實現一個Comparable<T>接口呢?讓我們舉例來說明一下,假如我們調用了以下代碼來對People對象數組peoples進行比較:
Arrays.sort(peoples);
在sort方法內部,實際上調用了類似下面這樣的代碼來比較People對象:
if (peoples[i].compareTo(peoples[j]) > 0) {
//若為true說明peoples[i] 大于peoples[j]
}
也就是說Arrays.sort方法內部調用了People對象的compareTo方法,那么編譯器如何知道People類中確實定義了一個方法呢?若People類沒有實現Comparable接口,編譯器就只有檢查這個類是否實現了這個方法,這樣做無疑會增大開銷,若接口中的方法不只一個,開銷就更大了。而且需要比較的對象可能不只一個,假如后面又有Date對象、Job對象需要我們比較呢?如果每次調用相應對象的compareTo方法都要去檢查一下它究竟實現了這個方法沒有,將無形中增加很多本不必要的開銷。
反過來,我們看看引入接口的好處。People類頭部的"implements Comparable<People>“就像是在告訴編譯器”我是可比較的,可以直接調用我的compareTo方法而不用檢查我有沒有這個方法“。這樣一來,所有可比較的對象只要實現Comparable<T>接口,編譯器就能夠知道它一定定義了compareTo方法。一個接口實際上是描述了一種規范,它只說明了這個接口需要實現什么需求,而沒有強制規定這個需求如何實現。就像之前我們提到的Comparable<T>接口,需要比較大小的對象不只People對象一個,既然大家都需要比較大小,那索性就來個規定,所有想比較大小的對象的類都實現Compareble<T>接口,統一規定一下可比較的對象究竟該滿足什么需求(這里的需求就是compareTo方法)。
接口的特性
既然已經知道了為什么要使用接口,接下來讓我們多了解下接口到底有什么特性。
首先,接口本身的訪問修飾符可以為public或protected,接口中的所有方法都只能是public(所以可以不加這個修飾符)。其次接口中可以定義常量,但不能包含實例變量(提供實例變量這個活應該交給接口的實現類)。使用接口的步驟也很簡單,正如我們上面提到的,只需要用關鍵子implements加上接口名表示這個類要實現這個接口,然后在類的定義中定義接口所要求實現的方法即可。
接口只是一種規范,所以我們不能用new實例化一個接口。但是我們可以聲明接口類型的變量,接口變量只能引用實現了該接口的類對象,比如以下代碼:
Comparable<People> p = new People(...);
雖然接口不是類,但我們也可以使用instanceof來判斷一個對象是否實現了某個接口:
if (p instanceof Comparable) {
...
}
我們也可以”繼承“一個接口:
public interface InA extends InB {
...
}
接口還有一個很有用的特性就是一個類可以實現多個接口。其實以上提到的接口特性只要”接口只是一種規范”這個本質來看就很好理解:比如接口中不能實例化、不能含有實例變量,就是因為接口不同于類那樣是一個“藍圖”,接口更像一個“標簽”。
接口與抽象類的比較
接口與抽象類的一個本質區別是,抽象類只是一種含有抽象方法的類。也就是說,抽象類是對常規類的進一步”抽象“。常規類是可以直接實例化的,因為它所定義的方法都有了明確的實現,也就是說相應類對象的行為應經能夠完全確定下來。而抽象類包含抽象方法,也就是說對象的一部分行為還沒有完全確定下來。可以把抽象方法也理解成一種需求描述,因為抽象類存在著”待解決的需求“,所以它不能實例化,只能先派生出子類,解決了這個需求后(也就是實現了抽象方法),從而把實例的整個“藍圖”確定下來,才能夠實例化。比如說抽象類People中定義一個write抽象方法,因為寫這個動作不同人可以選擇不同的方式完成(比如有的人是左撇子,拿筆姿勢各不相同)。因為抽象類是一種類,所以一個類只能繼承一個抽象類,而不能像接口那樣可以“多繼承”。
另一方面,抽象類由于本質上是一個類,所以可以包含實例變量,也可包含方法的實現。接口中只能含有public方法,而接口中的常量也都是public的(因為作為一種規范就要”公開透明“);而抽象類可以含有private變量及方法。抽象類中只有抽象方法需要派生類(假設派生來非抽象類)實現,而實現接口的類必須實現接口中規定的所有方法。這樣一來,若我們向某個接口中新增一個方法,那么所有實現了這個接口的類都需要進行相應的改動;而對于抽象類,我們向其中添加非抽象方法,不需要它的派生類做任何改動。
接口與回調(callback)
回調(callback)是一種常見的設計模式,利用這種模式,我們可以指出在某個事件發生時應該采取什么動作。比如,Java類庫中有個Timer類,可以使通過它在過了指定的時間后發出通知,就像一個鬧鐘一樣。初始化Timer類時,需要指定一個時間和到達這個時間后要執行的動作。在這里,告訴Timer要執行的動作就是通過傳遞給它一個對象,然后到達指定時間后,Timer會調用這個對象的方法。Timer要求我們傳遞給它的對象需要實現ActionListener接口,這個接口的定義如下:
public interface ActionListener {
public void actionPerformed(ActionEvent event);
}
我們傳遞給Timer構造器的對象必然定義了actionPerformed方法,我們可以在這個方法中定義我們在定時間隔到達后需要執行的動作。請看以下代碼:
1 public class TimerTest {
2 public static void main(String[] args) {
3 ActionListener listener = new TimePrinter();
4 Timer t = new Timer(10000, listener);
5 t.start();
6 System.exit(0);
7 }
8 }
9
10 class TimePrinter implements ActionListener {
11 public void actionPerformed(ActionEvent event) {
12 Date now = new Date();
13 System.out.println("The time is " + now);
14 }
15 }
在以上代碼中,會每隔10秒調用一次TimerPrinter中的actionPerformed方法,獲取并輸出一次當前時間。actionPerformed方法就是回調方法,在回調模式中,通常都是一個類(TimePrinter)實現一個接口(ActionListener),然后另一個類(Timer)的對象通過持有實現該接口的類對象引用(listener)來調用相應的回調方法。
來自:http://www.jianshu.com/p/e9e02bca7763