JavaでのMethod Sigunatureとは


JavaでのMethod Signatureとは

Java における Method Signature とは具体的に何を指すのでしょうか?

仕様差の確認

Java SE 18 Editionの2つの仕様を見比べてみます。

Java Language Specification(JLS)の 8.4.2 Method Signature によると、2つのメソッドの「メソッド名」と「引数の型」が同じであれば、これらのメソッドは同じとみなされます。

Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any), and, after adapting the formal parameter types of N to the type parameters of M, the same formal parameter types.

一方、Java Vitual Machine Specification(Java VM Spec.) の 4.7.9.1. Signatures では、「引数の型」、「復帰値」および「スローする例外」の3つの組み合わせで判別するとのことです。なんと、「メソッド名」が含まれていないのです。

A method signature for any method or constructor declaration which is either generic, or has a type variable or parameterized type as the return type or a formal parameter type, or has a type variable in a throws clause, or any combination thereof.

なぜ、こんな仕様差が出るのでしょうか?

そこで、バイトコードがどのような作りになっているかを紐解いてみましょう。

検証用のソースコード

まず、呼び出される側のクラスのJavaソース。 methodA メソッドを持つクラスです。

O.java
class O {
    static void methodA(String s) {
	    System.out.println(s);
    }
}

次に呼び出す側です。これらのクラスの違いは、methodA の登場回数です。前者は3回、後者は1回のみです。

A.java
class A {
	static final String S = "methodA";

	public static void main(String[] args){
		O.methodA("methodA");
	}
}
B.java
class B {
	public static void main(String[] args){
		O.methodA("a");
	}
}

バイトコードの中身

AB クラスのバイトコードを javap -verbose <className>.class でアセンブルした結果を、抜粋します。

A.class
  :
Constant pool:
    :
   #2 = String             #18            // methodA
   #3 = Methodref          #19.#20        // O.methodA:(Ljava/lang/String;)V
    :
  #18 = Utf8               methodA
  #19 = Class              #23            // O
  #20 = NameAndType        #18:#24        // methodA:(Ljava/lang/String;)V
    :
  #23 = Utf8               O
  #24 = Utf8               (Ljava/lang/String;)V
  :
B.class
  :
   #3 = Methodref          #16.#17        // O.methodA:(Ljava/lang/String;)V
    :
  #16 = Class              #20            // O
  #17 = NameAndType        #21:#22        // methodA:(Ljava/lang/String;)V
    :
  #20 = Utf8               O
  #21 = Utf8               methodA
  #22 = Utf8               (Ljava/lang/String;)V

注目点が2つあります。

  1. 両クラスとも methodA 文字列を1箇所のみで定義している
  2. 呼び出し先の NameAndType の参照先が、次のように異なる
    • Aクラスでは、#18、#24と離れている
    • Bクラスでは、#21、#22とくっついている

考察

バイトコードでは、 methodA 文字列の定義を1箇所のみに集約するのも、呼び出し先メソッド情報を「メソッド名」と「引数・復帰値」の2つに分けて定義するのも、Constant pool 資源を最小化するためにあることが伺えます。これによって、Javaアプリケーション実行時のJavaヒープの節約につながるわけです。

重複した文字列や「引数・復帰値」の定義を集約したバイトコード設計だと考えれば、Java VM Spec. の Method Signature がメソッド名を含めない合理性に合点できるのではないでしょうか?

追記

見直してみると、バイトコードにはメソッドの「引数・復帰値」の定義があるのですが、クラス名は集約していないですね。なので、以上の推察は当てにならないかと思い至っています。

A.class
  #24 = Utf8               (Ljava/lang/String;)V
B.class
  #22 = Utf8               (Ljava/lang/String;)V