C# 使いから見てうらやましい Java8 の default 実装の使い方
Java8 から追加されるインターフェイスの default 実装ですが、C# の拡張メソッドに似てますよね。 実際、このどちらも「シンインターフェイス」を定義するだけで「リッチインターフェイス」が手に入ります。
しかし、C# の拡張メソッドと Java のインターフェイスの default 実装には、それぞれの利点と欠点があります。
拡張メソッドの利点
拡張メソッドの利点は、インターフェイスの実装者だけでなく、 インターフェイスの使用者に対してもインターフェイスの拡張が開かれている点です。 既存の型ですら、後付けでメソッドを追加することができるということです。
using System; public static class StringExtension { // インターフェイスでなくても、どんな型に対しても拡張可能 public static int ToInt(this string self) { return int.Parse(self); } }
var num = "100".ToInt();
それに対して Java8 のインターフェイスの default 実装は、 あくまでインターフェイスの提供者しか提供できません。
これは、Java8 の default 実装に対する、拡張メソッドの大きなメリットです。
default 実装の利点
では default 実装には利点がないか、というと、そうではありません。 default 実装は、リッチインターフェイスをシンインターフェイスに変換してしまうこともできるのです。
import java.util.*; // Appendableは3つのメソッドを提供するインターフェイスだが、 // SamAppendable(SamはSingle Abstract Method)はそのうち2つをdefault実装として提供することで、 // 1つの抽象メソッドを実装すればいいようにしている interface SamAppendable extends Appendable { Appendable append(CharSequence csq, int start, int end); default Appendable append(CharSequence csq) { this.append(csq, 0, csq == null ? 0 : csq.length()); return this; } default Appendable append(char c) { this.append("" + c, 0, 1); return this; } } public class Main { static void appendHoge(SamAppendable a) { a.append("hoge"); } public static void main(String[] args) { // (実はあまりいい例ではないのだけれど) // SAMインターフェイスにしたおかげで、ラムダ式が使える! appendHoge((csq, start, end) -> { System.out.printf("csq: %s, start: %d, end: %d%n", csq, start, end); return null; }); } }
それに対して、拡張メソッドではこのようなことは出来ません。
ジェネリックな IEnumerable は非ジェネリックな IEnumerable を継承しているため、 GetEnumerator を 2 種類実装する必要があって面倒なんですが、 「あれ、Java 8 の default 実装だとこの問題解決できるんじゃ?」 ・・・と思ったのがこのエントリを書くことになったきっかけです。 なるほど、拡張メソッドにしなかったのには理由がありそうですね。
そんなことより Scala ですよ
こんな感じで、どちらも一長一短であって優劣つけがたい感じなのですが・・・ なんと、Scala の trait はこのどちらにも対応しています。
既存の型へのメンバの追加
object Extensions { implicit class StringExtension(val value: String) { def toInteger: Int = value.toInt } }
2.10 からかなり簡単に書けるようになりました。
リッチインターフェイスのシンインターフェイス化
trait SamAppendable extends Appendable { def append(csq: CharSequence, start: Int, end: Int): Appendable def append(csq: CharSequence): Appendable = { this.append(csq, 0, if (csq == null) 0 else csq.length) this } def append(c: Char): Appendable = { this.append("" + c, 0, 1) this } }
Scala では Appendable を SAM インターフェイスにする意味はないですが、 リッチでファットなインターフェイスをシンインターフェイスにできるのは、 既存の Java コードとのやり取りを考えるとありがたいかもしれません。
Java8
Java8 に期待するより、今使える Scala 使おう! でも、default 実装が拡張メソッドの劣化コピーとか言っちゃダメ!