什么是接口
接口是對類的一種抽象描述,準確的說是對類的行為(方法)的抽象描述。所有實現了某個接口的類都應該實現這個接口所描述行為。通過接口,我們可以了解實現了接口的類能夠對外界提供哪些方法和行為。
如何使用接口
定義接口
接口通過interface
關鍵字來定義,例如,現在需要定義一個接口來描述游泳這個行為,假設游泳有擺動法游泳,滑動法游泳,噴射法游泳三種方式。那么我們可以定義如下ISwim
接口如下。
public interface ISwim {
String SWIM_BY_SWING = "swim by swing";
String SWIM_BY_SLIDE = "swim by slide";
String SWIM_BY_JET = "swim by jet";
void swim();
}
通過ISwim
接口,我們可以看到,在接口中只定義了方法(描述),但是并沒有提供方法的具體實現。同時,方法沒有訪問修飾符(public,private,protected)來限定。此外,我們還在接口中定義了變量,如SWIM_BY_SWING,SWIM_BY_SLIDE,SWIM_BY_JET
。在接口中定義的方法和變量有如下特點。
接口中的變量會被隱式地指定為public static final類型(且只能是public static final類型,用其他類型修飾編譯器會報錯)
接口中的方法會被隱式地指定為public abstract類型(在Java8以前,方法只能是public abstract類型,在Java8以后,允許在接口中定默認方法和static方法)
基于上面兩個特點,我們可以得出下面的ISwim
接口和上面的接口是完全相同的。
public interface ISwim {
public static final String SWIM_BY_SWING = "swim by swing"; //擺動法游泳
public static final String SWIM_BY_SLIDE = "swim by slide"; //滑動法游泳
public static final SWIM_BY_JET = "swim by jet"; //噴射法游泳
public abstract void swim();
}
實現接口
由于接口只提供了方法的抽象描述,沒有提供方法的具體定義,所以單純的接口沒有意義的,因為我們不能像創建對象一樣直接new 一個接口對象。必須有實體類來實現接口才有意義,而實現接口的本質就是把接口中抽象的方法描述變成具體的方法實現。實現一個接口時要使用implements
關鍵字。一個接口可以被多個類實現,實現了接口的類具有接口所描述的特征。下面定義了三個類分別實現ISwim
接口。
public class Fish implements ISwim{
@Override
public void swim() {
System.out.println(ISwim.SWIM_BY_SWING);
}
}
public class Frog implements ISwim{
@Override
public void swim() {
System.out.println(ISwim.SWIM_BY_SLIDE);
}
}
public class Octopus implements ISwim{
@Override
public void swim() {
System.out.println(ISwim.SWIM_BY_JET);
}
}
實現接口和繼承基類有點類似,但是實現接口時,接口中定義的方法必須聲明為public的,例如上面三個類中的swim
方法。這是因為接口中的方法默認是public的。如果用其他訪問修飾符來改變方法的可見性,編譯器會報錯。
一個接口可以被多個類實現,同時一個類可以實現多個接口,實現多個接口時,所有接口都放到implements
關鍵字后面,接口之間通過逗號來分割,語法如下。
class A implements Interface1,Interface2,Interface3...{
}
例如,定義一個接口,描述在陸地行走這個行為。
public interface IWalk {
void walk();
}
因為青蛙(Frog)是兩棲動物,既可以在水中游泳,也可以在陸地上行走,所以青蛙同時具有游泳和行走兩種行為,那么我們可以讓Frog
類同時實現ISwim
和IWalk
接口。
public class Frog implements ISwim, IWalk {
@Override
public void swim() {
System.out.println(ISwim.SWIM_BY_SLIDE);
}
@Override
public void walk() {
System.out.println("walk by jump");
}
}
一個類可以實現多個接口,這是一個很有用的特性,它是Java中實現多繼承的一種重要方式。因為在Java中,一個類只能有一個基類,只能單繼承,而接口的存在,讓Java擁有了多重繼承的能力。
在上面的例子中,除了實現多個接口,也可以通過繼承的方式將多個接口組合成一個接口來擴展接口的能力。例如,可以定義一個接口來描述兩棲動物。
public interface IAmphibians extends ISwim{
void walk();
}
這樣IAmphibians
接口同時描述了swim
和walk
兩種能力,接下來讓Frog
類實現IAmphibians
接口。
public class Frog implements IAmphibians {
@Override
public void swim() {
System.out.println(ISwim.SWIM_BY_SLIDE);
}
@Override
public void walk() {
System.out.println("walk by jump");
}
}
使用接口
如果一個類實現了一個接口,那么這個類可以向上轉型為接口類型,就像可以轉型為基類一樣。雖然我們沒有辦法直接new一個接口類型的對象,但是我們卻可以把實現了接口的類向上轉型為接口的類型。基于這個特性,在代碼中可以使用接口類型來代替具體的類,這樣做可以提供更高的抽象性,將定義和實現分離,保證代碼的擴展性。例如,我們現在需要一個方法來顯示一個動物是如何游泳的,定義方法如下。
public class TestSwim {
public static void testSwim(ISwim swim) {
swim.swim();
}
public static void main(String[] args) {
testSwim(new Fish());
testSwim(new Frog());
testSwim(new Octopus());
}
}
通過將接口類型ISwim
作為testSwim
方法的參數,我們避免了testSwim
方法和具體的類型綁定。這樣可以把方法和具體的類實現解耦,后面任何實現了ISwim
接口的類都可以使用這個方法。便于代碼的復用和擴展。
在接口中定義靜態方法
在Java8之前,接口中只能存在方法的描述,不能定義任何方法的實體,并且方法默認是public abstract類型。所以在Java8之前,如果有一些通用的方法,很有可能會放在工具類里邊。但是Java8以后,允許在接口中定義靜態方法。這樣,可以將一些靜態的工具方法直接定義在接口中,而不再需要一個單獨的工具類。例如,可以把TestSwim
類中的靜態方法testSwim
移動到接口ISwim
中。
public interface ISwim {
String SWIM_BY_SWING = "swim by swing";
String SWIM_BY_SLIDE = "swim by slide";
String SWIM_BY_JET = "swim by jet";
void swim();
static void testSwim(ISwim swim) {
swim.swim();
}
}
這樣,就可以直接在其他類中通過ISwim.testSwim
的方式來調用testSwim
方法。需要注意的是,在Java8之前,在接口中定義靜態方法編譯器是會直接報錯的。
在接口中定義默認方法
在Java8后,除了可以在接口中定義靜態方法外,還可以在接口中定義默認方法。默認方法通過default
關鍵字來標識,默認方法不僅提供了方法描述,還提供了方法的具體實現。例如。
public interface Collection {
int size();
default boolean isEmpty() {
return size()==0;
}
}
在Collection
接口中,為isEmpty
方法提供了一個默認實現。這樣在實現Collection
接口的時候,我們可以只關注size
方法的實現,而不需要關心isEmpty
方法。
public class CollectionImpl implements Collection{
@Override
public int size() {
return 1;
}
}
當然,在CollectionImpl
中也可以重寫isEmpty
方法。同時,通過Collection
接口可以看到在默認方法中,還可以調用接口中的其他方法。
那為什么要在接口中增加這個能力呢?在接口中定義默認方法主要有兩個用途:
- 當接口中有多個方法時,默認方法可以讓實現接口的類只關注他們要實現的方法,而對其他方法則不必關心。
- 當接口后續拓展時,通過把新增的方法定義為默認方法,可以避免修改之前已經實現了該接口的類,保證向前兼容。
第一點很好理解,關于第二點,以Collection
接口為例,假設最開始Collection
接口定義如下。
public interface Collection {
int size();
}
然后,有一個類CollectionImpl
實現了Collection
接口。
public class CollectionImpl implements Collection{
@Override
public int size() {
return 1;
}
}
現在,如果想在Collection
接口中增加一個isEmpty
方法,那么我們就需要修改CollectionImpl
類,讓CollectionImpl
類實現isEmpty
方法,如果代碼中有很多類都實現了Collection
接口,我們就需要修改很多地方,這違反了面向對象設計的開閉原則。但是,如果isEmpty
方法是默認方法,那么Collection
接口的修改對先前已經實現了Collection
接口的類就是無感的。
以上,就是接口的一些基本特性和使用方法。