接口的特性
接口不是類,尤其不能使用new運算符實例化一個接口:
x = new Comparable(...); // 這樣是不可以的
然而,儘管不能構造接口的對象,卻能聲明接口的變量:
Comparable x; // 這樣是可以的
接口變量必須引用實現了接口的類對象:
x = new Employee(...);
接下來,如同使用instanceof檢查一個對象是否屬於某個特定類一樣,也可以使用instance檢查一個對象是否實現了某個特定的接口:
if (anObject instanceof Comparable) ...
與可以建立類的繼承關係一樣,接口也可以被擴展。這裏允許存在多條從具有較高通用性的接口到較高專用型的接口的鏈。如:假設有一個Moveable的接口:
public interface Moveable {
void move(double x, double y);
}
然後,可以以它爲基礎擴展一個叫做Powered的接口:
public interface Powered extends Moveable {
double milesPerGallon();
}
雖然在接口中不能包含實例域或靜態方法,但卻可以包含常量:
public interface Powered extends Moveable {
double milesPerGallon();
double SPEED_LIMIT = 95;
}
與接口中的方法都自動地設置爲public一樣,接口中的域將被自動設爲public static final。
有些接口只定義了常量,而沒有定義方法。例如,在標準庫中有一個SwingConstants就是這樣一個接口,其中只包含NORTH、SOUTH和HORIZONTAL等常量。任何實現SwingConstants接口的類都自動地繼承了這些常量,並可以在方法中直接地引用NORTH,而不必採用SwingConstants.NORTH這樣的繁瑣書寫形式。然而,這樣應用接口似乎有點偏離了接口概念的初衷,最好不要這樣使用。
儘管每個類只能夠擁有一個超類,但卻可以實現多個接口。這就爲定義類的行爲提供了極大的靈活性。例如,Java程序設計語言有一個非常重要的內置接口,稱爲Cloneable。如果某個類實現了這個Cloneable接口,Object類中的clone方法就可以創建類對象的一個拷貝。如果自己設計的類擁有克隆和比較的能力,只要實現這兩個接口就可以了。使用逗號將實現的各個接口分隔開。
class Employee implements Cloneable, Comparable
接口與抽象類
爲什麼Java程序設計語言還要不辭辛苦地引入接口概念?爲什麼不降Comparable直接設計成如下所示的抽象類。
abstract class Comparable {
public abstract int compareTo(Object other);
}
然後,Employee類再直接擴展這個抽象類,並提供compareTo方法的實現:
class Employee extends Comparable {
public int compareTo(Object other){...};
}
非常遺憾,使用抽象類表示通用屬性存在這樣一個問題:每個類只能擴展一個類。假設Employee類已經擴展了一個類,例如Person,它就不能想下面這樣擴展第二個類了。
class Employee extends Person, Comparable // 這樣是不可以的
但每個類可以像下面這樣實現多個接口:
class Employee extends Person implements Comparable // 這樣是可以的
有些程序設計語言允許一個類有多個超類。如C++,我們將此特性稱爲多重繼承(multiple inheritance)。而Java的設計者選擇了不支持多繼承,其主要原因是多繼承會讓語言本身變得非常複雜,效率也會降低。
實際上,接口可以提供多重繼承的大多數好處,同時還能避免多重繼承的複雜性和低效性。
靜態方法
在Java SE 8中,允許在接口中增加靜態方法。理論上講,沒有任何理由認爲這是不合法的。只是這有違於將接口作爲抽象規範的初衷。
目前爲止,通常的做法都是將靜態方法放在伴隨類中。在標準庫中,你會看到成對出現的接口和實用工具類,如Collection/Collections或Paht/Paths。
下面來看Paths類,其中包含兩個工廠方法。可以由一個字符串序列構造一個文件或目錄的路徑,如:Paths.get(“jdk1.8.1”, “jre”, “bin”)。在Java SE 8 中,可以爲Path接口增加以下方法:
public interface Path {
public static Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
}
這樣一來,Paths類就不再是必要的了。
不過這個Java庫都以這種方式重構是不太可能的,但是實現你自己的接口時,不再需要爲實用工具方法另外提供一個伴隨類。
默認方法
可以爲接口方法提供一個默認實現,必須用default修飾符標記這樣一個方法。
public interface Comparable<T> {
default int comparaTo(T other) { return 0; }
}
當然,這並沒有太大用處,因爲Comparable的每一個實際實現都要覆蓋這個方法。不過有些情況下,默認方法可能很有用。
默認方法可以調用任何其他方法。如:Collection接口可以定義一個便利方法:
public interface Collection {
int size(); // 一個抽象方法
default boolean isEmpty() {
return size() == 0;
}
}
這樣實現Collection的程序員就不用操心實現isEmpty方法了。
默認方法的一個重要用法是“接口演化”(interfae evolution)。以Collection接口爲例,這個接口作爲Java的一部分已經有很多年了。假設很久以前你提供了這樣一個類:
public class Bag implements Collection
後來,在Java SE 8中,又爲這個接口增加了一個stream方法。
假設stream方法不是一個默認方法。那麼Bag類將不能編譯,因爲它沒有實現這個新方法。爲接口增加一個非默認方法不能保證“源代碼兼容(source compatible)”
不過,假設不能編譯這個類,而只是使用原先一個包含這個類的JAR文件。這個類仍能正常加載,儘管沒有這個新方法。程序仍然可以正常構造Bag實例,不會發生意外。不過,如果程序在一個Bag實例上調用stream方法,就會出現一個AbstractMethodError。
將方法實現爲一個默認方法就可以解決這兩個問題。Bag類又能正常編譯了。另外如果沒有重新編譯而直接加載這個類,並在一個Bag實例上調用stream方法,將調用Collection.stream方法。
捐贈
若你感覺讀到這篇文章對你有啓發,能引起你的思考。請不要吝嗇你的錢包,你的任何打賞或者捐贈都是對我莫大的鼓勵。