18

以下のコードでは、1番目と2番目のprintステートメントはどのようにSubObjを出力しますか?トップとサブは同じサブクラスを指していますか?

class Top {
    public String f(Object o) {return "Top";}
}

class Sub extends Top {
    public String f(String s) {return "Sub";}
    public String f(Object o) {return "SubObj";}
}

public class Test {
    public static void main(String[] args) {  
        Sub sub = new Sub();
        Top top = sub;
        String str = "Something";
        Object obj = str;


        System.out.println(top.f(obj));
        System.out.println(top.f(str));
        System.out.println(sub.f(obj));
        System.out.println(sub.f(str));
    }
}

上記のコードは以下の結果を返します。

SubObj
SubObj
SubObj
Sub
4

6 に答える 6

15

ケース1、3、4はすでに理解しているので、ケース2に取り組みましょう。

(注意-私はJVMまたはコンパイラの内部動作の専門家ではありませんが、これが私が理解している方法です。これを読んでいる人がJVMの専門家である場合は、不一致があればこの回答を自由に編集してください。 。)

同じ名前で署名が異なるサブクラスのメソッドは、メソッドのオーバーロードと呼ばれます。メソッドのオーバーロードは静的バインディングを使用します。これは基本的に、コンパイル時に適切なメソッドが強制的に「選択」(つまりバインド)されることを意味します。コンパイラーは、オブジェクトの実行時タイプ(別名実際のタイプ)についての手がかりを持っていません。だからあなたが書くとき:

                         // Reference Type  // Actual Type
    Sub sub = new Sub(); // Sub                Sub
    Top top = sub;       // Top                Sub

コンパイラは、topがTop型(別名参照型)であることを「認識」しているだけです。したがって、後で書くとき:

    System.out.println(top.f(str)); // Prints "subobj"

コンパイラは、呼び出し'top.f'をTopクラスのfメソッドを参照しているものとして「認識」します。strがObjectを拡張するString型であることを「知っています」。したがって、1)呼び出し'top.f'はトップクラスのfメソッドを参照するため、2)クラスTopにはStringパラメータを受け取るfメソッドがないため、3)strはObjectのサブクラスであるため、トップクラスのfメソッドです。コンパイル時に有効な唯一の選択肢です。したがって、コンパイラはstrをその親型であるObjectに暗黙的にアップキャストするため、Topのfメソッドに渡すことができます。(これは、上記のコード行の型解決が実行時まで延期され、コンパイラーではなくJVMによって解決される動的バインディングとは対照的です。)

次に、実行時に、上記のコード行で、topがJVMによって実際のタイプsubにダウンキャストされます。ただし、引数strは、コンパイラによってObject型にアップキャストされています。したがって、JVMは、Object型のパラメータを受け取るsubクラスのfメソッドを呼び出す必要があります。

したがって、上記のコード行は「sub」ではなく「subobj」を出力します。

別の非常によく似た例については、 Javaの動的バインディングとメソッドのオーバーライドを参照してください。

更新:JVMの内部動作に関するこの詳細な記事を見つけました:

http://www.artima.com/underthehood/invocationP.html

何が起こっているのかをより明確にするために、コードにコメントを付けました。

class Top {
    public String f(Object o) {return "Top";}
}

class Sub extends Top {
    public String f(String s) {return "Sub";} // Overloading = No dynamic binding
    public String f(Object o) {return "SubObj";} // Overriding = Dynamic binding
}

public class Test {
    public static void main(String[] args) {  

                                  // Reference Type     Actual Type
        Sub sub = new Sub();      // Sub                Sub
        Top top = sub;            // Top                Sub
        String str = "Something"; // String             String
        Object obj = str;         // Object             String

                                        // At Compile-Time:      At Run-Time:
        // Dynamic Binding
        System.out.println(top.f(obj)); // Top.f (Object)   -->  Sub.f (Object)

        // Dynamic Binding
        System.out.println(top.f(str)); // Top.f (Object)   -->  Sub.f (Object)

        // Static Binding
        System.out.println(sub.f(obj)); // Sub.f (Object)        Sub.f (Object)

        // Static Binding
        System.out.println(sub.f(str)); // Sub.f (String)        Sub.f (String)
    }
}
于 2011-04-25T00:45:24.857 に答える
8

これは、Javaのすべてのメソッド呼び出しが仮想であるためです(デフォルト)。

つまり、解決は(のタイプではなく)実際のオブジェクトから始まり、最初の一致するメソッドが見つかるまで(実際のオブジェクトのタイプごとに)継承チェーンを「処理」します。非仮想メソッドは、式のタイプから始まります。(メソッドを非仮想としてマークします。)final

ただし、正確なメソッドシグネチャはコンパイル時に決定されます(Javaはマルチディスパッチをサポートしていません。シングルディスパッチは、レシーバーオブジェクトに基づいて実行時にのみ変化します)。これはSub.f(String)、たとえば、「Sub」になる理由を説明しています。 Topのサブタイプで呼び出された場合でも、Top.f(String)一致するメソッドに「バインド」します。Top.f(Object)(これは、コンパイル時に決定された最も適格な署名でした)。仮想ディスパッチ自体は同じです。

ハッピーコーディング。

于 2011-04-14T04:37:43.590 に答える
2

これは、オブジェクトの見かけのタイプと関係があります。コンパイル時に、Javaは、インスタンス化する特定の型ではなく、オブジェクトを宣言する型に基づいて型チェックを実行します。

メソッドf(Object)を持つタイプTopがあります。だからあなたが言うとき:

 System.out.println(top.f(obj));

Javaコンパイラは、オブジェクトtopがTop型であることのみを考慮し、使用可能な唯一のメソッドはObjectをパラメータとして受け取ります。次に、実行時に、実際にインスタンス化されたオブジェクトのf(Object)メソッドを呼び出します。

次の呼び出しも同じように解釈されます。

次の2つの呼び出しは、予想どおりに解釈されます。

于 2011-04-14T04:39:25.010 に答える
1

Subはい、どちらもクラスを指しています。問題は、それtopについてしか知らないということです

f(Object o)

そしてそれはその署名だけを呼び出すことができます。

ただしsub、両方のシグニチャを認識しており、パラメータタイプで選択する必要があります。

于 2011-04-14T04:36:09.627 に答える
1

継承では、基本クラスオブジェクトは派生クラスのインスタンスを参照できます。

これがTop top = sub;うまく機能する方法です。

  1. の場合System.out.println(top.f(obj));

    topオブジェクトは、クラスのf()メソッドを使用しようとします。Subクラスに2つのf()メソッドがあるSubので、渡された引数に対して型チェックが行われます。タイプはクラスObjectの2番目f()のメソッドであるため、Sub呼び出されます。

  2. の場合System.out.println(top.f(str));

    (1)と同じように解釈できます。つまり、タイプはString最初のf()関数が呼び出されるようになります。

  3. の場合System.out.println(sub.f(obj));

    Subクラス自体のメソッドを呼び出すので、これは簡単です。クラスには2つのオーバーロードされたメソッドがあるためSub、ここでも、渡された引数の型チェックが行われます。渡される引数はタイプObjectであるため、2番目のf()メソッドが呼び出されます。

  4. の場合System.out.println(sub.f(str));

    3.と同様に、ここで渡される型は、クラスStringの最初のf()関数Subが呼び出されるようにするためのものです。

お役に立てれば。

于 2011-04-14T04:46:32.847 に答える
0

Sub sub = new Sub();
Top top = sub;

subのインスタンスを作成し、それをtopにアップキャストしました。これにより、topに存在するメソッドのみが認識されます。トップに存在するメソッドはパブリックですString f(Object o) {return "Top";}

また、そのメソッドはsubによってオーバーロードされるため、subのインスタンスを作成し、それをtopにアップキャストすると呼び出されます。

これを別の言い方をすれば、

subをtopに割り当てたため、sub typeは見かけのタイプですが、topは実際のタイプです。実際の型をオーバーロードする場合は、見かけの型のメソッドを呼び出しますが、実際の型に存在しないメソッドを呼び出すことはできません。

于 2011-04-14T04:39:52.000 に答える