2つのJava 8のデフォルトメソッドを相互に実装するのは良い習慣ですか?


14

私はこれに似た2つの関連する方法でインターフェースを設計しています:

public interface ThingComputer {
    default Thing computeFirstThing() {
        return computeAllThings().get(0);
    }

    default List<Thing> computeAllThings() {
        return ImmutableList.of(computeFirstThing());
    }
}

実装の約半分で計算されるのは1つだけですが、残りの半分ではさらに多くの計算が行われます。

これは、広く使用されているJava 8コードに先例がありますか?Haskellがいくつかのタイプクラスで同様のことを行うことは知っています(Eqたとえば)。

利点は、2つの抽象クラス(SingleThingComputerおよびMultipleThingComputer)を使用した場合よりも大幅に少ないコードを記述する必要があることです。

欠点は、空の実装がコンパイルされますが、実行時にStackOverflowError。を使用して相互再帰を検出しThreadLocal、より良いエラーを与えることは可能ですが、それにより、バグのないコードにオーバーヘッドが追加されます。


3
無限再帰を保証するコードを意図的に書くのはなぜですか?そこから良いものは得られないでしょう。

1
まあ、それは「保証」されていません。適切な実装により、これらのメソッドの1つがオーバーライドされます。
タビアンバーンズ

6
あなたはそれを知りません。クラスまたはインターフェースは単独で立つ必要があり、サブクラスが重大な論理エラーを回避するために特定の方法を実行すると想定しないでください。そうでなければ、それはもろく、その保証を果たすことができません。インターフェイスが実装クラスを実行しthrow new Error();たり、バカなことを強制できると言っているわけではないことに注意してください。インターフェイス自体にdefaultメソッドを介した脆弱なコントラクトがないようにしてください。

私は@Snowmanに同意します、それは地雷です。私はジョンスキートの手段という意味での教科書「悪のコード」を考える-悪が同じではないことに注意してください悪いが、それは邪悪な/賢い/の魅力的なのですが、それでも、間違った:)私はお勧めyoutu.be/lGbQiguuUGc?t=15m26sを(または実際、より広い文脈のための全体の話-それは非常に興味深いです)。
コンラッドモラウスキー

1
各関数が他の用語で定義できるからといって、両方の関数が互いの用語で定義できるとは限りませ。それはどの言語でも飛びません。2つの抽象継承を備えたインターフェイスが必要です。それぞれが一方を他方の観点から実装しますが、他方を抽象化するため、実装者は正しい抽象クラスから継承することで実装する側を選択します。一方が他方から独立して定義されている必要がありますそうでない場合、popがスタックに進みます。あなたは実装者があなたのために問題を解決すると推測する非常識なデフォルトを持っています、彼らにそれを解決することabstract強制するために存在します。
ジミーホファ

回答:


16

TL; DR:これをしないでください。

ここで示すのは、脆弱なコードです。

インターフェースは契約です。「取得するオブジェクトに関係なく、XとYを実行できます」と表示されます。書かれているとおり、インターフェイスはスタックオーバーフローを引き起こすことが保証されているため XもYも実行しませ

エラーまたはサブクラスをスローすると、キャッチすべきではない重大なエラーが示されます。

エラーはThrowableのサブクラスであり、合理的なアプリケーションがキャッチしようとしてはならない重大な問題を示します。

さらに、StackOverflowErrorの親クラスであるVirtualMachineErrorは次のように言います。

Java仮想マシンが壊れているか、動作を継続するために必要なリソースが不足していることを示すためにスローされます。

あなたのプログラムはJVMリソースに関係するべきではありません。それがJVMの仕事です。通常の操作の一部としてJVMエラーを引き起こすプログラムを作成するのは悪いことです。プログラムがクラッシュすることを保証するか、このインターフェースのユーザーに、関係のないエラーをトラップさせます。


「C#言語設計委員会のメンバー」という名誉ある努力からエリック・リッパートを知っているかもしれません。彼は、人々を成功または失敗へと導く言語機能について語っています。これは言語機能や言語設計の一部ではありませんが、インターフェイスの実装やオブジェクトの使用に関しては、彼の主張は同様に有効です。

プリンセス・ブライドで、ウェストリーが目を覚まし、しわがれたアルビノと不吉な6本指の男、リューゲン伯爵と絶望の穴に閉じ込められているのを覚えていますか?絶望の穴の基本的な考え方は2つあります。第一に、それはピットであり、したがって、落ちやすいが、登るのは難しい仕事であること。第二に、それは絶望を引き起こす。したがって、名前。

ソース:C ++と絶望の穴

StackOverflowErrorデフォルトでインターフェースをスローすると、開発者は絶望のsに追い込まれますが、これは悪い考えです。代わりに、開発者をPit of Successにプッシュします それは作る簡単正しくJVMをクラッシュすることなく、あなたのインターフェースを使用します。

ここでは、メソッド間の委任は問題ありません。ただし、依存関係は一方向に進む必要があります。メソッドの委任を有向グラフのように考えるのが好きです。各メソッドはグラフ上のノードです。メソッドが別のメソッドを呼び出すたびに、呼び出し元のメソッドから呼び出されたメソッドにエッジを描画します。

グラフを描いて、それが周期的であることに気付いた場合、それはコードの匂いです。これは、開発者を絶望のpushingに追い込む可能性です。断固として禁止されるべきではなく、注意を払う必要があることに注意してください。再帰的アルゴリズムの呼び出しグラフには、特にサイクルがあります。それは問題ありません。それを文書化し、開発者に警告します。再帰的でない場合は、そのサイクルを中断してみてください。できない場合は、どの入力がスタックオーバーフローを引き起こす可能性があるかを調べ、それらを軽減するか、他に何も機能しない場合は最後のケースとして文書化します。


素晴らしい答え。なぜこれがHaskellでこれほど一般的な慣行なのか興味があります。さまざまな開発者の文化。
タビアンバーンズ

私はHaskellの経験がなく、これが関数型プログラミングで一般的であるかどうか、またはその理由について話すことができません。

もう少し調べてみると、Haskellコミュニティもあまり満足していないようです。また、コンパイラは最近、「最小限の完全な定義」をチェックすることを学習したため、空の実装は少なくとも警告を生成します。
タビアンバーンズ

1
ほとんどの関数型プログラミング言語には、末尾呼び出しの最適化があります。この最適化により、末尾再帰はスタックを爆破しないので、このようなプラクティスは理にかなっています。
ジェイソンヨー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.