1. 程式人生 > >java8增加的介面中預設方法

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());

    }
}

從上述測試結果可以看出:

  1. 實現類可以直接使用父介面中定義的default方法。

  2. 介面可以重寫父介面中定義的default方法。

  3. 實現類可以重寫父介面中定義的方法、

  4. 當父類和父介面都存在default方法時,使用父類中重寫的default方法

特別的,如果一個類實現了兩個介面,這兩個介面中有同名的default方法簽名時,此時會編譯不通過,必須在子類中重寫這個default方法。