5

最近、答えが見つからず、テスト コードを実行するための Java 環境がない 2 つの過負荷の質問に遭遇しました。Javaコンパイラがオーバーロードのために従うすべてのルールのリストを組み立てたり、既存のリストを代わりに指摘したりすることで、誰かが私を助けてくれることを願っています。

まず、2 つのメソッドが最後の varargs パラメーターだけが異なる場合、どのような状況でそれぞれが呼び出され、引数なしで varargs メソッドを呼び出すことができるでしょうか?

private void f(int a) { /* ... */ }
private void f(int a, int... b) { /* ... */ }

f(12); // calls the former? I would expect it to
f(12, (int[])null); // calls latter, but passes null for b? 
  // Can I force the compiler to call the second method in the same fashion
  // as would happen if the first method didn't exist?

2 番目の質問は、2 つのメソッドが呼び出される互いに継承された型によって異なる場合です。最も派生したバージョンが呼び出され、キャストが他のバージョンを呼び出すことができると期待しています。

interface A {}
class B implements A {}
class C implements A {}

private void f(A a) {}
private void f(B b) {}

f(new C()); // calls the first method
f(new B()); // calls the second method?
f((A)(new B()); // calls the first method using a B object?

これらは 2 つの例ですが、コード リーダーとして、コンパイラが何をしているかを確認するためにビルド環境をセットアップする時間がないことがよくあるため、これを解決するために使用される正確な順序付けられたルールの標準的なリストを好みます。

4

2 に答える 2

20

オーバーロードとオーバーライド

メソッドの正しい実装の選択は、ご指摘のとおり実行時に行われます。呼び出されるメソッドのシグネチャは、コンパイル時に決定されます。

コンパイル時のメソッド選択のオーバーロード

セクション 15.12メソッド呼び出し式Java 言語仕様(JLS)では、呼び出す正しいメソッドをコンパイラが選択するプロセスについて詳しく説明しています。

ここで、これがコンパイル時のタスクであることがわかります。JLS は、サブセクション 15.12.2 で次のように述べています。

このステップでは、メソッドの名前と引数式の型 を使用して、アクセス可能で適用可能なメソッドを見つけます。そのようなメソッドが複数存在する場合があります。その場合、最も具体的なものが選択されます。

通常、varargs メソッドは、他の候補メソッドと競合する場合、最後に選択されます。これは、同じパラメーター タイプを受け取るメソッドよりも具体的でないと見なされるためです。

これのコンパイル時の性質を確認するには、次のテストを実行できます。

このようなクラスを宣言してコンパイルします (つまりjavac ChooseMethod.java)。

public class ChooseMethod {
   public void doSomething(Number n){
    System.out.println("Number");
   }
}

最初のクラスのメソッドを呼び出す 2 番目のクラスを宣言し、それをコンパイルします (つまりjavac MethodChooser.java)。

public class MethodChooser {
   public static void main(String[] args) {
    ChooseMethod m = new ChooseMethod();
    m.doSomething(10);
   }
}

プログラムを実行すると (つまりjava MethodChooser)、出力は と表示されNumberます。

ここで、2 つ目のより具体的なオーバーロードメソッドをChooseMethodクラスに追加し、それを再コンパイルします (ただし、他のクラスは再コンパイルしないでください)。

public void doSomething(Integer i) {
 System.out.println("Integer");
}

メインを再度実行すると、出力はまだNumber.

基本的に、コンパイル時に決定されたためです。クラス (メインのクラス) を再コンパイルしMethodChooser、プログラムを再度実行すると、出力はInteger.

そのため、オーバーロードされたメソッドのいずれかを強制的に選択する場合は、実行時だけでなく、コンパイル時に引数の型がパラメーターの型に対応している必要があります。

実行時のメソッド選択のオーバーライド

繰り返しますが、メソッドのシグネチャはコンパイル時に決定されますが、実際の実装は実行時に決定されます。

このようなクラスを宣言してコンパイルします。

public class ChooseMethodA {
   public void doSomething(Number n){
    System.out.println("Number A");
   }
}

次に、2 番目の拡張クラスを宣言してコンパイルします。

public class ChooseMethodB extends ChooseMethodA {  }

MethodChooser クラスでは、次のようにします。

public class MethodChooser {
    public static void main(String[] args) {
        ChooseMethodA m = new ChooseMethodB();
        m.doSomething(10);
    }
}

そして、それを実行すると、出力が得られますNumber A。メソッドは でオーバーライドされていないChooseMethodBため、呼び出される実装は の実装であるため、これで問題ありませんChooseMethodA

次に、オーバーライドされたメソッドを に追加しますMethodChooserB

public void doSomething(Number n){
    System.out.println("Number B");
}

そして、これだけを再コンパイルして、メイン メソッドを再度実行します。

これで、出力が得られますNumber B

MethodChooserそのため、実装は実行時に選択され、クラスの再コンパイルは必要ありませんでした。

于 2012-06-05T16:43:47.310 に答える
1

最初の質問:

あなたの仮定は正しいです。の 2 番目の呼び出しf()は、varargs メソッドを呼び出します。次のコマンドを使用して、コンパイラに 2 番目のメソッドを呼び出させることができます。

private void f(int a) {
    f(a, null);
}

2 番目の質問:

はい。ただし、インターフェイスを拡張することはできません。抽象クラスに変更Aすると、コンパイルされます。

于 2012-06-05T16:46:13.180 に答える