1. ホーム
  2. java

[解決済み] Java 8 のデフォルトメソッドを trait として利用する:安全か?

2022-09-25 10:11:59

質問

を使うのは安全な方法ですか? デフォルトのメソッドを traits の貧乏人バージョンとして使うのは安全な方法でしょうか? を使うのは安全な方法ですか?

パンダを悲しませるかもしれないと主張する人もいる をカッコイイからという理由だけで使うと、パンダが悲しむかもしれないと言う人もいますが、それは私の意図するところではありません。また、デフォルトメソッドはAPIの進化や後方互換性をサポートするために導入されたとよく言われますが、それはその通りですが、だからといってそれをtraitとして使うこと自体が間違っていたり捻くれているわけではありません。

私は を使用していますが、次のような実用的なユースケース を念頭に置いています。

public interface Loggable {
    default Logger logger() {
        return LoggerFactory.getLogger(this.getClass());
    }
}

あるいは PeriodTrait :

public interface PeriodeTrait {
    Date getStartDate();
    Date getEndDate();
    default isValid(Date atDate) {
        ...
    }
}

確かに、コンポジションを使うこともできますが(あるいはヘルパークラスも)、より冗長で雑然とした印象を与え、ポリモーフィズムの恩恵を受けることができません。

そこで 基本的な特徴としてデフォルトのメソッドを使用するのは良いのでしょうか? それとも予期せぬ副作用を心配すべきでしょうか?

いくつかの質問 のいくつかの質問は、JavaとScalaの特性に関するものですが、ここではそれが目的ではありません。私は単に意見を求めているわけでもありません。その代わりに、私は権威ある答え、あるいは少なくとも現場での洞察を求めています:もしあなたが企業のプロジェクトでtraitとしてデフォルトのメソッドを使ったことがあるなら、それは時限爆弾であることが判明しましたか?

どのように解決するのですか?

簡単に言うと、「安全に使えば大丈夫」ということです :)

嫌味な答え:教えてください あなた そうすれば、もっといい答えを出せるかもしれません。)

真面目に言うと、traitという用語はあまり定義されていません。 多くの Java 開発者は Scala で表現される trait に最も精通していますが、Scala は名実ともに trait を持つ最初の言語とは程遠いものです。

例えば、Scalaでは、traitはステートフル(stateful)です。 var 変数を持つことができる)、Fortressでは純粋な振る舞いです。 Javaのデフォルトメソッド付きインターフェースはステートレスですが、これはtraitではないということでしょうか? (ヒント:ひっかけ問題でした)

繰り返しますが、Scalaでは、traitは線形化によって構成されます;もしクラス A が traits を拡張している場合 XY の順で、その後に XY が混在していると XY は解決されます。 Java では、この線形化メカニズムはありません (あまりにも "un-Java-like" であったため、一部で却下されました)。

インターフェースにデフォルトのメソッドを追加する近接した理由は インターフェイスの進化 をサポートするためでしたが、私たちはそれを超えていることを十分認識していました。 それを「インターフェースの進化++"」と考えるか、「形質--"」と考えるかは、個人の解釈の問題です。 ですから、安全性についての質問には、メカニズムが実際にサポートしていないものまで希望的に拡大しようとするのではなく、メカニズムが実際にサポートしているものに固執する限り、問題ないでしょう。

設計上の重要な目標は、"em "の観点から、"em "が "em "であることでした。 クライアント の観点から、デフォルトのメソッドは通常のインターフェイスメソッドと区別がつかないようにすることです。 そのため、メソッドのデフォルト性は 設計者 インプリメンター があります。

設計目標の範囲内にあるユースケースをいくつか紹介します。

  • インターフェースの進化。 ここでは、既存のインターフェイスに新しいメソッドを追加していますが、そのインターフェイスの既存のメソッドから見て、賢明なデフォルトの実装になっています。 例としては forEach メソッドを Collection のように、デフォルトの実装が書かれています。 iterator() メソッドで記述されます。

  • "Optional"メソッドです。 ここで、インターフェースの設計者は、「実装者は、このメソッドがもたらす機能の制限に耐えられるのであれば、このメソッドを実装する必要はない」と述べているのです。 例えば Iterator.remove を投げるデフォルトが与えられています。 UnsupportedOperationException の実装の大部分は Iterator の実装の大部分はいずれにせよこの動作をしているので、 デフォルトではこのメソッドは本質的にオプションになっています。 (もし AbstractCollection の挙動がデフォルトとして表現されていた場合 Collection のデフォルトとして表現されていたのであれば、変異型メソッドについても同じように表現することができるかもしれません)。

  • 便宜的なメソッド。 これらは厳密に利便性を追求したメソッドで、やはり一般的にはクラス上の非デフォルトメソッドという形で実装されます。 このメソッドは logger() メソッドは、この合理的な例です。

  • コンビネータです。 これらは、現在のインスタンスに基づいてインターフェースの新しいインスタンスをインスタンス化するコンポジション・メソッドです。 例えば、メソッド Predicate.and() または Comparator.thenComparing() はコンバイネータの例です。

デフォルトの実装を提供する場合、デフォルトのための何らかの仕様も提供する必要があります(JDKでは @implSpec javadoc タグを使用します)、実装者がメソッドをオーバーライドするかどうかを理解するのに役立つようにします。 便利なメソッドやコンビネーターのように、ほとんどオーバーライドされないデフォルトもあれば、オプションのメソッドのように、しばしばオーバーライドされるものもあります。 デフォルトが何を約束するかについて十分な仕様(文書だけでなく)を提供する必要があり、実装者はそれをオーバーライドする必要があるかどうかについて賢明な決定を下すことができます。