java8增加的介面中預設方法
前言
最近在工作中的一次小修改讓自己應用到了java8中的新特性:介面預設方法,這裡去簡單記錄下。在java8之後可以在介面定義方法的實現,成為default方法,類似於Scala中的trait。比如在Iterable介面中新增了foreach預設方法:
/** * Performs the given action for each element of the {@code Iterable} * until all elements have been processed or the action throws an * exception. Unless otherwise specified by the implementing class, * actions are performed in the order of iteration (if an iteration order * is specified). Exceptions thrown by the action are relayed to the * caller. * * @implSpec * <p>The default implementation behaves as if: * <pre>{@code * for (T t : this) * action.accept(t); * }</pre> * * @param action The action to be performed for each element * @throws NullPointerException if the specified action is null * @since 1.8 */ default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
這個default方法的主要目的是為java8的Lambda表示式提供支援,如果將這個方法定義為普通介面方法,則會對現有的JDK的其他使用Iterable介面的類造成影響,因此提供了default方法的功能。
工作中的例子
有一個小需求是對一個表的插入的實體的name欄位做一個處理,這裡name欄位如果為空則用手機號(預設手機號不為空)進行插入。因為呼叫這個insert方法的業務程式碼比較多,每個都去做這個邏輯會顯得很麻煩並且很重複。所以就想到了直接在mapper的xml中去進行修改:
insert into table_name ( <if test="name ==null or name == ''">NAME,</if> ) values( <if test="name == null or name == ''">#{mobile}</if> )
這樣確實能夠實現我們這個小需求,但是這個insert的mapper.xml是通過程式碼工具自動生成的標準insert方法,並且這樣寫可讀性也不好,給人一種很奇怪的感覺。
這時候就用到了預設方法。這裡我們可以在介面中定義一個預設方法insert,然後將之前的insert方法更換名稱,在預設方法中去呼叫更換之後插入方法,而在預設方法中去做如果name為空則用手機號去代替這個邏輯。
int insertDefault(Clues entity); /** * * @param entity * @return */ default int insert(Clues entity) { if (StringUtils.isEmpty(entity.getName()) && StringUtils.isNotEmpty(entity.getMobile())) { entity.setName(entity.getMobile()); } return insertDefault(entity); }
這樣原來呼叫的insert方法也不需要去做更改,並且也不用在xml中進行改動就實現了這個小邏輯。注意要將xml中方insert方法改為insertDefault,這個更改比上邊那種修改要顯得合理的多。
預設方法的一個總結
java是面向物件的語言,那麼就會有實現介面和繼承父類,那麼這些會對介面的預設方法有什麼影響呢?下邊參考部落格:預設方法
存在一個父介面,定義了一個default方法:
public interface Parent {
default String doit() {
return "Parent";
}
}
有一個類實現該介面,使用了預設的default方法:
public class ParentImpl implements Parent{
}
有一個ParentImpl2繼承了ParentImpl,裡面重寫了介面中的預設方法:
public class ParentImpl2 extends ParentImpl {
@Override
public String doit() {
return "ParentImpl2";
}
}
有一個Child介面繼承了Parent介面,並且重寫了Parent介面中的預設方法:
public interface Child extends Parent {
/**
* 重寫父介面中的default方法
* @return
*/
@Override
default String doit() {
return "Child";
}
}
有一個ChildImpl實現了Child:
public class ChildImpl implements Child {
}
又有一個ChildImpl2繼承了ParentImpl2也實現了Child介面,為了測試當子類實現的介面和繼承的父類中都有預設方法的場景:
public class ChildImpl2 extends ParentImpl2 implements Child {
}
測試類:
public class Main {
public static void main(String[] args) {
/**
* 測試實現類可以直接呼叫介面中的default方法
*/
Parent parentImpl = new ParentImpl();
// 將輸出Parent
System.out.println(parentImpl.doit());
/**
* 測試Child介面重寫了Parent介面的default方法
*/
Child child = new ChildImpl();
// 將輸出Child
System.out.println(child.doit());
/**
* 測試ParentImpl2重寫了Parent介面中default方法
*/
Parent parentImpl2 = new ParentImpl2();
// 將輸出ParentImpl2
System.out.println(parentImpl2.doit());
/**
* 測試ChildImpl2父類和實現的介面都有default方法,優先使用父類中定義的方法
*/
Child childImpl2 = new ChildImpl2();
// 將輸出ParentImpl2
System.out.println(childImpl2.doit());
}
}
從上述測試結果可以看出:
-
實現類可以直接使用父介面中定義的default方法。
-
介面可以重寫父介面中定義的default方法。
-
實現類可以重寫父介面中定義的方法、
-
當父類和父介面都存在default方法時,使用父類中重寫的default方法
特別的,如果一個類實現了兩個介面,這兩個介面中有同名的default方法簽名時,此時會編譯不通過,必須在子類中重寫這個default方法。