Java動的バインディングとメソッドのオーバーライド


89

昨日、2時間の技術的な電話インタビュー(私は合格しました、woohoo!)を行いましたが、Javaの動的バインディングに関する次の質問を完全に覆い隠しました。そして、数年前に私がTAだったときにこの概念を学部生に教えていたので、それは二重に困惑しているので、彼らに誤った情報を与えた見込みは少し不安です...

ここに私が与えられた問題があります:

/* What is the output of the following program? */

public class Test {

  public boolean equals( Test other ) {
    System.out.println( "Inside of Test.equals" );
    return false;
  }

  public static void main( String [] args ) {
    Object t1 = new Test();
    Object t2 = new Test();
    Test t3 = new Test();
    Object o1 = new Object();

    int count = 0;
    System.out.println( count++ );// prints 0
    t1.equals( t2 ) ;
    System.out.println( count++ );// prints 1
    t1.equals( t3 );
    System.out.println( count++ );// prints 2
    t3.equals( o1 );
    System.out.println( count++ );// prints 3
    t3.equals(t3);
    System.out.println( count++ );// prints 4
    t3.equals(t2);
  }
}

出力は、オーバーライドされたequals()メソッド内からの2つの別個の印刷ステートメントであるはずだと断言しました:at t1.equals(t3)t3.equals(t3)。後者のケースは十分明白であり、前者のケースでt1は、Object型の参照があっても、それはTest型としてインスタンス化されるため、動的バインディングはオーバーライドされた形式のメソッドを呼び出す必要があります。

どうやらそうではない。私のインタビュアーは私にプログラムを自分で実行するように勧めました、そして見れば、オーバーライドされたメソッドからの単一の出力しかありませんでした:行でt3.equals(t3)

私の質問は、なぜですか?すでに述べたように、t1Object型の参照であっても(静的バインディングはObjectのequals()メソッドを呼び出す)、動的バインディングインスタンス化された参照の型に基づいて、メソッドの最も具体的なバージョンの呼び出しを処理する必要があります。何が欠けていますか?


親切にこの回答への私の投稿を見つけてください。追加のケースで説明するために最善を尽くしています。私はあなたの入力を本当に感謝します:)
Devendra Lattu 2017

回答:


81

Javaは、オーバーロードされたメソッドには静的バインディングを使用し、オーバーライドされたメソッドには動的バインディングを使用します。あなたの例では、equalsメソッドはオーバーロードされている(Object.equals()とは異なるパラメータータイプを持っている)ので、呼び出されたメソッドはコンパイル時に参照タイプにバインドされます。

ここでいくつかの議論

equalsメソッドであるという事実はあまり重要ではありませんが、面接での問題への回答に基づいてすでに知っている、オーバーライドではなくオーバーロードすることはよくある間違いです。

編集:ここに良い説明。この例は、代わりにパラメータタイプに関連する同様の問題を示していますが、同じ問題が原因です。

バインディングが実際に動的である場合、呼び出し元とパラメーターがTestのインスタンスである場合は、オーバーライドされたメソッドが呼び出されると思います。したがって、t3.equals(o1)は、印刷されない唯一のケースになります。


多くの人々はそれがオーバーロードされていてオーバーライドされていないと指摘しますが、それでもあなたはそれがオーバーロードされたものを正しく解決すると期待するでしょう。あなたの投稿は、私が知る限り、実際に質問に正しく答える唯一の投稿です。
ビルK

4
私の間違いは、メソッドがオーバーライドされているのではなく、実際にオーバーロードされているという事実を完全に見落としていました。「equals()」を見て、すぐに継承されてオーバーライドされると思いました。繰り返しますが、私はより広範で難しい概念を正しく理解しているようですが、単純な詳細を台無しにしています。:P
Magsol

14
@Overrideアノテーションが存在するもう1つの理由。
Matt

1
私の後に繰り返します:「Javaはオーバーロードされたメソッドに静的バインディングを使用し、オーバーライドされたメソッドに動的バインディングを使用します」-+1
Mr_and_Mrs_D 2013年

1
卒業して卒業しました。ありがとう!
Atieh 2014

25

equalsメソッドはTest、のequalsメソッドをオーバーライドしませんjava.lang.Object。パラメータタイプを見てください。Testクラスは、オーバーロードしているequalsAを受け入れる方法でTest

equalsメソッドがオーバーライドする場合は、@ Overrideアノテーションを使用する必要があります。これにより、この一般的な間違いを指摘するコンパイルエラーが発生します。


ええ、なぜ私がその単純で重要な詳細を見逃したのかはよくわかりませんが、それがまさに私の問題でした。ありがとうございました!
Magsol-2008年

質問者の好奇心が強い結果に対する真の答えであるための+1
matt b

親切にこの回答への私の投稿を見つけてください。追加のケースで説明するために最善を尽くしています。私はあなたの入力を本当に感謝します:)
Devendra Lattu 2017

6

興味深いことに、Groovyコード(クラスファイルにコンパイルできる)では、1つの呼び出しを除くすべての呼び出しがprintステートメントを実行します。(TestとObjectを比較するものは、明らかにTest.equals(Test)関数を呼び出しません。)これは、groovyが完全に動的な型指定を行うためです。明示的に動的に型付けされる変数がないため、これは特に重要です。プログラマーがgroovyがJavaのことをすることを期待しているので、これは有害であると考えられていることをいくつか読んだことがあります。


1
残念ながら、すべてのメソッド呼び出しはリフレクションを使用するため、Groovyがその代償を払うと、パフォーマンスが大幅に低下します。ある言語が別の言語とまったく同じように機能することを期待することは、一般的に有害であると考えられています。違いを認識する必要があります。
ヨアヒムザウアー

JDK7のinvokedynamicを使用して(または、今日同様の実装手法を使用しても)、素晴らしく高速である必要があります。
トム・ホーティン-タックライン2008年

5

Javaはパラメーターの共分散をサポートせず、戻り値の型のみをサポートします。

言い換えると、オーバーライドするメソッドでの戻り値の型は、オーバーライドされるもののサブタイプである可能性がありますが、パラメーターには当てはまりません。

Object内のequalsのパラメーターがObjectである場合、サブクラスに他の何かとequalsを置くと、オーバーライドされたメソッドではなく、オーバーロードされます。したがって、そのメソッドが呼び出される唯一の状況は、T3の場合のように、パラメーターの静的タイプがTestの場合です。

就職の面接プロセスで頑張ってください!生徒に教える通常のアルゴ/データ構造の質問の代わりに、この種の質問をする会社でインタビューを受けたいです。


1
反変パラメーターを意味します。
トム・ホーティン-2008年

異なるメソッドパラメータが、オーバーライドされたメソッドではなく、本質的にオーバーロードされたメソッドを作成するという事実を、私はどういうわけか完全に失明しました。心配しないで、アルゴ/データ構造の質問もありました。:Pそして幸運をありがとう、私はそれが必要になります!:)
Magsol 2008年

4

重要なのは、equals()メソッドが標準に準拠していないという事実にあると思います。Objectオブジェクトではなく、別のTestオブジェクトを取り込むため、equals()メソッドをオーバーライドしていません。これは、実際には、オブジェクトオブジェクトにObject.equals(Object o)を呼び出すときに、Testオブジェクトが与えられたときに、それをオーバーロードして特別な何かを行うだけのことです。IDEを介してそのコードを見ると、Testの2つのequals()メソッドが表示されます。


これと、ほとんどの回答には要点がありません。問題は、オーバーライドの代わりにオーバーロードが使用されているという事実ではありません。これが、t1がObjectとして宣言されているがTestに初期化されている場合、t1.equals(t3)にオーバーロードされたメソッドが使用されない理由です。
ロビン

4

メソッドはオーバーライドされる代わりにオーバーロードされます。Equalsは常にパラメーターとしてObjectを取ります。

ところで、Blochの有効なJavaにこれに関するアイテムがあります(所有する必要があります)。


ジョシュア・ブロックの効果的なJava?
DJクレイワース2008年

効果的なうん、入力中に他のことを考えていた:D
Gilles

4

しばらく検索した後の動的バインディング(DD)と静的バインディング̣̣̣(SB)の注意事項:

1.タイミング実行:(Ref.1)

  • DB:実行時
  • SB:コンパイラー時間

2.使用

  • DB:オーバーライド
  • SB:オーバーロード(静的、プライベート、最終)(Ref.2)

参照:

  1. どちらの方法を使用することを好む平均リゾルバーを実行する
  2. static、private、またはfinalの修飾子でメソッドをオーバーライドできないため
  3. http://javarevisited.blogspot.com/2012/03/what-is-static-and-dynamic-binding-in.html

2

オーバーロードする代わりにオーバーライドする別のメソッドを追加すると、実行時の動的バインディング呼び出しが説明されます。

/ *次のプログラムの出力は何ですか?* /

public class DynamicBinding {
    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside @override: this is dynamic binding");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++);// prints 0
        t1.equals(t2);
        System.out.println(count++);// prints 1
        t1.equals(t3);
        System.out.println(count++);// prints 2
        t3.equals(o1);
        System.out.println(count++);// prints 3
        t3.equals(t3);
        System.out.println(count++);// prints 4
        t3.equals(t2);
    }
}


0

「なぜ?」という質問への答え それがJava言語が定義されている方法です。

共分散と反変に関するウィキペディアの記事を引用するには:

戻り型共分散は、Javaプログラミング言語バージョンJ2SE 5.0で実装されています。メソッドのオーバーライドでは、パラメーターの型はまったく同じ(不変)である必要があります。そうでない場合、メソッドは代わりに並列定義でオーバーロードされます。

他の言語は異なります。


私の問題は、3 + 3を見て9を書き、次に1 + 1を見て2を書くこととほぼ同じでした。Java言語の定義方法を理解しています。この場合、何らかの理由で、同じ問題の他の場所でその間違いを回避したとしても、メソッドを完全に誤ったものと間違えました。
Magsol-2008年

0

ここでオーバーライドの概念がないことは非常に明確です。メソッドのオーバーロードです。Object()ObjectクラスのメソッドはObject型equal()の参照のパラメーターを受け取り、このメソッドはTest型の参照のパラメーターを受け取ります。


-1

私はオンラインで出会ったいくつかの例の拡張バージョンである2つの例を通してこれを説明しようと思います。

public class Test {

    public boolean equals(Test other) {
        System.out.println("Inside of Test.equals");
        return false;
    }

    @Override
    public boolean equals(Object other) {
        System.out.println("Inside of Test.equals ot type Object");
        return false;
    }

    public static void main(String[] args) {
        Object t1 = new Test();
        Object t2 = new Test();
        Test t3 = new Test();
        Object o1 = new Object();

        int count = 0;
        System.out.println(count++); // prints 0
        o1.equals(t2);

        System.out.println("\n" + count++); // prints 1
        o1.equals(t3);

        System.out.println("\n" + count++);// prints 2
        t1.equals(t2);

        System.out.println("\n" + count++);// prints 3
        t1.equals(t3);

        System.out.println("\n" + count++);// prints 4
        t3.equals(o1);

        System.out.println("\n" + count++);// prints 5
        t3.equals(t3);

        System.out.println("\n" + count++);// prints 6
        t3.equals(t2);
    }
}

ここでは、カウント値が0、1、2、3の行の場合。我々は、参照オブジェクトのためのO1T1equals()方法を。したがって、コンパイル時にequals()Object.classファイルのメソッドがバインドされます。

しかし、にもかかわらず、参照t1があるオブジェクト、それが有する使う初期テストクラス
Object t1 = new Test();
したがって、実行時にpublic boolean equals(Object other)whichが呼び出されます

オーバーライドされたメソッド

ここに画像の説明を入力してください

これで、カウント値が4と6の場合、Testの参照初期化を持つt3がObject参照としてパラメーターを使用してメソッドを呼び出し、それがequals()

オーバーロードされたメソッド

OK!

繰り返しますが、コンパイラーが呼び出すメソッドをよりよく理解するには、メソッドをクリックするだけで、Eclipseはコンパイル時に呼び出すと思われる類似のタイプのメソッドを強調表示します。コンパイル時に呼び出されない場合、それらのメソッドはメソッドのオーバーライドの例です。

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.