Javaのデフォルトメソッドの使用


13

何十年には、インターフェースがあった場合をされているのみ だけメソッドのシグネチャを特定する(のみ)。これが「物事を行う正しい方法™」だと言われました。

その後、Java 8が登場して次のように述べました。

えーと、ええと、今ではデフォルトのメソッドを定義できます。さようなら。

経験豊富なJava開発者と、最近(ここ数年)開発を始めたJava開発者の両方によって、これがどのように消化されているのか興味があります。また、これがJavaの正統性と実践にどのように適合するかについても疑問に思っています。

私はいくつかの実験的なコードを構築しており、リファクタリングを行っている間に、標準インターフェース(Iterable)を単純に拡張し、2つのデフォルトメソッドを追加するインターフェースになりました。そして正直に言うと、私はそれについてかなり気分が良いと感じています。

私はこれが少しオープンエンドであることを知っていますが、実際のプロジェクトでJava 8を使用する時間がありましたが、デフォルトメソッドの使用に関する正統性はまだありますか?それらが議論されるときに私が主に見るものは、既存の消費者を壊すことなく、インターフェースに新しいメソッドを追加する方法についてです。しかし、上記の例のように最初からこれを使用するのはどうですか。インターフェースに実装を提供することで問題に直面した人はいますか?


私もこの視点に興味があります。.Netの世界で6年ぶりにJavaに戻ります。これは、C#拡張メソッドに対するJavaの回答であり、Rubyのモジュールメソッドの影響を少し受けているように思えます。私はそれで遊んでいませんでしたので、私は確信できません。
ベリンロリチュ

1
私は主にので、彼らは完全に異なるインタフェースを作ることなく、コレクションインタフェースを拡張することができることである彼らは、デフォルトのメソッドを追加した理由のように感じる
ジャスティンを

1
@Justin:java.util.function.Function新しいインターフェイスでのデフォルトメソッドの使用法を参照してください。
ヨルグWミットタグ

@ジャスティン私の推測では、これが主なドライバーだった。彼らが本当に変更を始めたので、私は本当にプロセスに再び注意を払うべきです。
ジミージェームズ

回答:


12

優れたユースケースは、「レバー」インターフェースと呼ばれるものです。抽象メソッドは少数(理想的には1)のみですが、多くの機能を提供するという点で多くの「レバレッジ」を提供するインターフェースです。クラスに1つのメソッドを実装する必要がありますが、「無料」で他の多くのメソッドを取得します。単一抽象的で、例えば、コレクションインタフェースを考えるforeach方法及びdefault方法のようにmapfoldreducefilterpartitiongroupBysortsortBy、など

以下に例をいくつか示します。から始めましょうjava.util.function.Function<T, R>。単一の抽象メソッドがありR apply<T>ます。また、2つの異なる方法(前または後)で別の関数と関数を構成できる2つのデフォルトメソッドがあります。これらの構成メソッドは両方とも、次を使用して実装されますapply

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    return (T t) -> after.apply(apply(t));
}

次のような、同等のオブジェクトのインターフェースを作成することもできます。

interface MyComparable<T extends MyComparable<T>> {
  int compareTo(T other);

  default boolean lessThanOrEqual(T other) {
    return compareTo(other) <= 0;
  }

  default boolean lessThan(T other) {
    return compareTo(other) < 0;
  }

  default boolean greaterThanOrEqual(T other) {
    return compareTo(other) >= 0;
  }

  default boolean greaterThan(T other) {
    return compareTo(other) > 0;
  }

  default boolean isBetween(T min, T max) {
    return greaterThanOrEqual(min) && lessThanOrEqual(max);
  }

  default T clamp(T min, T max) {
    if (lessThan(   min)) return min;
    if (greaterThan(max)) return max;
                          return (T)this;
  }
}

class CaseInsensitiveString implements MyComparable<CaseInsensitiveString> {
  CaseInsensitiveString(String s) { this.s = s; }
  private String s;

  @Override public int compareTo(CaseInsensitiveString other) {
    return s.toLowerCase().compareTo(other.s.toLowerCase());
  }
}

または、Collection元のタイプが何であったかに関係なく、すべてのコレクション操作がを返す非常に単純化されたコレクションフレームワーク:

interface MyCollection<T> {
  void forEach(java.util.function.Consumer<? super T> f);

  default <R> java.util.Collection<R> map(java.util.function.Function<? super T, ? extends R> f) {
    java.util.Collection<R> l = new java.util.ArrayList();
    forEach(el -> l.add(f.apply(el)));
    return l;
  }
}

class MyArray<T> implements MyCollection<T> {
  private T[] array;

  MyArray(T[] array) { this.array = array; }

  @Override public void forEach(java.util.function.Consumer<? super T> f) {
    for (T el : array) f.accept(el);
  }

  @Override public String toString() {
    StringBuilder sb = new StringBuilder("(");
    map(el -> el.toString()).forEach(s -> { sb.append(s); sb.append(", "); } );
    sb.replace(sb.length() - 2, sb.length(), ")");
    return sb.toString();
  }

  public static void main(String... args) {
    MyArray<Integer> array = new MyArray<>(new Integer[] {1, 2, 3, 4});
    System.out.println(array);
    // (1, 2, 3, 4)
  }
}

このような「レバー」インターフェースはラムダ(SAMインターフェース)で実装できるため、これはラムダとの組み合わせで非常に興味深いものになります。

これは、拡張メソッドがC♯で追加されたのと同じユースケースですが、デフォルトのメソッドには1つの明確な利点があります。「適切な」インスタンスメソッドです。つまり、インターフェイスのプライベート実装詳細にアクセスできます(privateインターフェイスメソッドが登場しますJava 9)では、拡張メソッドは静的メソッドの構文糖にすぎません。

Javaがインターフェイスインジェクションを取得した場合、タイプセーフなスコープ付きモジュールモンキーパッチも許可されます。これは、JVMの言語実装者にとって非常に興味深いものです。たとえば、現時点では、JRubyは、Javaクラスを継承するかラップして、追加のRubyセマンティクスを提供しますが、理想的には、同じクラスを使用します。インターフェイスインジェクションとデフォルトメソッドをRubyObject使用するとjava.lang.Object、たとえばにインターフェイスをインジェクトでき​​るため、Java ObjectとRuby Objectまったく同じものになります。


1
私はこれに完全には従いません。インターフェイス上のデフォルトのメソッドは、インターフェイス上の他のメソッドまたはObjectで定義されたメソッドに関して定義する必要があります。デフォルトのメソッドを使用して、意味のある単一メソッドインターフェイスを作成する方法の例を教えてください。デモにJava 9構文が必要な場合は、それで問題ありません。
ジミージェームズ

たとえば、次のようにComparable抽象とのインタフェースcompareTo方法、およびデフォルトlessThanlessThanOrEqualgreaterThangreaterThanOrEqualisBetween、及びclamp方法は、全ての面で実施しますcompareTo。または、単に見てくださいjava.util.function.Function。抽象applyメソッドと2つのデフォルトの合成メソッドがあり、両方ともで実装されていapplyます。Collectionインターフェースの例を挙げようとしましたが、すべてをタイプセーフにするのは難しく、この答えには長すぎます。非タイプセーフ、非タイプ保存バージョンを試してみましょう。乞うご期待。
ヨルグWミットタグ

3
例が役立ちます。ありがとう。単一のメソッドインターフェイスが何を意味するのかを誤解しました。
ジミージェームズ

デフォルトのメソッドが意味することは、単一の抽象メソッドインターフェースがもはや単一のメソッドインターフェースである必要はないということです;
JörgW Mittag

私はこれについて考えていましたが、AbstractCollectionとAbstractListは基本的にここで話していることです(1の代わりに2つのメソッドですが、それは重要ではないと思います)。サイズを追加して任意のものからリストを作成することで、イテレート可能オブジェクトをコレクションに変換するのは非常に簡単です。インデックスを作成してサイズを知ることができれば簡単です。
ジミージェームズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.