Java8:java.lang.Objectからメソッドのデフォルトメソッドを定義することが禁じられているのはなぜですか


130

デフォルトのメソッドは、Javaツールボックスの優れた新しいツールです。しかし、私はを定義するインターフェイスを書いてみましたdefaulttoStringメソッドのバージョン。で宣言されたメソッドは編集java.lang.Objectできない可能性があるため、これは禁止されているとJavaは教えてくれdefaultます。これはなぜですか?

「基本クラスは常に勝つ」というルールがあることを知っているので、デフォルトでは(pun;)、 defaultObjectメソッドの実装はObjectとにかくメソッドによって上書きされます。ただし、Object仕様に記載されているメソッドの例外が存在してはならない理由はありません。特にtoString、デフォルトの実装があると非常に便利です。

それで、Javaデザイナーが許可しないことに決めた理由は何ですか defaultメソッドをオーバーライドメソッドをObjectですか?


1
私は今自分自身についてとても気持ちがよく、これを100回賛成して、金のバッジを獲得します。良い質問!
ユージーン

回答:


186

これは、掘り出しを開始して実際に悪いアイデアであることに気付くまでは、「明らかに良いアイデア」と思われる言語設計の問題のもう1つです。

このメールには、件名(およびその他の件名)に関する多くの情報が含まれています。現在の設計に私たちを導くために収束したいくつかの設計力がありました。

  • 継承モデルをシンプルにしたいという願望。
  • 明らかな例を過ぎると(たとえば、AbstractListインターフェースに変わる)、equals / hashCode / toStringの継承は単一の継承と状態に強く結びついており、インターフェースは多重継承され、ステートレスであることがわかります。
  • それが潜在的にいくつかの驚くべき行動への扉を開いたこと。

「シンプルに保つ」という目標についてはすでに触れました。継承と競合解決のルールは非常にシンプルになるように設計されています(クラスはインターフェイスに勝ち、派生インターフェイスはスーパーインターフェイスに勝ちます。その他の競合は実装クラスによって解決されます)。もちろん、これらのルールを微調整して例外を作成することもできますが、そのひもを引っ張り始めると、増分する複雑さが思ったほど小さくはないことがわかるでしょう。

もちろん、より複雑さを正当化するある程度の利点はありますが、この場合はありません。ここで話しているメソッドは、equals、hashCode、およびtoStringです。これらのメソッドはすべて本質的にオブジェクトの状態に関するものであり、そのクラスの等価が何を意味するかを決定するのに最適な位置にあるのは、インターフェイスではなく状態を所有するクラスです(特に、等価のコントラクトは非常に強いため、効果を参照してください)いくつかの驚くべき結果のためのJava); インターフェースライターが削除されすぎています。

このAbstractList例は簡単に引き出すことができます。AbstractList動作を取り除いてListインターフェースに組み込むことができれば、すばらしいでしょう。しかし、この明白な例を超えて移動すると、他に見つかる優れた例は多くありません。ルートでAbstractListは、単一継承のために設計されています。ただし、インターフェースは多重継承用に設計する必要があります。

さらに、あなたがこのクラスを書いていると想像してください:

class Foo implements com.libraryA.Bar, com.libraryB.Moo { 
    // Implementation of Foo, that does NOT override equals
}

Fooスーパータイプで作家のルックスは、対等のない実装を見ないし、その取得するための参照の等価を締結、彼が行う必要があるのは、から継承イコールですObject。その後、来週、Barのライブラリメンテナがデフォルトのequals実装を追加しました。おっと!現在、のセマンティクスはFoo、別のメンテナンスドメインのインターフェースにより、「便利に」一般的なメソッドにデフォルトを追加することで壊れています。

デフォルトはデフォルトです。何もない(階層のどこにでもある)インターフェースにデフォルトを追加しても、具象実装クラスのセマンティクスには影響しません。しかし、デフォルトがObjectメソッドを「オーバーライド」できる場合、それは真実ではありません。

したがって、無害な機能のように見えますが、実際には非常に有害です。少しずつ表現するために複雑さが増し、個別にコンパイルされたインターフェースへの意図的で無害に見える変更を弱体化するのが非常に簡単になります。クラス実装の意図されたセマンティクス


13
これを説明するのに時間を割いていただき、ありがとうございます。考慮されたすべての要因に感謝します。これはhashCodeand にとって危険であることに同意しますが、にとってequals非常に役立つと思いますtoString。たとえば、一部のDisplayableインターフェイスはString display()メソッドを定義する場合があり、基本クラスの実装または拡張をすべての人に要求する代わりdefault String toString() { return display(); }Displayable、で定義できるように大量のボイラープレートを節約できます。DisplayabletoString()DisplayableToString
ブランドン

8
@ブランドンtoString()の継承を許可することは、equals()およびhashCode()の場合と同じように危険ではないことは間違いありません。一方で、この機能はさらに不規則になります-この1つの方法のために、継承ルールに関して同じ追加の複雑さが発生します...私たちが行ったところに線をきれいに描く方が良いようです。
Brian Goetz 2014年

5
@gexicide toString()がインターフェースメソッドのみに基づいている場合、インターフェースに何かを追加し、各サブクラスでdefault String toStringImpl()オーバーライドtoString()してインターフェース実装を呼び出すことができます-少し醜いが、機能し、何もないよりはましです。それを行うには:)もう一つの方法は何かなどを作ることですObjects.hash()Arrays.deepEquals()Arrays.deepToString()。@BrianGoetzの回答を+1しました!
Siu Ching Pong -Asuka Kenji-

3
ラムダのtoString()のデフォルトの動作は本当に不愉快です。ラムダファクトリは非常にシンプルで高速になるように設計されていますが、派生したクラス名を吐き出すことは本当に役に立ちません。持つdefault toString()機能的なインターフェースでオーバーライドすることは--at least--行う関数のシグネチャと実装の親クラス吐き出すようなものを私たちにできるようになります。さらに良いことに、いくつかの再帰的なtoString戦略をもたらすことができれば、クロージャーを通り抜けてラムダの非常に優れた説明を取得できるため、ラムダの学習曲線が大幅に改善されます。
Groostav

クラス、サブクラス、インスタンスメンバーのtoStringを変更すると、クラスまたはクラスのユーザーの実装に影響が及ぶ可能性があります。さらに、デフォルトのメソッドを変更すると、すべての実装クラスにも影響が及ぶ可能性があります。では、インターフェイスの動作を変更する誰かに関して、toString、hashCodeの何が特別なのでしょうか。クラスが別のクラスを拡張する場合、それらも変更できます。または、委任パターンを使用している場合。Java 8インターフェースを使用している人は、アップグレードする必要があります。サブクラスで抑制できる警告/エラーが提供されている可能性があります。
mmm '18

30

のメソッドのインターフェースでデフォルトのメソッドを定義することは禁止されています。デフォルトのメソッドはjava.lang.Object「到達可能」にはならないためです。

デフォルトのインターフェースメソッドは、インターフェースを実装するクラスで上書きでき、メソッドがスーパークラスで実装されている場合でも、メソッドのクラス実装はインターフェース実装よりも優先されます。すべてのクラスはから継承するためjava.lang.Object、のメソッドjava.lang.Objectはインターフェースのデフォルトメソッドよりも優先され、代わりに呼び出されます。

OracleのBrian Goetzが、このメーリングリストの投稿で、設計の決定に関する詳細をいくつか提供しています。


3

私はJava言語の著者の頭には触れていませんので、推測するだけかもしれません。しかし、私は多くの理由を見て、この問題でそれらに完全に同意します。

デフォルトのメソッドを導入する主な理由は、古い実装の下位互換性を損なうことなく、新しいメソッドをインターフェースに追加できることです。デフォルトのメソッドは、各実装クラスでそれらを定義する必要なしに「便利な」メソッドを提供するためにも使用できます。

これらはいずれも、ObjectのtoStringおよびその他のメソッドには適用されません。簡単に言えば、デフォルトのメソッドはデフォルトを提供するように設計されていますは、他の定義がない動作。他の既存の実装と「競合する」実装を提供しないこと。

「基本クラスは常に勝つ」というルールには、確かな理由もあります。クラスは実際の実装を定義しているのに対し、インターフェースはデフォルトの実装を定義しているが、これはやや弱いものです。

また、一般的なルールに例外を導入すると、不必要に複雑になり、他の疑問が生じます。オブジェクトは(多かれ少なかれ)他のクラスと同じなので、なぜ異なる動作をする必要があるのでしょうか。

何よりも、あなたが提案する解決策は、おそらく長所より短所をもたらすでしょう。


私が投稿したとき、私はgexicideの回答の2番目の段落に気づきませんでした。問題を詳細に説明するリンクが含まれています。
マーウィン、2014年

1

理由は非常に単純です。これは、ObjectがすべてのJavaクラスの基本クラスであるためです。したがって、一部のインターフェイスでObjectのメソッドがデフォルトメソッドとして定義されている場合でも、Objectのメソッドが常に使用されるため、それは役に立ちません。そのため、混乱を避けるために、Objectクラスのメソッドをオーバーライドするデフォルトのメソッドは使用できません。


1

非常に教訓的な答えを与えるために、からのパブリックメソッドのdefaultメソッドを定義することは禁止されています。検討すべき11の方法がありますが、この質問に答えるには3つの方法に分類できます。java.lang.Object

  1. 六つObjectの方法は持つことができないdefault彼らがためのメソッドをfinal:し、すべてで上書きすることができないgetClass()notify()notifyAll()wait()wait(long)、とwait(long, int)
  2. 三つObjectの方法が持つことはできませんdefault:ブライアン・ゲッツにより上記の理由のための方法をequals(Object)hashCode()toString()
  3. 二つObjectの方法ができる必要がありdefault、このようなデフォルトの値が最高の状態で疑わしいですが、方法を:clone()finalize()

    public class Main {
        public static void main(String... args) {
            new FOO().clone();
            new FOO().finalize();
        }
    
        interface ClonerFinalizer {
            default Object clone() {System.out.println("default clone"); return this;}
            default void finalize() {System.out.println("default finalize");}
        }
    
        static class FOO implements ClonerFinalizer {
            @Override
            public Object clone() {
                return ClonerFinalizer.super.clone();
            }
            @Override
            public void finalize() {
                ClonerFinalizer.super.finalize();
            }
        }
    }

。ポイントは何ですか?それでもWHYの部分には答えませんでした-「それでは、Javaデザイナーが、Objectからのメソッドをオーバーライドするデフォルトのメソッドを許可しないことにした理由は何ですか?」
pro_cheats 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.