Skip to content

Instantly share code, notes, and snippets.

@asufana
Last active December 14, 2015 03:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asufana/07ba1360d69ad8304955 to your computer and use it in GitHub Desktop.
Save asufana/07ba1360d69ad8304955 to your computer and use it in GitHub Desktop.
Java8 デフォルトメソッド

Java8 デフォルトメソッド

インターフェースに実装を持たせることができる。

インターフェース、、、ということは、継承と違って、多重継承が可能!

プリミティブな振る舞いをインターフェース化しておけば、好きなものを合成して簡単に機能性を拡張できる!

通知クラスを実装してみる

  • 社員番号とメッセージを渡すと、当該社員にメールで通知してくれるというクラスを作りたい
  • 社員番号から社員を取得する処理は、UserRepositoryインターフェースにお任せする
  • 社員にメールでメッセージを送付する処理は、SendMailインターフェースにお任せする
  • ユーザ検索機能とメール送信機能を合成する
@Test
//テスト
public void test() throws Exception {
    final Integer 社員番号 = 123;
    final String メッセージ = "こんにちは世界";
    new SendMessage().send(社員番号, メッセージ);
    // ⇒ アドレス hoge@hoge.com に、メッセージ こんにちは世界 をメール送付しました
}

/** 指定社員番号宛にメールでメッセージを送るクラス */
public class SendMessage implements UserRepository, SendMail {
    public void send(final Integer empId, final String message) {
        final String mailAddress = getMailAddress(empId); //デフォルトメソッドのユーザ検索機能を利用
        send(mailAddress, message); //デフォルトメソッドのメール送信機能を利用
    }
}

/** ユーザ情報検索インターフェース */
public interface UserRepository {
    public default String getMailAddress(final Integer empId) {
        //TODO ほんとはちゃんと実装すること
        return "hoge@hoge.com";
    }
    
    default String getName(final Integer empId) {
        //TODO ほんとはちゃんと実装すること
        return "ほげたろう";
    }
}

/** メール送信インターフェース */
public interface SendMail {
    default void send(final String mailAddress, final String message) {
        //TODO ほんとはちゃんと実装すること
        System.out.println(String.format("アドレス %s に、メッセージ %s を送付しました",
                                         mailAddress,
                                         message));
    }
}

/** SMS送信インターフェース */
// SendMessage implements UserRepository, SendSMS とすれば、メール送信をSMS送信に切り替えられる
public interface SendSMS {
    default void send(final String telNo, final String message) {
        //TODO ほんとはちゃんと実装すること
        System.out.println(String.format("電話番号 %s に、メッセージ %s を送付しました",
                                         telNo,
                                         message));
    }
}

みんなだいすきPredicate

StreamAPI#filterの引数型 Predicate は、and / or を使って合成することで論理演算が可能。

public static class UserCollection {
    final Predicate<User> 正社員 = user -> user.userType.equals(UserType.正社員);
    final Predicate<User> 役員 = user -> user.title.equals(Title.役員);
    final Predicate<User> 部長 = user -> user.title.equals(Title.部長);

    private final List<User> list;

    public UserCollection エグゼクティブなひとたち() {
        return filterBy(正社員.and(役員)); // <= Predicateの合成
    }

    private UserCollection filterBy(Predicate predicate){
        return new UserCollection(
        	list.stream()
                .filter(predicate)
                .collect(Collectors.toList());
    }

and / or は、Predicate のデフォルトメソッドとして実装されている。

@FunctionalInterface
public interface Predicate<T> {
	//実装すべき一つだけのメソッド
    boolean test(T t);

    //デフォルトメソッド
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

多重承継によるシグネチャ衝突の問題

シグネチャ衝突するとエラーになる。たとえば、SendMail#send と SendSMS#send を多重継承した場合など。

    /** 指定社員番号宛にメールでメッセージを送る */
    public class SendMessage implements UserRepository, SendMail, SendSMS {
        public void send(final Integer empId, final String message) {
            final String mailAddress = getMailAddress(empId);
            send(mailAddress, message);
        }
    }

Duplicate default methods named send with the parameters (String, String) and (String, String) are inherited from the types TestDefaultMethod.SendSMS and TestDefaultMethod.SendMail

その場合、sendメソッドをオーバーライド実装するか、どちらかのメソッドをsuperで指定してあげればOK

    /** 指定社員番号宛にメールでメッセージを送る */
    public class SendMessage implements UserRepository, SendMail, SendSMS {
        @Override
        public void send(final String mailAddress, final String message) {
            //どの実装を使うのか明示する
            SendSMS.super.send(mailAddress, message);
        }
        
        public void send(final Integer empId, final String message) {
            final String mailAddress = getMailAddress(empId);
            send(mailAddress, message);
        }
    }

菱型継承による継承の曖昧さの問題

インターフェースのデフォルトメソッドにより多重継承実装が実現可能となり、菱型継承問題が顕在化するようになる。

でもとりあえず対応されているよ。ややこしいから詳細は下記参照のこと。

Rubyのmixin、Scalaのtrait

Javaはファーストクラスオブジェクトがクラスのため、なにかと制約が多い。

RubyのmixinやScalaのtraitのように、関数がファーストクラスオブジェクトな言語は「振る舞い」だけを抽出・合成して多重継承を実現している。

Java8もインターフェースのデフォルト実装という荒業で「振る舞い」だけを抜き出して、ファーストクラスオブジェクトがクラスな制約を解決した。

また一歩モダンな言語に近づいた!

#ちなみにScalaはtraitにインスタンス変数を定義できる(すげー)

抽象クラスとの違い

インターフェース

  • 多重継承できるが、状態を持てない

抽象クラス

  • 多重継承できないが、状態を持てる

使い道

様々なクラスで汎用的に呼び出される振る舞いの実装に利用する、ログとか基底処理など。もしくはライブラリの基底クラスの実装など。

ただしScalaやPHPのTraitと違ってインスタンス変数が保持できない。いままで継承で実装していたものを単純に置き換えすることはできない。できることは振る舞いのみ。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment