Java8で最もインパクトのある構文拡張、デフォルトメソッド

Java8でのラムダの使い方などを説明してきたのですが、構文拡張自体には触れていなかったので、改めてここで簡単に説明しておこうと思います。
まずは、Java8で実際に最もインパクトがある言語拡張、インタフェースのデフォルトメソッドです。

デフォルトメソッドとデフォルト実装

いままでインタフェースには実装をもつことができませんでしたが、Java 8からはインタフェースが実装をもてるようになります。
実装をもつメソッドを定義するときには、キーワードdefaultをメソッドの前につけます。

interface Foo{
    void print(String s);
    default void twice(String s){
        print(s);
        print(s);
    }
}


twiceメソッドが実装をもっています。この実装をデフォルト実装といいます。
デフォルトメソッドを実装するクラスで、デフォルトメソッドを実装していない場合は、デフォルト実装が使われます。

static class FooImpl implements Foo{
    @Override
    public void print(String s) {
        System.out.println(s);
    }
}


次のtwiceメソッドの呼び出しでは、Fooインタフェースでのデフォルト実装が使われます。

public static void main(String... args){
    Foo f = new FooImpl();
    f.twice("yeah!");
}


ただし、toStringなどObjectクラスがもつメソッドのデフォルト実装をもつことはできません。

デフォルトメソッドでの多重継承

デフォルトメソッドは、Javaに多重継承を持ち込みます。
インタフェースは複数のインタフェースを継承することができ、またクラスでは複数のインタフェースを実装することができます。そうすると、インタフェースが実装をもつことによって、複数のインタフェースで同じシグネチャのメソッドがあったときに衝突が発生します。


たとえば、次のようにtwiceというデフォルトメソッドをもったインタフェースがあるとします。

interface Bar{
    void put(String s);
    default void twice(String s){
        put(s + s);
    }
}


単体での実装は問題ありません。

static class BarImpl implements Bar{
    @Override
    public void put(String s) {
        System.out.println(s);
    }
}

次のように呼び出すことができます。

Bar b = new BarImpl();
b.twice("ゴゴ");


ところが、次のように、それぞれデフォルトメソッドとしてtwiceメソッドを持ったFoo、Barインタフェースを両方実装するクラスを定義しようとすると、コンパイルエラーになります。

static class FooBarImpl implements Foo, Bar{
    @Override
    public void print(String s) {
        System.out.println(s);
    }

    @Override
    public void put(String s) {
        System.out.println(s);
    }
}


次のようなコンパイルエラーになります。


この場合、改めてデフォルトメソッドを実装してしまえば、問題なくコンパイルすることができます。どちらかのインタフェースのデフォルト実装を利用したい場合は、キーワードsuperを使ってインタフェースを指定したデフォルト実装を呼び出すことができます。

static class FooBarImpl implements Foo, Bar{

    @Override
    public void print(String s) {
        System.out.println(s);
    }

    @Override
    public void put(String s) {
        System.out.println(s);
    }
    @Override
    public void twice(String s){
        Bar.super.twice(s);
    }
}

抽象クラスとの違い

2013/6/23 追記
インタフェースが実装をもてることで、抽象クラスとの違いがあまりなくなってきました。
簡単に両者の違いをまとめると次のようになります。
インタフェース:多重継承できる。状態をもてない。
抽象クラス:多重継承できない。状態をもてる。

現場で使える[最新]Java SE 7/8 速攻入門

現場で使える[最新]Java SE 7/8 速攻入門