8

このコードを考えてみましょう (完全なクラス、正常に実行されます。簡潔にするために、すべてのクラスを 1 つのクラスにまとめています)。

私の質問は、コード リストの後にあります。

import java.util.LinkedList;
import java.util.List;

class Gadget {
    public void switchon() {
        System.out.println("Gadget is Switching on!");
    }
}

interface switchonable {
    void switchon();
}

class Smartphone extends Gadget implements switchonable {
    @Override
    public void switchon() {
        System.out.println("Smartphone is switching on!");
    }
}

class DemoPersonnel {
    public void demo(Gadget g) {
        System.out.println("Demoing a gadget");
    }

    public void demo(Smartphone s) {
        System.out.println("Demoing a smartphone");
    }
}

public class DT {

    /**
     * @param args
     */
    public static void main(String[] args) {
        List<Gadget> l = new LinkedList<Gadget>();
        l.add(new Gadget());
        l.add(new Smartphone());
        for (Gadget gadget : l) {
            gadget.switchon();
        }

        DemoPersonnel p = new DemoPersonnel();
        for (Gadget gadget : l) {
            p.demo(gadget);
        }
    }
}

質問:

  1. コンパイラの観点から、スマートフォンの switchon メソッドの起源は何ですか? 基底クラスのガジェットから継承されていますか? それとも、switchonable インターフェイスによって義務付けられている switchon メソッドの実装ですか? 注釈はここで違いを生みますか?

  2. メイン メソッドの最初のループ: ここでは、ランタイム ポリモーフィズムのケースが見られます。つまり、最初の for ループが実行され、gadget.switchon() が呼び出されると、最初に「Gadget iswitching on」が出力され、次に「スマートフォンの電源が入っています」と表示されます。しかし、2 番目のループでは、このランタイム解決は行われず、demo への両方の呼び出しの出力は「ガジェットのデモ中」ですが、最初の反復で「ガジェットのデモ中」と「スマートフォンのデモ中」が出力されることを期待していました。 2回目。

私は何を間違って理解していますか? ランタイムが最初の for ループで子クラスを解決するのに、2 番目の for ループでは解決しないのはなぜですか?

最後に、Java での実行時/コンパイル時のポリモーフィズムに関する明快なチュートリアルへのリンクを歓迎します。(Java チュートリアル トレイルのリンクを投稿しないでください。細かいニュアンスをかなり深く議論するときに、特に印象的な資料は見つかりませんでした)。

4

8 に答える 8

4

これは簡単にどのように機能するかです:
コンパイル時間

  • コンパイラは、要求されたメソッドに必要な署名を定義します
  • シグネチャが定義されると、コンパイラは型クラスでそれを探し始めます
  • 必要なシグネチャを持つ互換性のある候補メソッドが見つかった場合は続行し、そうでない場合はエラーを返します

ランタイム

  • 実行中、JVM は、コンパイル時に 正確に定義された署名を持つ候補メソッドを探し始めます。
  • 実行可能なメソッドの検索は、実際には実際のオブジェクト実装クラス (型クラスのサブクラスである可能性があります) から開始され、階層全体をサーフアップします。

リストはタイプ Gadget で定義されています。

for (Gadget gadget : l) {
        gadget.switchon();
    }

あなたが要求すると、コンパイラはGadget クラスgadget.switchon();のメソッドを探します。そこにあるので、署名の候補は単純に であることが確認されます。switchon()switchon()

実行中、JVM はスマートフォン クラスからswitchon()メソッドを探します。これが正しいメッセージを表示する理由です。

2 番目の for ループで何が起こるかを次に示します。

DemoPersonnel p = new DemoPersonnel();
    for (Gadget gadget : l) {
        p.demo(gadget);
    }

この場合のシグネチャは両方のオブジェクトに対するものですdemo(Gadget g)。これが、両方の反復メソッドに対して実行される理由ですdemo(Gadget g)

それが役に立てば幸い!

于 2013-07-13T22:20:06.573 に答える
1

コンパイラの観点から、スマートフォンの switchon メソッドの起源は何ですか? 基底クラスのガジェットから継承されていますか? それとも、switchonable インターフェイスによって義務付けられている switchon メソッドの実装ですか?

2番目のケース

注釈はここで違いを生みますか?

まったくそうではありません。@Overrideは単なるヘルパーです。これを使用すると、コンパイラに次のように伝えます。

2 番目の質問については、この場合、署名に従ってより適切に一致するメソッドが呼び出されます。2 番目のループでの実行時に、オブジェクトは「関連付けられた」スーパータイプを持ちます。これが、public void demo(Smartphone g)ではなくpublic void demo(Gadget g)が呼び出される理由です。

于 2013-07-13T22:04:28.273 に答える
0

コンパイラは、メソッドの "this" ポインターの宣言された型とパラメーターの宣言された型に基づいて、メソッド シグネチャを選択します。したがって、switchonGadget の「this」ポインターを受け取るので、それは、コンパイラーが生成されたコードで参照するメソッドのバージョンです。もちろん、実行時のポリモーフィズムはそれを変えることができます。

ただし、ランタイム ポリモーフィズムはメソッドの "this" ポインターにのみ適用され、parms には適用されないため、2 番目のケースでは、コンパイラによるメソッド シグネチャの選択が "支配" します。

于 2013-07-13T22:20:39.720 に答える
0

最初に質問 2 に答えます。2 番目のループでは、ガジェットとして型指定されたオブジェクトを渡します。したがって、デモ クラスで最も一致するのは、ガジェットを取得するメソッドです。これはコンパイル時に解決されます。

質問 1 の場合: 注釈は違いを生みません。インターフェイスでメソッドをオーバーライド(実装)していることを示しているだけです。

于 2013-07-13T22:10:24.187 に答える
0
  1. コンパイラの場合、Smartphone は Gadget から switchon() メソッドの「実装」を継承し、継承された実装を独自の実装でオーバーライドします。一方、switchonable インターフェイスは、switchon() メソッド定義の実装を提供するようにスマートフォンに指示し、これはスマートフォンでオーバーライドされた実装によって実現されました。

  2. 最初のケースは期待どおりに機能しています。これは実際にはポリモーフィズムのケースであるためです。つまり、1 つのコントラクトと 2 つの実装 (1 つはガジェットに、もう 1 つはスマートフォン) があります。後で以前の実装を「オーバーライド」しました。2 番目のケースは、コントラクトと実装が 1 つしかないため、期待どおりに機能しないはずです。demo() メソッドを「オーバーライド」していないことに注意してください。実際には、demo() メソッドを「オーバーロード」しています。また、オーバーロードとは、「同じ名前」のみを共有する 2 つの「異なる」一意のメソッド定義を意味します。したがって、Gadget パラメーターを指定して demo() を呼び出すと、1 つのコントラクトと 1 つの実装の場合になります。これは、コンパイラーがメソッド名を正確なメソッド パラメーター タイプと照合し、そうすることで「異なるメソッド」を呼び出すためです。

于 2013-07-13T22:11:34.243 に答える
0

2 番目の質問について: Java では、動的メソッドのディスパッチは、メソッドが呼び出されるオブジェクトに対してのみ発生し、オーバーロードされたメソッドのパラメーターの型に対しては発生しません。

ここに Java 言語仕様へのリンクがあります。

それが言うように:

メソッドが呼び出されると (§15.12)、呼び出されるメソッドのシグネチャを決定するために、コンパイル時に実際の引数 (および明示的な型引数) の数と引数のコンパイル時の型が使用されます ( §15.12.2)。呼び出されるメソッドがインスタンス メソッドである場合、呼び出される実際のメソッドは、動的メソッド ルックアップ (§15.12.4) を使用して、実行時に決定されます。

基本的に、メソッドパラメータのコンパイル時の型は、呼び出されるメソッドのシグネチャを決定するために使用されます

実行時に、メソッドが呼び出されるオブジェクトのクラスは、メソッドをオーバーライドする宣言された型のサブクラスのインスタンスである可能性があることを考慮して、そのメソッドのどの実装が呼び出されるかを決定します。

あなたの場合、 new child(); によって子クラスのオブジェクトを作成するとき。オーバーロードされたメソッドに渡すと、スーパークラスの型が関連付けられています。したがって、親のオブジェクトでオーバーロードされたメソッドが呼び出されます。

于 2013-07-13T22:16:33.217 に答える