1. 概要

メソッドシグネチャは、Javaのメソッド定義全体のサブセットにすぎません。 したがって、署名の正確な構造は混乱を引き起こす可能性があります。

このチュートリアルでは、メソッドシグネチャの要素とJavaプログラミングにおけるその意味を学習します。

2. メソッドシグネチャ

Java のメソッドはオーバーロードをサポートします。つまり、同じ名前の複数のメソッドを同じクラスまたはクラスの階層で定義できます。 したがって、コンパイラは、クライアントコードが参照するメソッドを静的にバインドできる必要があります。 このため、methodシグニチャは各methodを一意に識別します。

Oracle によると、メソッドシグネチャは名前とパラメータタイプで構成されています。 したがって、修飾子、戻り値の型、パラメーター名、例外リスト、本体など、メソッドの宣言の他のすべての要素はシグニチャーの一部ではありません。

メソッドのオーバーロードと、それがメソッドのシグネチャとどのように関連しているかを詳しく見てみましょう。

3. オーバーロードエラー

次のコードを考えてみましょう

public void print() {
    System.out.println("Signature is: print()");
}

public void print(int parameter) {
    System.out.println("Signature is: print(int)");
}

ご覧のとおり、メソッドにはさまざまなパラメータータイプのリストがあるため、コードはコンパイルされます。 事実上、コンパイラーは任意の呼び出しをいずれかの呼び出しに決定論的にバインドできます。

次に、次のメソッドを追加して、オーバーロードが合法かどうかをテストしましょう。

public int print() { 
    System.out.println("Signature is: print()"); 
    return 0; 
}

コンパイルすると、「メソッドはすでにクラスで定義されています」というエラーが発生します。 これは、メソッドreturnタイプがメソッドsignatureの一部ではないことを証明しています。

修飾子を使って同じことを試してみましょう。

private final void print() { 
    System.out.println("Signature is: print()");
}

同じ「メソッドはすでにクラスで定義されています」というエラーが引き続き表示されます。 したがって、メソッドシグニチャは修飾子に依存しません。

スローされた例外を変更することによるオーバーロードは、以下を追加することでテストできます。

public void print() throws IllegalStateException { 
    System.out.println("Signature is: print()");
    throw new IllegalStateException();
}

ここでも、「メソッドはクラスですでに定義されています」というエラーが表示されます。これは、throw宣言をsignatureの一部にすることはできないことを示しています。

最後にテストできるのは、パラメーター名を変更するとオーバーロードが許可されるかどうかです。 次のメソッドを追加しましょう。

public void print(int anotherParameter) { 
    System.out.println("Signature is: print(int)");
}

予想どおり、同じコンパイルエラーが発生します。 これは、パラメータ名がメソッドシグネチャに影響を与えないことを意味します。

3. ジェネリックスと型消去

汎用パラメーターを使用すると、型消去によって有効な署名が変更されます。 実際には、ジェネリックトークンの代わりにジェネリックタイプの上限を使用する別のメソッドとの衝突を引き起こす可能性があります。

次のコードを考えてみましょう。

public class OverloadingErrors<T extends Serializable> {

    public void printElement(T t) {
        System.out.println("Signature is: printElement(T)");
    }

    public void printElement(Serializable o) {
        System.out.println("Signature is: printElement(Serializable)");
    }
}

シグニチャは異なって見えますが、コンパイラは型消去後に正しいメソッドを静的にバインドできません。

型消去により、コンパイラが Tを上限のSerializable、に置き換えていることがわかります。 したがって、Serializableを明示的に使用するメソッドと衝突します。

ジェネリック型にバインドがない場合、基本型Objectでも同じ結果が表示されます。

4. パラメータリストとポリモーフィズム

メソッドシグネチャは、正確なタイプを考慮に入れます。 つまり、パラメータタイプがサブクラスまたはスーパークラスであるメソッドをオーバーロードできるということです。

ただし、静的バインディングはポリモーフィズム、自動ボクシング、およびタイププロモーションを使用して一致を試みるため、特に注意する必要があります。

次のコードを見てみましょう。

public Number sum(Integer term1, Integer term2) {
    System.out.println("Adding integers");
    return term1 + term2;
}

public Number sum(Number term1, Number term2) {
    System.out.println("Adding numbers");
    return term1.doubleValue() + term2.doubleValue();
}

public Number sum(Object term1, Object term2) {
    System.out.println("Adding objects");
    return term1.hashCode() + term2.hashCode();
}

上記のコードは完全に合法であり、コンパイルされます。 これらのメソッドを呼び出すときに混乱が生じる可能性があります。これは、呼び出す正確なメソッドシグネチャだけでなく、Javaが実際の値に基づいて静的にバインドする方法も知る必要があるためです。

最終的にsum(Integer、Integer)にバインドされるいくつかのメソッド呼び出しを調べてみましょう。

StaticBinding obj = new StaticBinding(); 
obj.sum(Integer.valueOf(2), Integer.valueOf(3)); 
obj.sum(2, 3); 
obj.sum(2, 0x1);

最初の呼び出しでは、正確なパラメータタイプがあります整数、整数。 2回目の呼び出しで、Javaは自動ボックス化されます int整数私たちのために最後に、Javaはバイト値を変換します 0x1 int タイププロモーションを使用して、自動ボックス化して整数。 

同様に、 sum(Number、Number)にバインドする次の呼び出しがあります。

obj.sum(2.0d, 3.0d);
obj.sum(Float.valueOf(2), Float.valueOf(3));

最初の呼び出しでは、 double の値があり、 Doubleに自動ボックス化されます。次に、ポリモーフィズムによって、DoubleNumberと一致します。 同様に、Floatは2番目の呼び出しのNumberと一致します。

両方が浮くダブルから継承番号物体。 ただし、デフォルトのバインディングは番号 。 これは、Javaがメソッドシグネチャに一致する最も近いスーパータイプに自動的に一致するという事実によるものです。

次に、次のメソッド呼び出しについて考えてみましょう。

obj.sum(2, "John");

この例では、最初のパラメーターに int to Integerオートボックスがあります。 ただし、このメソッド名には sum(Integer、String)オーバーロードはありません。 その結果、Javaはすべてのパラメーターのスーパータイプを実行して、一致するものが見つかるまで、最も近い親からObjectにキャストします。 この場合、それはにバインドします sum(オブジェクト、オブジェクト)。 

デフォルトのバインディングを変更するには、次のように明示的なパラメーターキャストを使用できます。

obj.sum((Object) 2, (Object) 3);
obj.sum((Number) 2, (Number) 3);

5. Varargパラメータ

次に、varargsがメソッドの有効なシグネチャと静的バインディングにどのように影響するかに注目しましょう。

ここに、varargsを使用したオーバーロードされたメソッドがあります。

public Number sum(Object term1, Object term2) {
    System.out.println("Adding objects");
    return term1.hashCode() + term2.hashCode();
}

public Number sum(Object term1, Object... term2) {
    System.out.println("Adding variable arguments: " + term2.length);
    int result = term1.hashCode();
    for (Object o : term2) {
        result += o.hashCode();
    }
    return result;
}

では、メソッドの効果的なシグネチャは何ですか? sum(Object、Object)が最初の署名であることはすでに見てきました。 可変引数は本質的に配列であるため、コンパイル後の2番目の有効な署名は次のようになります。 sum(Object、Object [])。 

トリッキーな質問は、パラメーターが2つしかない場合に、メソッドバインディングをどのように選択できるかということです。

次の呼び出しについて考えてみましょう。

obj.sum(new Object(), new Object());
obj.sum(new Object(), new Object(), new Object());
obj.sum(new Object(), new Object[]{new Object()});

明らかに、最初の呼び出しはにバインドされます sum(オブジェクト、オブジェクト) と2番目に sum(Object、Object [])。 Javaに2つのオブジェクトを使用して2番目のメソッドを呼び出させるには、3番目の呼び出しと同様に配列でラップする必要があります。

ここで最後に注意することは、次のメソッドを宣言すると、varargバージョンと衝突することです。

public Number sum(Object term1, Object[] term2) {
    // ...
}

6. 結論

このチュートリアルでは、メソッドのシグネチャが名前とパラメータタイプのリストで構成されていることを学びました。 修飾子、戻り値のタイプ、パラメーター名、および例外リストは、オーバーロードされたメソッドを区別できないため、シグニチャーの一部ではありません。

また、型消去とvarargsが効果的なメソッドシグネチャを隠す方法と、Javaの静的メソッドバインディングをオーバーライドする方法についても見てきました。

いつものように、この記事に示されているすべてのコードサンプルは、GitHubから入手できます。