本系列文章經補充和完善,已修訂整理成書《Java程式設計的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連結
資料型別的侷限
之前我們一直在說,程式主要就是資料以及對資料的操作,而為了方便運算元據,高階語言引入了資料型別的概念,Java定義了八種基本資料型別,而類相當於是自定義資料型別,通過類的組合和繼承可以表示和操作各種事物或者說物件。
但,這種只是將物件看做屬於某種資料型別,並按該型別進行操作,在一些情況下,並不能反映物件以及對物件操作的本質。
為什麼這麼說呢?很多時候,我們實際上關心的,並不是物件的型別,而是物件的能力,只要能提供這個能力,型別並不重要。我們來看一些生活中的例子。
要拍個照片,很多時候,只要能拍出符合需求的照片就行,至於是用手機拍,還是用Pad拍,或者是用單反相機拍,並不重要,關心的是物件是否有拍出照片的能力,而並不關心物件到底是什麼型別,手機、Pad或單反相機都可以。
要計算一組數字,只要能計算出正確結果即可,至於是由人心算,用算盤算,用計算器算,用電腦軟體算,並不重要,關心的是物件是否有計算的能力,而並不關心物件到底是算盤還是計算器。
要將冷水加熱,只要能得到熱水即可,至於是用電磁爐加熱,用燃氣灶加熱,還是用電熱水壺,並不重要,重要的是物件是否有加熱水的能力,而並不關心物件到底是什麼型別。
在這些情況中,型別並不重要,重要的是能力。那如何表示能力呢?
介面的概念
Java使用介面這個概念來表示能力。
介面這個概念在生活中並不陌生,電子世界中一個常見的介面就是USB介面。電腦往往有多個USB介面,可以插各種USB裝置,可以是鍵盤、滑鼠、U盤、攝像頭、手機等等。
介面宣告瞭一組能力,但它自己並沒有實現這個能力,它只是一個約定,它涉及互動兩方物件,一方需要實現這個介面,另一方使用這個介面,但雙方物件並不直接互相依賴,它們只是通過介面間接互動。圖示如下:
拿上面的USB介面來說,USB協議約定了USB裝置需要實現的能力,每個USB裝置都需要實現這些能力,電腦使用USB協議與USB裝置互動,電腦和USB裝置互不依賴,但可以通過USB介面相互互動。
下面我們來看Java中的介面。
定義介面
我們通過一個例子來說明Java中介面的概念。
這個例子是"比較",很多物件都可以比較,對於求最大值、求最小值、排序的程式而言,它們其實並不關心物件的型別是什麼,只要物件可以比較就可以了,或者說,它們關心的是物件有沒有可比較的能力。Java API中提供了Comparable介面,以表示可比較的能力,但它使用了泛型,而我們還沒有介紹泛型,所以本節,我們自己定義一個Comparable介面,叫MyComparable。
現在,首先,我們來定義這個介面,程式碼如下:
public interface MyComparable {
int compareTo(Object other);
}
複製程式碼
解釋一下:
- Java使用interface這個關鍵字來宣告介面,修飾符一般都是public。
- interface後面就是介面的名字MyComparable。
- 介面定義裡面,宣告瞭一個方法compareTo,但沒有定義方法體,介面都不實現方法。介面方法不需要加修飾符,加與不加都是public的,不能是別的修飾符。
再來解釋一下compareTo方法:
- 方法的引數是一個Object型別的變數other,表示另一個參與比較的物件。
- 第一個參與比較的物件是自己
- 返回結果是int型別,-1表示自己小於引數物件,0表示相同,1表示大於引數物件
介面與類不同,它的方法沒有實現程式碼。定義一個介面本身並沒有做什麼,也沒有太大的用處,它還需要至少兩個參與者,一個需要實現介面,另一個使用介面,我們先來實現介面。
實現介面
類可以實現介面,表示類的物件具有介面所表示的能力。我們來看一個例子,以前面介紹過的Point類來說明,我們讓Point具備可以比較的能力,Point之間怎麼比較呢?我們假設按照與原點的距離進行比較,下面是Point類的程式碼:
public class Point implements MyComparable {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public double distance(){
return Math.sqrt(x*x+y*y);
}
@Override
public int compareTo(Object other) {
if(!(other instanceof Point)){
throw new IllegalArgumentException();
}
Point otherPoint = (Point)other;
double delta = distance() - otherPoint.distance();
if(delta<0){
return -1;
}else if(delta>0){
return 1;
}else{
return 0;
}
}
@Override
public String toString() {
return "("+x+","+y+")";
}
}
複製程式碼
我們解釋一下:
- Java使用implements這個關鍵字表示實現介面,前面是類名,後面是介面名。
- 實現介面必須要實現介面中宣告的方法,Point實現了compareTo方法。
我們再來解釋一下Point的compareTo實現:
- Point不能與其他型別的物件進行比較,它首先檢查要比較的物件是否是Point型別,如果不是,使用throw丟擲一個異常,異常我們還沒提到,後續文章講解,此處可以忽略。
- 如果是Point型別,使用強制型別轉換將Object型別的引數other轉換為Point型別的引數otherPoint。
- 這種顯式的型別檢查和強制轉換是可以使用泛型機制避免的,後續文章我們再介紹泛型。
一個類可以實現多個介面,表明類的物件具備多種能力,各個介面之間以逗號分隔,語法如下所示:
public class Test implements Interface1, Interface2 {
....
}
複製程式碼
定義和實現了介面,接下來我們來看怎麼使用介面。
使用介面
與類不同,介面不能new,不能直接建立一個介面物件,物件只能通過類來建立。但可以宣告介面型別的變數,引用實現了介面的類物件。比如說,可以這樣:
MyComparable p1 = new Point(2,3);
MyComparable p2 = new Point(1,2);
System.out.println(p1.compareTo(p2));
複製程式碼
p1和p2是MyComparable型別的變數,但引用了Point型別的物件,之所以能賦值是因為Point實現了MyComparable介面。如果一個型別實現了多個介面,那這種型別的物件就可以被賦值給任一介面型別的變數。
p1和p2可以呼叫MyComparable介面的方法,也只能呼叫MyComparable介面的方法,實際執行時,執行的是具體實現類的程式碼。
為什麼Point型別的物件非要賦值給MyComparable型別的變數呢?在以上程式碼中,確實沒必要。但在一些程式中,程式碼並不知道具體的型別,這才是介面發揮威力的地方,我們來看下面使用MyComparable介面的例子。
public class CompUtil {
public static Object max(MyComparable[] objs){
if(objs==null||objs.length==0){
return null;
}
MyComparable max = objs[0];
for(int i=1;i<objs.length;i++){
if(max.compareTo(objs[i])<0){
max = objs[i];
}
}
return max;
}
public static void sort(Comparable[] objs){
for(int i=0;i<objs.length;i++){
int min = i;
for(int j=i+1;j<objs.length;j++){
if(objs[j].compareTo(objs[min])<0){
min = j;
}
}
if(min!=i){
Comparable temp = objs[i];
objs[i] = objs[min];
objs[min] = temp;
}
}
}
}
複製程式碼
類CompUtil提供了兩個方法,max獲取傳入陣列中的最大值,sort對陣列升序排序,引數都是MyComparable型別的陣列。sort使用的是簡單選擇排序。
可以看出,這個類是針對MyComparable介面程式設計,它並不知道具體的型別是什麼,也並不關心,但卻可以對任意實現了MyComparable介面的型別進行操作。我們來看下對Point型別進行操作,程式碼如下:
Point[] points = new Point[]{
new Point(2,3),
new Point(3,4),
new Point(1,2)
};
System.out.println("max: " + CompUtil.max(points));
CompUtil.sort(points);
System.out.println("sort: "+ Arrays.toString(points));
複製程式碼
建立了一個Point型別的陣列points,然後使用CompUtil的max方法獲取最大值,使用sort排序,並輸出結果,輸出如下:
max: (3,4)
sort: [(1,2), (2,3), (3,4)]
複製程式碼
這裡演示的是對Point陣列操作,實際上可以針對任何實現了MyComparable介面的型別陣列進行操作。
這就是介面的威力,可以說,針對介面而非具體型別進行程式設計,是計算機程式的一種重要思維方式。針對介面,很多時候反映了物件以及對物件操作的本質。它的優點有很多,首先是程式碼複用,同一套程式碼可以處理多種不同型別的物件,只要這些物件都有相同的能力,如CompUtil。
更重要的是降低了耦合,提高了靈活性,使用介面的程式碼依賴的是介面本身,而非實現介面的具體型別,程式可以根據情況替換介面的實現,而不影響介面使用者。解決複雜問題的關鍵是分而治之,分解為小問題,但小問題之間不可能一點關係沒有,分解的核心就是要降低耦合,提高靈活性,介面為恰當分解,提供了有力的工具。
介面的細節
上面我們介紹了介面的基本內容,介面還有一些細節,包括:
- 介面中的變數
- 介面的繼承
- 類的繼承與介面
- instanceof
我們逐個來介紹下。
介面中的變數
介面中可以定義變數,語法如下所示:
public interface Interface1 {
public static final int a = 0;
}
複製程式碼
這裡定義了一個變數int a,修飾符是public static final,但這個修飾符是可選的,即使不寫,也是public static final。這個變數可以通過"介面名.變數名"的方式使用,如Interface1.a。
介面的繼承
介面也可以繼承,一個介面可以繼承別的介面,繼承的基本概念與類一樣,但與類不同,介面可以有多個父介面,程式碼如下所示:
public interface IBase1 {
void method1();
}
public interface IBase2 {
void method2();
}
public interface IChild extends IBase1, IBase2 {
}
複製程式碼
介面的繼承同樣使用extends關鍵字,多個父介面之間以逗號分隔。
類的繼承與介面
類的繼承與介面可以共存,換句話說,類可以在繼承基類的情況下,同時實現一個或多個介面,語法如下所示:
public class Child extends Base implements IChild {
//...
}
複製程式碼
extends要放在implements之前。
instanceof
與類一樣,介面也可以使用instanceof關鍵字,用來判斷一個物件是否實現了某介面,例如:
Point p = new Point(2,3);
if(p instanceof MyComparable){
System.out.println("comparable");
}
複製程式碼
使用介面替代繼承
上節我們提到,可以使用介面替代繼承。怎麼替代呢?
我們說繼承至少有兩個好處,一個是複用程式碼,另一個是利用多型和動態繫結統一處理多種不同子類的物件。
使用組合替代繼承,可以複用程式碼,但不能統一處理。使用介面,針對介面程式設計,可以實現統一處理不同型別的物件,但介面沒有程式碼實現,無法複用程式碼。將組合和介面結合起來,就既可以統一處理,也可以複用程式碼了。我們還是以上節的例子來說明。
我們先增加一個介面IAdd,程式碼如下:
public interface IAdd {
void add(int number);
void addAll(int[] numbers);
}
複製程式碼
修改Base程式碼,讓他實現IAdd介面,程式碼基本不變:
public class Base implements IAdd {
//...
}
複製程式碼
修改Child程式碼,也是實現IAdd介面,程式碼基本不變:
public class Child implements IAdd {
//...
}
複製程式碼
這樣,就既可以複用程式碼,也可以統一處理,而且不用擔心破壞封裝了。
小結
本節我們談了資料型別思維的侷限,提到了很多時候關心的是能力,而非型別,所以引入了介面,介紹了Java中介面的概念和細節,針對介面程式設計是一種重要的程式思維方式,這種方式不僅可以複用程式碼,還可以降低耦合,提高靈活性,是分解複雜問題的一種重要工具。
介面沒有任何實現程式碼,而之前介紹的類都有完整的實現,都可以建立物件,Java中還有一個介於介面和類之間的概念,抽象類,它有什麼用呢?
未完待續,檢視最新文章,敬請關注微信公眾號“老馬說程式設計”(掃描下方二維碼),深入淺出,老馬和你一起探索Java程式設計及計算機技術的本質。用心原創,保留所有版權。