web-dev-qa-db-ja.com

Java 8インターフェースメソッドで「最終」が許可されないのはなぜですか?

Java 8の最も便利な機能の1つは、インターフェイスの新しいdefaultメソッドです。それらが導入された理由は、本質的に2つあります(他にもある可能性があります)。

APIデザイナーの観点から、インターフェイスメソッドで他の修飾子を使用できるようにしたいと思いました。 final。これは、便利なメソッドを追加し、クラスを実装する際の「偶発的な」オーバーライドを防ぐときに役立ちます。

interface Sender {

    // Convenience method to send an empty message
    default final void send() {
        send(null);
    }

    // Implementations should only implement this method
    void send(String message);
}

Senderがクラスの場合、上記はすでに一般的な方法です。

abstract class Sender {

    // Convenience method to send an empty message
    final void send() {
        send(null);
    }

    // Implementations should only implement this method
    abstract void send(String message);
}

ここで、defaultfinalは明らかに矛盾するキーワードですが、デフォルトのキーワード自体 厳密には必須ではありませんでした なので、この矛盾は "本体を持つクラスメソッド"(単なるメソッド)および "本体を持つインターフェイスメソッド"(デフォルトの方法)、つまり、まだ理解していない違い。

ある時点で、インターフェイスメソッドでのstaticfinalのような修飾子のサポートはまだ完全には検討されていませんでした citing Brian Goetz

もう1つの部分は、最終メソッド、プライベートメソッド、保護メソッド、静的メソッドなど、インターフェイスでクラス構築ツールをどこまでサポートするかです。答えは、まだわかりません。

2011年後半のその時以来、明らかに、インターフェイスでのstaticメソッドのサポートが追加されました。明らかに、これは Comparator.comparing() などのように、JDKライブラリ自体に多くの価値を追加しました。

質問:

final(およびstatic final)がJava 8インターフェイスに到達しなかった理由は何ですか?

314
Lukas Eder

この質問は、ある程度、 Java 8インターフェースメソッドで「同期」が許可されない理由は何ですか?

デフォルトのメソッドについて理解しておくべき重要なことは、主な設計目標がインターフェイスの進化であり、「インターフェイスを(中程度の)特性に変える」ことではないということです。 2つの間には重複があり、前者の邪魔にならない後者に対応しようとしましたが、これらの質問はこの観点から最もよく理解されます。 (クラスメソッドareは、インターフェイスメソッドを多重継承できるため、意図が何であれ、インターフェイスメソッドとは異なることに注意してください。)

デフォルトメソッドの基本的な考え方は、デフォルト実装を備えたインターフェースメソッドであり、派生クラスはより具体的な実装を提供できるということです。また、デザインセンターはインターフェイスの進化であったため、デフォルトメソッドをインターフェイスに追加できることが重要な設計目標でした事後ソース互換およびバイナリ互換の方法で。

「最終的なデフォルトのメソッドではない理由」に対するあまりにも単純な答えは、その場合、本体は単にデフォルトの実装ではなく、それが唯一の実装になるということです。これは少し単純すぎる答えですが、質問がすでに疑わしい方向に向かっているという手がかりを与えてくれます。

最終的なインターフェイスメソッドが疑わしいもう1つの理由は、実装者にとって不可能な問題が生じることです。たとえば、次のものがあるとします。

interface A { 
    default void foo() { ... }
}

interface B { 
}

class C implements A, B { 
}

ここでは、すべてが良好です。 Cは、Aからfoo()を継承します。 Bがデフォルトでfooメソッドを持つように変更されたと仮定します。

interface B { 
    default void foo() { ... }
}

ここで、Cを再コンパイルすると、コンパイラはfoo()の継承する動作がわからないことを通知するため、Cはそれをオーバーライドする必要があります(同じ動作を維持する場合は、A.super.foo()に委任することもできます)。 )しかし、Bがデフォルトのfinalを作成し、ACの作成者の制御下にない場合はどうでしょうか?現在、Cは完全に壊れています。 foo()をオーバーライドせずにコンパイルすることはできませんが、Bで最後になった場合はfoo()をオーバーライドできません。

これはほんの一例に過ぎませんが、ポイントは、メソッドの最終性は、単に動作を提供して乗算できるインターフェースよりも、単一継承クラス(一般に状態を動作に結び付けるクラス)の世界で本当に意味のあるツールであるということです継承されました。 「最終的な実装者に他のインターフェイスがどのように混在する可能性があるか」について推論するのは難しすぎるため、インターフェイスメソッドを最終的に許可すると、これらの問題が発生する可能性が高くなります(インターフェイスを作成した人ではなく、それを実装しようとする貧しいユーザー。)

それらを禁止する別の理由は、彼らがあなたが彼らが意味すると思うものを意味しないということです。デフォルトの実装は、クラス(またはそのスーパークラス)がメソッドの宣言(具体的または抽象的)を提供しない場合にのみ考慮されます。デフォルトのメソッドがファイナルであるが、スーパークラスがすでにメソッドを実装している場合、デフォルトは無視されます。これはおそらく、デフォルトの作成者がファイナルを宣言するときに期待していたことではありません。 (この継承動作は、デフォルトメソッドのデザインセンターを反映しています-インターフェースの進化。デフォルトメソッド(または既存のインターフェースメソッドへのデフォルトの実装)を、既に実装されている既存のインターフェースに追加することができます。インターフェイスを実装する既存のクラスの動作。デフォルトのメソッドが追加される前にすでに動作していたクラスが、デフォルトのメソッドが存在する場合でも同じように動作することを保証します。

386
Brian Goetz

ラムダメーリングリストで それについての議論がたくさんあります 。これらすべてに関する多くの議論が含まれていると思われるものの1つは、次のとおりです。On さまざまなインターフェイスメソッドの可視性(最終防御者でした)

このディスカッションでは、 元の質問 の著者であるTaldenが、あなたの質問に非常によく似た質問をしています。

すべてのインターフェイスメンバーを公開するという決定は、実際には不幸な決定でした。内部設計でインターフェイスを使用すると、実装のプライベートな詳細が公開されることは大きなことです。

あいまいさや互換性を損なうニュアンスを言語に追加せずに修正するのは難しいものです。その大きさと潜在的な微妙さの互換性の破れは無慈悲に見えるので、既存のコードを壊さない解決策が存在しなければなりません。

Access-specifierとして「package」キーワードを再導入できる可能性があります。インターフェイスに指定子がないことは、パブリックアクセスを意味し、クラスに指定子がないことはパッケージアクセスを意味します。インターフェイスでどの指定子が意味をなすかは不明です。特に、開発者の知識負担を最小限に抑えるために、アクセス指定子が存在する場合は、クラスとインターフェイスの両方で同じことを意味する必要があります。

デフォルトメソッドがない場合、インターフェイスのメンバーの指定子は、少なくともインターフェイス自体と同じくらい見える必要があると推測していました(したがって、インターフェイスは実際にすべての可視コンテキストで実装できます)-デフォルトメソッドではありません確かに。

これが可能な範囲内の議論であるかどうかに関して明確なコミュニケーションがありましたか?そうでない場合は、他の場所で開催する必要があります。

最終的に Brian Goetzの答え は:

はい、これはすでに調査中です。

ただし、いくつかの現実的な期待を設定させてください。言語/ VM機能には、このような些細なものであっても、長いリードタイムがあります。 Java SE 8の新しい言語機能のアイデアを提案する時間はほとんど過ぎました。

したがって、スコープの一部ではないため、実装されなかった可能性があります。検討する時間内に提案されることはありませんでした。

別の白熱した議論で 最終的な防御方法について 主題について ブライアンが再び言った

そして、あなたはまさにあなたが望むものを手に入れました。それがまさにこの機能が追加するものです-動作の多重継承。もちろん、人々はそれらを特性として使用することを理解しています。そして、私たちは、彼らが提供する継承のモデルがシンプルでクリーンであることを保証するために一生懸命努力しました。同時に、単純かつきれいに機能するものの境界を超えてそれらをプッシュしないことを選択しました。しかし、実際には、このスレッドのほとんどは、グラスが98%しか満たされていないと不平を言っているようです。私はその98%を受け入れて、それに取りかかります!

ですから、これは、それが単に範囲の一部でも設計の一部でもないという私の理論を補強します。彼らがしたことは、APIの進化の問題に対処するのに十分な機能を提供することでした。

42
Edwin Dalorzo

@EJPのコメントで言及されている回答については、「THE」の答えを見つけて特定することは困難です。世界には、明確な答えを出すことができるおよそ2人(+/- 2)の人がいますまったく。疑いの余地はありますが、答えは「最終的なデフォルトメソッドをサポートすることは、内部コール解決メカニズムを再構築するだけの価値があるとは思えなかった」というようなものにすぎないかもしれません。もちろんこれは推測ですが、少なくとも次のような微妙な証拠に裏付けられています OpenJDKメーリングリストの(2人のうちの1人による)ステートメント

「「最終デフォルト」メソッドが許可された場合、内部invokespecialからユーザーに見えるinvokeinterfaceに書き換える必要があるかもしれません。」

そして、そのような些細な事実は、メソッドがdefaultメソッドであるときに(実際には)最終メソッドであると考慮されないであり、現在 Method :: is_final_method OpenJDKのメソッド。

過度のWeb検索やコミットログの読み取りでも、さらに「信頼できる」情報を見つけるのは困難です。 invokeinterface命令を使用したインターフェイスメソッド呼び出しと、invokevirtual命令に対応するクラスメソッド呼び出しの解決時の潜在的なあいまいさに関連する可能性があると考えました:invokevirtual命令には、単純なvtable検索。メソッドはスーパークラスから継承するか、クラスによって直接実装する必要があります。それとは対照的に、invokeinterface呼び出しは、それぞれの呼び出しサイトを調べて、この呼び出しが実際に参照しているwhichインターフェースを見つける必要があります(これは InterfaceCalls HotSpot Wikiのページ)。ただし、finalメソッドはvtableにまったく挿入されないか、vtableの既存のエントリを置き換えません( klassVtableを参照してください。 cpp。行3 )、および同様に、デフォルトのメソッドはvtableの既存のエントリを置き換えます( klassVtable.cpp、行202 を参照)。そのため、実際の理由(したがって、答え)は(かなり複雑な)メソッド呼び出し解決メカニズムの奥深くに隠されている必要がありますが、それでもこれらの参照は役立つと考えられます。それから実際の答えを導き出すことができた他の人のためだけに。

16
Marco13

コンビニエンスインターフェイスメソッドでfinalを指定する必要はないと思いますが、それはmayが有用であることに同意できますが、コストがメリットを上回るようです。

いずれにしても、あなたがすべきことは、デフォルトのメソッドに対して適切なjavadocを作成し、メソッドが何であり、何が許可されていないかを正確に示すことです。そのようにして、インターフェースを実装するクラスは実装を変更することを「許可されません」が、保証はありません。

インターフェイスに準拠したCollectionをだれでも書くことができ、絶対に直観に反するメソッドで物事を行うことができます。広範な単体テストを書くこと以外は、それから自分自身を保護する方法はありません。

4
skiwi