本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連接 html
以前咱們一直在說,程序主要就是數據以及對數據的操做,而爲了方便操做數據,高級語言引入了數據類型的概念,Java定義了八種基本數據類型,而類至關因而自定義數據類型,經過類的組合和繼承能夠表示和操做各類事物或者說對象。java
但,這種只是將對象看作屬於某種數據類型,並按該類型進行操做,在一些狀況下,並不能反映對象以及對對象操做的本質。編程
爲何這麼說呢?不少時候,咱們實際上關心的,並非對象的類型,而是對象的能力,只要能提供這個能力,類型並不重要。咱們來看一些生活中的例子。數組
要拍個照片,不少時候,只要能拍出符合需求的照片就行,至因而用手機拍,仍是用Pad拍,或者是用單反相機拍,並不重要,關心的是對象是否有拍出照片的能力,而並不關心對象究竟是什麼類型,手機、Pad或單反相機均可以。微信
要計算一組數字,只要能計算出正確結果便可,至因而由人心算,用算盤算,用計算器算,用電腦軟件算,並不重要,關心的是對象是否有計算的能力,而並不關心對象究竟是算盤仍是計算器。ide
要將冷水加熱,只要能獲得熱水便可,至因而用電磁爐加熱,用燃氣竈加熱,仍是用電熱水壺,並不重要,重要的是對象是否有加熱水的能力,而並不關心對象究竟是什麼類型。工具
在這些狀況中,類型並不重要,重要的是能力。那如何表示能力呢?this
Java使用接口這個概念來表示能力。spa
接口這個概念在生活中並不陌生,電子世界中一個常見的接口就是USB接口。電腦每每有多個USB接口,能夠插各類USB設備,能夠是鍵盤、鼠標、U盤、攝像頭、手機等等。3d
接口聲明瞭一組能力,但它本身並無實現這個能力,它只是一個約定,它涉及交互兩方對象,一方須要實現這個接口,另外一方使用這個接口,但雙方對象並不直接互相依賴,它們只是經過接口間接交互。圖示以下:
拿上面的USB接口來講,USB協議約定了USB設備須要實現的能力,每一個USB設備都須要實現這些能力,電腦使用USB協議與USB設備交互,電腦和USB設備互不依賴,但能夠經過USB接口相互交互。
下面咱們來看Java中的接口。
咱們經過一個例子來講明Java中接口的概念。
這個例子是"比較",不少對象均可以比較,對於求最大值、求最小值、排序的程序而言,它們其實並不關心對象的類型是什麼,只要對象能夠比較就能夠了,或者說,它們關心的是對象有沒有可比較的能力。Java API中提供了Comparable接口,以表示可比較的能力,但它使用了泛型,而咱們尚未介紹泛型,因此本節,咱們本身定義一個Comparable接口,叫MyComparable。
如今,首先,咱們來定義這個接口,代碼以下:
public interface MyComparable {
int compareTo(Object other);
}
複製代碼
解釋一下:
再來解釋一下compareTo方法:
接口與類不一樣,它的方法沒有實現代碼。定義一個接口自己並無作什麼,也沒有太大的用處,它還須要至少兩個參與者,一個須要實現接口,另外一個使用接口,咱們先來實現接口。
類能夠實現接口,表示類的對象具備接口所表示的能力。咱們來看一個例子,之前面介紹過的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+")";
}
}
複製代碼
咱們解釋一下:
咱們再來解釋一下Point的compareTo實現:
一個類能夠實現多個接口,代表類的對象具有多種能力,各個接口之間以逗號分隔,語法以下所示:
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。
更重要的是下降了耦合,提升了靈活性,使用接口的代碼依賴的是接口自己,而非實現接口的具體類型,程序能夠根據狀況替換接口的實現,而不影響接口使用者。解決複雜問題的關鍵是分而治之,分解爲小問題,但小問題之間不可能一點關係沒有,分解的核心就是要下降耦合,提升靈活性,接口爲恰當分解,提供了有力的工具。
上面咱們介紹了接口的基本內容,接口還有一些細節,包括:
咱們逐個來介紹下。
接口中能夠定義變量,語法以下所示:
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關鍵字,用來判斷一個對象是否實現了某接口,例如:
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編程及計算機技術的本質。用心原創,保留全部版權。