1.介面的基本概念
在 Java 中,被關鍵字 interface 修飾的“類”是介面。
介面的定義如下:
interface 介面名稱{
全域性常量;
抽象方法;
}
複製程式碼
2. 介面的使用
介面的使用分兩步:
- 建立介面
- 實現介面
2.1 建立介面
//原始碼
public interface Move {
void move();
}
複製程式碼
2.2 實現介面
//原始碼
public class Animal implements Move {
@Override
public void move() {
System.out.println("Animal move");
}
}
public class Human implements Move{
@Override
public void move() {
System.out.println("Human move");
}
}
public class Car implements Move {
@Override
public void move() {
System.out.println("Car move");
}
}
public class MoveTest {
public static void main(String[] args) {
Move [] move = {new Animal(), new Human(), new Car()};
for (Move m : move) {
m.move();
}
}
}
//執行結果
Animal move
Human move
Car move
複製程式碼
3. 介面存在的意義
介面存在的意義主要有兩點:
- 禁止直接為其例項化物件
- 打破單繼承侷限(實現偽多重繼承)
3.1 禁止直接為其例項化物件
在這個點上,相比於抽象類,Java 對介面的限制更加嚴格了,因為介面連構造方法都沒有,所以,根本不可能為其例項化物件。
//原始碼
public interface Move {
//此時編譯器會提示 Interfaces cannot have constructors 錯誤資訊
public Move(){}
void move();
}
複製程式碼
3.2 打破單繼承侷限(實現偽多重繼承)
由於 Java 中允許多實現,所以,一個類在實現了多個介面之後,就可以上轉型為多個介面,即打破單繼承侷限。
//原始碼
public interface Fly {
void fly();
}
public interface Fight {
void fight();
}
public class SuperMan implements Fly,Fight{
@Override
public void fight() {
System.out.println("SuperMan fight");
}
@Override
public void fly() {
System.out.println("SuperMan fly");
}
}
public class MultiImplementsTest {
public static void main(String[] args) {
SuperMan sm = new SuperMan();
fly(sm);
fight(sm);
}
private static void fly(Fly f){
f.fly();
}
private static void fight(Fight f){
f.fight();
}
}
//執行結果
SuperMan fly
SuperMan fight
複製程式碼
由於 SuperMan 實現了 Fly 和 Fight 介面,所以 SuperMan 可以上轉型為 Fly 介面,也可以上轉型為 Fight 介面,即“多重繼承?”。
4. 介面中易混淆的概念
4.1 介面中有構造方法嗎?
介面中沒有構造方法。詳情請參考《3.1 禁止直接為其例項化物件》。
4.2 介面可以繼承普通類嗎?介面可以繼承抽象類嗎?
介面不可以繼承普通類。
//原始碼
public class Animal {
}
//此時開發工具會提示 The type Animal cannot be a superinterface of Fly; a superinterface must be an interface 錯誤資訊
public interface Fly extends Animal{
void fly();
}
複製程式碼
介面不可以繼承抽象類。
//原始碼
public abstract class Airplane {
}
//此時開發工具會提示 The type Airplane cannot be a superinterface of Fly; a superinterface must be an interface 錯誤資訊
public interface Fly extends Airplane{
void fly();
}
複製程式碼
其實這很好理解,因為介面中只能定義靜態常量和抽象方法,無論普通類還是抽象類都沒有如此嚴格的要求,因此介面既不能繼承普通類也不能繼承抽象類。
4.3 當實現類的父類中的方法和介面中的方法一樣時,會出現什麼情況?
4.3.1 正常情況下的類繼承
在 Java 中,一個類的子類將繼承父類的所有用 public 和 protected 關鍵字修飾的方法和屬性。
//原始碼
public class Animal {
public void eat(){
System.out.println("Animal eat");
}
}
public class Tiger extends Animal{
}
public class TigerTest {
public static void main(String[] args) {
Tiger tiger = new Tiger();
tiger.eat();
}
}
//執行結果
Animal eat
複製程式碼
4.3.2 正常情況下的介面實現
在 Java 中,一個類實現了某個介面,就要實現該介面中所有的方法。
//原始碼
public interface Fly {
void fly();
}
public class Eagle implements Fly {
@Override
public void fly() {
System.out.println("Eagle fly");
}
}
public class EagleTest {
public static void main(String[] args) {
Eagle eagle = new Eagle();
eagle.fly();
}
}
//執行結果
Eagle fly
複製程式碼
4.3.3 實現類的父類中的方法和介面中的方法不一樣
//原始碼
public class Animal {
public void eat(){
System.out.println("Animal eat");
}
}
public interface Fly {
void fly();
}
public class Vulture extends Animal implements Fly {
@Override
public void fly() {
System.out.println("Vulture fly");
}
}
public class VultureTest {
public static void main(String[] args) {
Vulture vulture = new Vulture();
vulture.eat();
vulture.fly();
}
}
//執行結果
Animal eat
Vulture fly
複製程式碼
4.3.4 實現類的父類中的方法和介面中的方法一樣
其實通常情況下這種事是不會發生的,除非某個程式設計師想自找麻煩。但如果是為了搞清介面的概念,那這很值得一試。正如《Thiking in Java》的作者 Bruce Eckel 所說:
I generally find that once you know about a feature, you often discover places where it is useful.
方法名確定之後,就剩下方法的引數列表和返回值型別,接下來,對各種情況進行分析:
序號 | 具體情況 |
---|---|
1 | 引數相同,返回值相同 |
2 | 引數相同,返回值不同 |
3 | 引數不同,返回值相同 |
4 | 引數不同,返回值不同 |
Ps:此處討論的前提是方法名稱一樣。
4.3.4.1 引數相同,返回值相同
//原始碼
public class Animal {
public void hunt(){
System.out.println("Animal hunt");
}
}
public interface Hunt {
void hunt();
}
public class Eagle extends Animal implements Hunt{
}
public class EagleTest {
public static void main(String[] args) {
Eagle eagle = new Eagle();
eagle.hunt();
}
}
//執行結果
Animal hunt
複製程式碼
結論:當實現類的父類中的方法的簽名和返回值跟介面中的方法的簽名和返回值完全一樣時,此時子類可以不同顯式實現介面中的方法。如果此時,實現類沒有顯式實現介面中的,那麼將呼叫父類中的方法。
4.3.4.2 引數相同,返回值不同
//原始碼
public class Animal {
public void hunt(){
System.out.println("Animal hunt");
}
}
public interface Hunt {
String hunt();
}
//此時,如果不實現介面中的方法,開發工具會提示 The return types are incompatible for the inherited methods Hunt.hunt(), Animal.hunt() 錯誤資訊
public class Eagle extends Animal implements Hunt{
//此時開發工具會提示 The return type is incompatible with Animal.hunt() 錯誤資訊
public String hunt(){
return "";
}
}
複製程式碼
結論:當實現類的父類中的方法的簽名跟介面中的方法的簽名一樣,而返回值不一樣時,實現類定義不成功,即根本不存在這樣的(實現)類。
4.3.4.3 引數不同,返回值相同
//原始碼
public class Animal {
public void hunt(){
System.out.println("Animal hunt");
}
}
public interface Hunt {
void hunt(String place);
}
public class Eagle extends Animal implements Hunt{
@Override
public void hunt(String place) {
System.out.println("Eagles hunt on the " + place);
}
}
public class EagleTest {
public static void main(String[] args) {
Eagle eagle = new Eagle();
eagle.hunt();
eagle.hunt("grasslands");
}
}
//執行結果
Animal hunt
Eagles hunt on the grasslands
複製程式碼
結論:當實現類的父類中的方法的引數跟介面中的方法的引數不一樣,而返回值一樣時,需要在實現類中重新實現介面中的方法。
4.3.4.4 引數不同,返回值不同
//原始碼
public class Animal {
public void hunt(){
System.out.println("Animal hunt");
}
}
public interface Hunt {
String hunt(String place);
}
public class Eagle extends Animal implements Hunt{
@Override
public String hunt(String place) {
System.out.println("Eagles hunt on the " + place);
return place;
}
}
public class EagleTest {
public static void main(String[] args) {
Eagle eagle = new Eagle();
eagle.hunt();
System.out.println(eagle.hunt("grasslands"));
}
}
//執行結果
Animal hunt
Eagles hunt on the grasslands
grasslands
複製程式碼
結論:當實現類的父類中的方法的引數和返回值跟介面中的方法的引數和返回值均不一樣時,需要在實現類中重新實現介面中的方法。
之所以上面的概念沒有搞清是因為對類的定義理解的不夠透徹:
class 類名稱 extends 父類名稱 implements 介面名稱{
屬性;
方法;
}
複製程式碼
從上面的定義中可以知道:子類是先繼承父類後實現介面。《4.3.4.1 引數相同,返回值相同》的程式碼也證實了這一點,因為如果是先實現後繼承,那麼在《4.3.4.1 引數相同,返回值相同》的 Eagle 類中就需要實現介面 Hunt 的方法,而此處未實現,開發工具也沒有報錯,那說明,在實現之前已經定義好了,而此時在 Eagle 類中實際上是未顯式定義介面 Hunt 中的方法的,因此可以確定推論子類是先繼承父類後實現介面是正確的。
明白了子類是先繼承父類後實現介面之後,上面的結論也就很好理解了,比如《4.3.4.2 引數相同,返回值不同》的結論:
當實現類的父類中的方法的簽名跟介面中的方法的簽名一樣,而返回值不一樣時,實現類定義不成功,即根本不存在這樣的(實現)類。
因為實現類是先繼承父類後實現介面的,所以,當實現類繼承了父類之後,相當於已經定義了一個方法簽名與介面中方法簽名一樣的方法。此時二者唯一的不同就是返回值不一樣,而返回值不一樣並不能區分兩個方法,即不滿足方法過載的條件(方法名相同,引數型別或個數不同),故不能定義那樣的類。同樣的道理,這也是為什麼《4.3.4.3 引數不同,返回值相同》和《4.3.4.4 引數不同,返回值不同》需要在 Eagle 類中重新定義(實現) hunt 方法的原因——Eagle 類並未實現 Hunt 介面中的方法,它所擁有的只不過是 Animal 類中一個與 Hunt 介面中的方法互為過載關係的方法而已。
4.4 普通類中可以定義介面嗎?
可以。
//原始碼
public class Animal {
interface Climb{
}
}
複製程式碼
4.5 介面中可以定義介面嗎?
可以。
//原始碼
public interface Hunt {
interface Kill{
}
}
複製程式碼
4.6 如何使用普通類中定義的 private 介面?
//原始碼
public class Animal {
private Climb mClimb;
public interface Hunt{
void hunt();
}
private interface Climb{
void climb();
}
public class ClimbImpl implements Climb{
@Override
public void climb() {
System.out.println("ClimbImpl climb");
}
}
public Climb getClimb(){
return new ClimbImpl();
}
public void setClimb(Climb climb){
this.mClimb = climb;
mClimb.climb();
}
}
public class Eagle implements Animal.Hunt {
@Override
public void hunt() {
System.out.println("Eagle hunt");
}
}
//此時開發工具會提示 The type Animal.Climb is not visible 錯誤
public class Tortoise implements Animal.Climb {
}
public class AnimalTest {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.toString());
Animal.Hunt hunt = new Eagle();
hunt.hunt();
//Climb cannot be resolved to a type
// Climb climb = animal.getClimb();
animal.setClimb(animal.getClimb());
}
}
//執行結果
com.smart.www.define_interface_in_class_one.Animal@7852e922
Eagle hunt
ClimbImpl climb
複製程式碼
最後的 animal.setClimb(animal.getClimb())
方法雖然有點突兀,但這裡主要想表達的意思是:確實可以在外面用類內部定義的 private 介面。
5. 介面實際應用
介面的使用場景非常多,我們不可能把所有的情況都分析一遍的。接下來,我們就從設計模式的維度來分析如何在實際的開發中使用介面。
工廠方法模式是一種常用的建立型模式,它能很好地將物件的建立和物件的使用分離,並且新增新型別的產品物件不會影響原有的程式碼。接下來,我們分析下如何在工廠方法設計模式中應用介面。
工廠方法模式的定義如下:
在工廠父類中定義一個建立產品物件的介面,讓子類負責生產具體的產品物件。(Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclassed.)
工廠方法模式的結構圖如下:
由上面的結構圖可知,工廠方法模式一共包含四部分:
- Product(抽象產品類)
抽象產品類是定義產品的介面,是工廠方法模式建立的產品物件的“父類”。
- ConcreteProduct(具體產品類)
具體產品類是抽象產品類的實現類,它實現了抽象產品類,與具體工廠類一一對應。
- Factory(抽象工廠類)
抽象工廠類是定義建立產品類的介面。
- ConcreteFactory(具體工廠類)
具體工廠類是抽象工廠類的實現類,它實現了抽象工廠類,用於生產具體的產品,與具體產品類一一對應。
接下來,我們就根據工廠方法模式建立一個能生產手機的工廠,結構圖如下:
以下是具體程式碼實現:
//原始碼
public interface Phone {
void call();
}
public class IPhone implements Phone {
@Override
public void call() {
System.out.println("IPhone call");
}
}
public class XiaoMi implements Phone {
@Override
public void call() {
System.out.println("XiaoMi call");
}
}
public class MeiZu implements Phone {
@Override
public void call() {
System.out.println("MeiZu call");
}
}
public interface PhoneFactory {
Phone producePhone();
}
public class IPhoneFactory implements PhoneFactory {
@Override
public Phone producePhone() {
return new IPhone();
}
}
public class XiaoMiFactory implements PhoneFactory {
@Override
public Phone producePhone() {
return new XiaoMi();
}
}
public class MeiZuFactory implements PhoneFactory {
@Override
public Phone producePhone() {
return new MeiZu();
}
}
public class PhoneFactoryTest {
public static void main(String[] args) {
PhoneFactory phoneFactory = new XiaoMiFactory();
Phone phone = phoneFactory.producePhone();
phone.call();
}
}
//執行結果
XiaoMi call
複製程式碼
想要新增新的產品類也十分方便,只用建立新的產品類和新的工廠即可,對原有程式碼沒有任何影響,如為該工廠新增建立華為手機的方法:
//原始碼
public class HuaWei implements Phone {
@Override
public void call() {
System.out.println("HuaWei call");
}
}
public class HuaWeiFactory implements PhoneFactory {
@Override
public Phone producePhone() {
return new HuaWei();
}
}
public class PhoneFactoryTest {
public static void main(String[] args) {
PhoneFactory phoneFactory = new HuaWeiFactory();
Phone phone = phoneFactory.producePhone();
phone.call();
}
}
//執行結果
HuaWei call
複製程式碼
參考文件
- 《Java 開發實戰經典》
- 《Thinking in Java》
- 《設計模式》
- Java Tutorials
- Design Principles