今日のjavaおっかけ(20150428):デフォルトメソッド

java8対応のEffective Java がほしくなる.

目次とぶっつぶつテーマ

http://snoopopo.hatenablog.com/entry/2015/04/14/102732

今日のテーマ:デフォルトメソッド

 

default void forEach(Consumer<? super T> action)

前回のStream API をやったときに出てきた、java.lang.Iterable インタフェース(これ↑)の default ってなに?ということでやっていきます.

インタフェースで実装が書けるようになった

らしい. それがこのdefault とついたメソッドだ.

public interface DefaultMethodSample {
    default void print(String str) {
        System.out.println(str);
    }
}

これでは抽象クラスとの使い分けがますます難しくなるなあ.

javaメソッドの多重継承が出来るようになったということか.

この仕様について、思いつく範囲のことを通り試してみる. ↓↓↓↓

実装クラスでオーバーライド出来る

すでにインタフェースでデフォルトメソッドとしてメソッドの実装が定義されているため、 実装クラス側では、実装しなくても問題なくなる.

public class DefaultMethodSampleImpl implements DefaultMethodSample{
    //実際こんなクラスは意味がないけど…
}

デフォルトメソッドを実装クラス側でオーバーライドすることも出来ます.

public class DefaultMethodSampleImpl implements DefaultMethodSample{
    @Override
    public void print(String str) {
        System.out.println("DefaultMethodSampleImpl: " + str);
    }
}

インタフェースのメソッドはすべてpublic なので、実装クラス側でオーバーライドするときは、 public がつきます. というかインタフェースのメソッドのpublicを省略してたのが、実装クラス側では当然省略出来なくなるからつきます、ってことだ.

複数インタフェースを実装したクラスでデフォルトメソッドが衝突したらコンパイルエラーになる

クラスは複数のインタフェースを実装することが出来るので、 以下のようにまったく同じシンタックスなデフォルトメソッドをもつインタフェースをimplementsした場合を考えてみた.

public interface DefaultMethodSampleSecond {
    default public void print(String str) {
        System.out.println("second: " + str);
    }
}

これの実装クラスが以下のように複数のインタフェースを実装してしまうと…

public class DefaultMethodSampleImpl implements DefaultMethodSample, DefaultMethodSampleSecond{
}

以下のようなコンパイルエラーがおこる。 どっちのインタフェースのデフォルトメソッドなのか判断出来ないからだ.

Duplicate default methods named print with the parameters (String) and (String) are inherited from the types DefaultMethodSampleSecond and DefaultMethodSample

インタフェース名.super.デフォルトメソッド

でも、実装クラスで implements しているインタフェースのデフォルトメソッドを使いたい場合もあると思う。

そのときは、インタフェース名を指定してあげると呼び出せる.

public class DefaultMethodSampleImpl implements DefaultMethodSample, DefaultMethodSampleSecond{
    @Override
    public void print(String str) {
        DefaultMethodSample.super.print("DefaultMethodSampleImpl: " + str);
    }
}

抽象クラスとインタフェースの使い分けを考えてみた.

結局使い方に困ってしまいそうな気がする.

今までインタフェースと抽象クラスの使い分けは、 インタフェースで定義したメソッドや変数はすべてpublicになるという特性から、 外向きなものを定義していた.

逆にpublicにする必要がないものは抽象クラスで定義してたってかんじ.

ただ、抽象クラスでもpublicで書きたい処理があって、 ↓のようなクラスのexec()メソッドみたいな処理.

abstract public class Executer {
    public void exec(){
        System.out.println("log: exec start.");
        init();
        main();
        end();
        System.out.println("log: exec end.");
    }

    /**
    * 初期処理.
    */
    abstract void init();

    /** 
    * メイン処理.
    */
    abstract void main();

    /**
    * 後処理.
    */
    abstract void end();
}

Executerクラスの子クラスはそれぞれabstractメソッドを定義してもらって、 このクラスを使うときは以下のように呼び出すようなかんじ.

public class Main {
    public static void main(String[] args) {
        Executer execter = new ExecuterChild();
        execter.exec();
    }
}

今までこれを抽象クラスのpublicなメソッドで書いていたのは、 インタフェースにメソッドの実装をかけないからだったので、

今後は、こういうものをインタフェースのデフォルトメソッドで書くようにして、 publicなものはインタフェースに、そうでないものは抽象クラスに書く.

ということが今までよりもはっきり出来そう.

個人的な今までの経験で思いつく範囲では、こんな使い方になりそうだ. 

(そういう使い方をすると抽象クラス自体のスコープをpublicで書くことがなくなりそうだ)

他のひとの意見も聞きたいし、ググってみよう.