24

オーバーライドとオーバーロードの構文上の違いは知っています。また、オーバーライドは実行時のポリモーフィズムであり、オーバーロードはコンパイル時のポリモーフィズムであることも知っています。しかし、私の質問は次のとおりです。「オーバーロードは本当にコンパイル時のポリモーフィズムですか?メソッド呼び出しは本当にコンパイル時に解決しますか?」. 私の主張を明確にするために、クラスの例を考えてみましょう。

public class Greeter {
    public void greetMe() {
        System.out.println("Hello");
    }

    public void greetMe(String name) {
        System.out.println("Hello " + name);
    }

    public void wishLuck() {
        System.out.println("Good Luck");
    }
}

すべてのメソッドgreetMe(), greetMe(String name), wishLuck()が公開されているため、すべてオーバーライドできます (オーバーロードされたものを含む)。例えば、

public class FancyGreeter extends Greeter {
    public void greetMe() {
        System.out.println("***********");
        System.out.println("*  Hello  *");
        System.out.println("***********");
    }
}

ここで、次のスニペットを検討してください。

Greeter greeter = GreeterFactory.getRandomGreeter();
greeter.greetMe();

このgetRandomGreeter()メソッドはランダムGreeterオブジェクトを返します。のオブジェクト、または のようなそのサブクラスのいずれかを返すことができGreeterます。クラスファイルを使用するか動的にロードしてオブジェクトを作成し、リフレクション(リフレクションで可能だと思います)またはその他の可能な方法を使用してオブジェクトを作成します。これらのメソッドはすべて、サブクラスでオーバーライドされる場合とされない場合があります。そのため、コンパイラは特定のメソッド (オーバーロードされているかどうか) がオーバーライドされているかどうかを知る方法がありません。右?また、ウィキペディアは仮想機能について次のように述べています。FancyGreeterGraphicalGreetergetRandomGreeter()newGreeter

Java では、すべての非静的メソッドはデフォルトで「仮想関数」です。オーバーライドできないキーワード final でマークされたメソッドと、継承されないプライベート メソッドのみが非仮想です。

仮想関数は動的メソッド ディスパッチを使用して実行時に解決され、すべての非プライベート、非最終メソッドは (オーバーロードされているかどうかにかかわらず) 仮想であるため、実行時に解決する必要があります。右?

では、コンパイル時にオーバーロードを解決するにはどうすればよいでしょうか? または、私が誤解していること、または見逃していることはありますか?

4

5 に答える 5

15

void greetMe()すべての「Greeter」クラスには、、、、およびの3つの仮想メソッドvoid greetMe(String)がありvoid wishLuck()ます。

コンパイラを呼び出すとgreeter.greetMe()、3つの仮想メソッドのどれをメソッドシグネチャから呼び出す必要があるかを判断できます。void greetMe()引数を受け入れないためです。メソッドのどの特定の実装void greetMe()が呼び出されるかは、greeterインスタンスのタイプによって異なり、実行時に解決されます。

あなたの例では、メソッドのシグネチャはすべて完全に異なるため、コンパイラがどのメソッドを呼び出すかを決めるのは簡単です。「コンパイル時のポリモーフィズム」の概念を示すためのもう少し良い例は、次のとおりです。

class Greeter {
    public void greetMe(Object obj) {
        System.out.println("Hello Object!");
    }

    public void greetMe(String str) {
        System.out.println("Hello String!");
    }
}

このグリータークラスを使用すると、次の結果が得られます。

Object obj = new Object();
String str = "blah";
Object strAsObj = str;

greeter.greetMe(obj); // prints "Hello Object!"
greeter.greetMe(str); // prints "Hello String!"
greeter.greetMe(strAsObj); // prints "Hello Object!"

コンパイラは、コンパイル時の型を使用して最も具体的に一致するメソッドを選択します。これが、2番目の例が機能してvoid greetMe(String)メソッドを呼び出す理由です。

最後の呼び出しは最も興味深いものです。実行時型のstrAsObjはですがString、それはとしてキャストされているObjectため、コンパイラーはそれを認識します。したがって、コンパイラがその呼び出しに対して見つけることができる最も近い一致はvoid greetMe(Object)メソッドです。

于 2011-12-02T12:37:06.513 に答える
13

オーバーロードされたメソッドは、必要に応じてオーバーライドできます。

オーバーロードされたメソッドは、同じ名前を共有していても、異なるファミリーのようなものです。コンパイラは、シグネチャを指定して 1 つのファミリを静的に選択し、実行時にクラス階層内の最も具体的なメソッドにディスパッチします。

つまり、メソッドのディスパッチは 2 つのステップで実行されます。

  • 1 つ目は、利用可能な静的情報を使用してコンパイル時に行われます。コンパイラはcall、メソッドが呼び出されるオブジェクトの宣言された型のオーバーロードされたメソッドのリストの中で、現在のメソッド パラメーターに最も一致するシグネチャの を発行します。
  • 2 番目のステップは実行時に実行され、呼び出す必要のあるメソッド シグネチャが与えられます (前のステップ、覚えていますか?)。JVM は、レシーバー オブジェクトの実際の型で最も具体的なオーバーライドされたバージョンにそれをディスパッチします。

メソッド引数の型がまったく共変でない場合、オーバーロードは、コンパイル時にメソッド名をマングルするのと同じです。これらは事実上異なるメソッドであるため、JVM はレシーバーのタイプに応じてそれらを交換してディスパッチすることは決してありません。

于 2011-12-02T11:54:54.447 に答える
3

この点でのオーバーロードとは、動的ディスパッチではなく、関数の型がコンパイル時に静的に決定されることを意味します。

バックグラウンドで実際に行われているのは、タイプが「A」および「B」の「foo」という名前のメソッドに対して、2 つのメソッド (「foo_A」および「foo_B」) が作成されることです。どちらが呼び出されるかは、コンパイル時に決定されます (foo((A) object)またはfoo((B) object)、 orfoo_Aが呼び出されfoo_Bます)。したがって、これはある意味ではコンパイル時のポリモーフィズムですが、実際のメソッド (つまり、クラス階層でどの実装を採用するか) は実行時に決定されます。

于 2011-12-02T12:04:36.027 に答える