Java開發筆記(五十九)Java8之後的擴展接口
前面介紹了接口的基本用法,有心的朋友可能註意到這麽一句話“在Java8以前,接口內部的所有方法都必須是抽象方法”,如此說來,在Java8之後,接口的內部方法也可能不是抽象方法了嗎?之所以Java8對接口的定義規則發生變化,是因為原來的接口定義存在先天不足導致的,例如下列幾點需求就難以滿足:
1、Java8以前規定接口的內部方法只能是抽象方法,在該接口的實現類裏面全部都要重寫。這個規定明顯太霸道了,為什麽非得所有都重寫呢?有的行為分明是通用的,比如呼吸動作,凡是陸上動物都用鼻子呼吸,把新鮮空氣吸進去,再把循環後的空氣呼出來,這個呼吸方法理應放之四海而皆準,根本無需在每個實現類中依次重寫過去。
2、Java8以前的接口不支持構造方法也就算了,可是它居然也不支持靜態成員(包括靜態屬性和靜態方法)!這下可苦了程序員,因為與行為有關的常量與工具方法不能放到接口內部,只能另外寫個工具類填入這些常量與工具方法,於是原本應當在一個屋檐之下的行為動作和行為概念不得不分居兩地了。
有鑒於此,從Java8開始,接口順應時代要求進行了規則修訂,針對以上的兩點需求分別補充了相應的處理對策:
1、增加了默認方法,並通過前綴default來標識。接口內部需要編寫默認方法的完整實現代碼,這樣實現類無需重寫該方法即可直接繼承並使用,仿佛默認方法就是父類方法一樣,唯一的區別在於實現類不允許重寫默認方法。
2、增加了靜態屬性和靜態方法,而且都通過前綴static來標識。接口的靜態屬性同時也是終態屬性,初始化賦值之後便無法再次修改;接口的靜態方法不能被實現類繼承,因而實現類允許定義同名的靜態方法,緣於接口的靜態方法與實現類的靜態方法沒有任何關聯,僅僅是它倆恰好同名而已。
據此對先前的行為接口Behavior進行增強,按照Java8的新特性補充了默認方法與靜態方法,修補之後的新接口ExpandBehavior代碼如下所示:
//定義一個增加了Java8新特性的接口 public interface ExpandBehavior { // 聲明了一個抽象的飛翔方法 public void fly(); // 聲明了一個抽象的遊泳方法 public void swim(); // 聲明了一個抽象的奔跑方法 public void run(); // 默認方法,以前綴default標識。默認方法不支持重寫,但可以被繼承。 public default String getOrigin(String place, String name, String ancestor) { return String.format("%s%s的祖先是%s。", place, name, ancestor); } public static int MALE = 0; public static int FEMALE = 1; // 接口內部的靜態屬性也默認為終態屬性,所以final前綴可加可不加 //public final static int MALE = 0; //public final static int FEMALE = 1; // 靜態方法,以關鍵字static標識。靜態方法支持重寫,但不能被繼承。 public static String getNameByLeg(int leg_count) { if (leg_count == 2) { return "二足動物"; } else if (leg_count == 4) { return "四足動物"; } else if (leg_count >= 6) { return "多足動物"; } else { return "奇異動物"; } } }
根據上面的擴展接口,重新編寫實現了該接口的鵝類,其中fly、swim、run這三個抽象方法均須重寫,唯有默認方法getOrigin不要重寫,並且鵝類代碼當中可以直接調用這個默認方法。新寫的鵝類代碼ExpandGoose示例如下:
//定義實現了擴展接口的鵝類 public class ExpandGoose extends Bird implements ExpandBehavior { public ExpandGoose(String name, int sexType) { super(name, sexType); } // 實現了接口的fly方法 @Override public void fly() { System.out.println("鵝飛不高,也飛不遠。"); } // 實現了接口的swim方法 @Override public void swim() { System.out.println("鵝,鵝,鵝,曲項向天歌。白毛浮綠水,紅掌撥清波。"); } // 實現了接口的run方法 @Override public void run() { System.out.println("檻外蕭聲輕蕩漾,沙間鵝步滿蹣跚。"); } // 根據產地和祖先拼接並打印該動物的描述文字 public void show(String place, String ancestor) { // getOrigin是來自擴展接口ExpandBehavior的默認方法,可以在實現類中直接使用 String desc = getOrigin(place, getName(), ancestor); System.out.println(desc); } }
接著輪到外部訪問這個鵝類ExpandGoose了,表面上外部仍跟平常一樣調用鵝類的成員方法,然而在調用接口的靜態成員時有所差別。對於接口的靜態屬性,外部依然能夠通過鵝類直接訪問,訪問格式形如“實現類的名稱.靜態屬性名”;對於接口的靜態方法,外部卻不能通過鵝類訪問了,因為實現類並未繼承接口的靜態方法,所以外部只能通過接口自身訪問它的靜態方法,訪問格式形如“擴展接口的名稱.靜態方法名(***)”。下面是外部調用鵝類ExpandGoose的代碼例子:
// 演示擴展接口的實現類用法 private static void testExpand() { // 實現類可以繼承接口的靜態屬性 ExpandGoose goose = new ExpandGoose("鵝", ExpandGoose.FEMALE); goose.show("中國", "鴻雁"); goose.show("歐洲", "灰雁"); // 接口中的靜態方法沒有被實現類所繼承,因而只能通過擴展接口自身訪問 String typeName = ExpandBehavior.getNameByLeg(2); System.out.println("鵝是"+typeName); }
運行上面的測試代碼,觀察到如下的日誌結果,可見不管是默認方法getOrigin,還是靜態方法getNameByLeg,都得到了正確執行:
中國鵝的祖先是鴻雁。 歐洲鵝的祖先是灰雁。 鵝是二足動物
更多Java技術文章參見《Java開發筆記(序)章節目錄》
Java開發筆記(五十九)Java8之後的擴展接口