パラメータの実際のタイプに基づくオーバーロードされたメソッド選択


115

私はこのコードを実験しています:

interface Callee {
    public void foo(Object o);
    public void foo(String s);
    public void foo(Integer i);
}

class CalleeImpl implements Callee
    public void foo(Object o) {
        logger.debug("foo(Object o)");
    }

    public void foo(String s) {
        logger.debug("foo(\"" + s + "\")");
    }

    public void foo(Integer i) {
        logger.debug("foo(" + i + ")");
    }
}

Callee callee = new CalleeImpl();

Object i = new Integer(12);
Object s = "foobar";
Object o = new Object();

callee.foo(i);
callee.foo(s);
callee.foo(o);

これはfoo(Object o)3回印刷されます。メソッドの選択では、実際の(宣言されていない)パラメータタイプを考慮に入れることを期待しています。何か不足していますか?それは印刷しますように、このコードを変更する方法はあるfoo(12)foo("foobar")foo(Object o)

回答:


96

メソッドの選択では、実際の(宣言されていない)パラメータタイプを考慮に入れることを期待しています。何か不足していますか?

はい。あなたの期待は間違っています。Javaでは、動的メソッドのディスパッチは、メソッドが呼び出されたオブジェクトに対してのみ行われ、オーバーロードされたメソッドのパラメーター型に対しては行われません。

Java言語仕様の引用:

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


4
引用したスペックを教えてください。2つの文は互いに矛盾しているようです。上記の例ではインスタンスメソッドを使用していますが、呼び出されるメソッドは実行時に決定されないことは明らかです。
Alex Worden

15
@Alex Worden:メソッドパラメータのコンパイル時のタイプが、呼び出されるメソッドのシグネチャを決定するために使用されます(この場合)foo(Object)。実行時に、メソッドが呼び出されるオブジェクトのクラスは、メソッドをオーバーライドする宣言された型のサブクラスのインスタンスである可能性があることを考慮して、呼び出されるメソッドの実装を決定します。
Michael Borgwardt

86

前述のように、オーバーロードの解決はコンパイル時に実行されます。

Java Puzzlersには、その良い例があります。

パズル46:混乱するコンストラクターの場合

このパズルは、2つの混乱するコンストラクターを示しています。mainメソッドはコンストラクターを呼び出しますが、どのコンストラクターですか?プログラムの出力は答えに依存します。プログラムは何を印刷しますか、それとも合法ですか?

public class Confusing {

    private Confusing(Object o) {
        System.out.println("Object");
    }

    private Confusing(double[] dArray) {
        System.out.println("double array");
    }

    public static void main(String[] args) {
        new Confusing(null);
    }
}

ソリューション46:混乱するコンストラクターのケース

... Javaの過負荷解決プロセスは2つのフェーズで動作します。最初のフェーズでは、アクセス可能で適用可能なすべてのメソッドまたはコンストラクターを選択します。2番目のフェーズでは、最初のフェーズで選択されたメソッドまたはコンストラクターの中で最も具体的なものが選択されます。1つのメソッドまたはコンストラクターは、他のメソッドまたはコンストラクターに渡されたパラメーターを受け入れることができる場合、他のメソッドまたはコンストラクターよりも限定的ではありません [JLS 15.12.2.5]。

私たちのプログラムでは、両方のコンストラクターがアクセス可能で適用可能です。コンストラクター Confusing(Object)は、Confusing(double [])に渡されたパラメーターを受け入れるため、 Confusing(Object)はそれほど限定的ではありません。(すべてのdouble配列は、あるオブジェクトではなく、すべてのオブジェクトが Aであります二重アレイ。)最も特定のコンストラクタは、したがってされ混乱(ダブル[])、プログラムの出力を説明しているが。

この動作は、double []型の値を渡す場合に意味があります。nullを渡すと直感に反するこのパズルを理解するための鍵は、メソッドまたはコンストラクターが最も具体的なテストでは実際のパラメーターを使用しないことです呼び出しに表示されるです。それらは、どのオーバーロードが適用可能かを決定するためにのみ使用されます。コンパイラーは、どのオーバーロードが適用可能でアクセス可能かを判別すると、仮パラメーター(宣言に現れるパラメーター)のみを使用して、最も具体的なオーバーロードを選択します。

nullパラメーターを使用してConfusing(Object)コンストラクターを呼び出すには、new Confusing((Object)null)を記述します。これにより、Confusing(Object)のみが適用されます。より一般的には、コンパイラーに特定のオーバーロードを選択させるには、実際のパラメーターを仮パラメーターの宣言された型にキャストします。


4
「SOFに関する最も優れた説明の1つ」と言うのが遅すぎないことを願っています。ありがとう:)
TheLostMind 2014年

5
コンストラクター 'private Confusing(int [] iArray)'も追加した場合、コンパイルに失敗すると思いますか?同じ特異性を持つ2つのコンストラクタがあるからです。
Risser 2014年

動的な戻り値の型を関数の入力として使用する場合、常により具体的ではないものを使用します...すべての可能な戻り値に使用できるそのメソッドを言った...
kaiser

16

引数のタイプに基づいてメソッドの呼び出しをディスパッチする機能は、マルチディスパッチと呼ばます。Javaでは、これはVisitorパターンで行われます

ただし、IntegersとStrings を扱っているため、このパターンを簡単に組み込むことはできません(これらのクラスを変更することはできません)。したがって、switchオブジェクトランタイムの巨人はあなたの選択の武器になります。


11

Javaでは、呼び出すメソッド(使用するメソッドシグネチャなど)はコンパイル時に決定されるため、コンパイル時のタイプに応じて決まります。

これを回避するための一般的なパターンは、オブジェクトシグネチャを使用してメソッドのオブジェクトタイプを確認し、キャストを使用してメソッドに委任することです。

    public void foo(Object o) {
        if (o instanceof String) foo((String) o);
        if (o instanceof Integer) foo((Integer) o);
        logger.debug("foo(Object o)");
    }

多くの型があり、これが管理できない場合は、メソッドのオーバーロードはおそらく適切なアプローチではなく、パブリックメソッドはObjectを取得し、何らかの種類の戦略パターンを実装して、オブジェクト型ごとの適切な処理を委任する必要があります。


4

String、Integer、Boolean、Longなどのいくつかの基本的なJava型をとることができる "Parameter"と呼ばれるクラスの適切なコンストラクターを呼び出すことで同様の問題がありました。オブジェクトの配列を前提として、それらを配列に変換したいと思います入力配列内の各オブジェクトの最も具体的なコンストラクターを呼び出すことにより、私のパラメーターオブジェクトの また、IllegalArgumentExceptionをスローするコンストラクターParameter(Object o)を定義する必要がありました。もちろん、配列内のすべてのオブジェクトに対してこのメ​​ソッドが呼び出されることを発見しました。

私が使用した解決策は、リフレクションを介してコンストラクタを検索することでした...

public Parameter[] convertObjectsToParameters(Object[] objArray) {
    Parameter[] paramArray = new Parameter[objArray.length];
    int i = 0;
    for (Object obj : objArray) {
        try {
            Constructor<Parameter> cons = Parameter.class.getConstructor(obj.getClass());
            paramArray[i++] = cons.newInstance(obj);
        } catch (Exception e) {
            throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e);
        }
    }
    return paramArray;
}

醜いinstanceof、switchステートメント、ビジターパターンは必要ありません。:)


2

Javaは、呼び出すメソッドを決定しようとするときに参照タイプを調べます。コードを強制したい場合は、「正しい」メソッドを選択し、フィールドを特定のタイプのインスタンスとして宣言できます。

Integeri = new Integer(12);
String s = "foobar";
Object o = new Object();

パラメータのタイプとしてパラメータをキャストすることもできます。

callee.foo(i);
callee.foo((String)s);
callee.foo(((Integer)o);

1

メソッド呼び出しで指定された引数の数とタイプ、およびオーバーロードされたメソッドのメソッドシグネチャが完全に一致する場合、それが呼び出されるメソッドです。Object参照を使用しているため、Javaはコンパイル時にObject paramにObjectを直接受け入れるメソッドがあると判断します。そのため、そのメソッドを3回呼び出しました。

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