前面介紹了介面的基本用法,有心的朋友可能註意到這麼一句話“在Java8以前,介面內部的所有方法都必須是抽象方法”,如此說來,在Java8之後,介面的內部方法也可能不是抽象方法了嗎?之所以Java8對介面的定義規則發生變化,是因為原來的介面定義存在先天不足導致的,例如下列幾點需求就難以滿足:1、Jav ...
前面介紹了介面的基本用法,有心的朋友可能註意到這麼一句話“在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開發筆記(序)章節目錄》