Java クラスファイルのアクセスフラグの一つに ACC_SUPER
というものがあります。
このフラグの意味は、ドキュメントに以下のように書かれています。
ACC_SUPER
フラグは、このクラスまたはインタフェースにinvokespecial
命令 (§invokespecial) が出現した場合に、2 つの代替セマンティクスのどちらが表現されるかを示します。
Chapter 4. The class File Format
これは何を意味しているのでしょうか。
結論としては、もはやなんの意味もありません。
以前はこのフラグの有無で少し挙動が変わっていたのですが、Java 7 Update 13 から (正式には Java 8 から)クラスファイルにこのフラグがセットされていてもいなくても同じ挙動になったためです。
Java SE 8 以降では、Java 仮想マシンは、クラス ファイル内のフラグの実際の値やクラスファイルのバージョンに関係なく、すべてのクラス ファイルで
ACC_SUPER
フラグが設定されているものと見なします。
Chapter 4. The class File Format
そのため、このフラグを気にする必要はありません。
以下、Java 考古学者向けの補足です。
ACC_SUPER
誕生の経緯
この StackOverflow の回答を元に、補足しながら説明します。
元々、ACC_SUPER
は invokespecial
の挙動を変更するために生まれました。
Java 1.0.2 の invokespecial
の仕様
invokespecial
はコンストラクタの呼び出しや、オーバーライドした親クラスのメソッド呼び出しなどで使われる命令です。Java 1.0.2 の仕様では、invokespecial
命令は単純に指定されたメソッドを呼び出すとなっていました。
例えば、以下のクラスは Java の最初のバージョン(1.0.2)では C
クラスの super.foo()
の部分が invokespecial
命令で A.foo()
を呼び出すというコンパイル結果になります。実行すると C.foo()
→ A.foo()
の順番で実行されるので "CA" と表示されます。1
class A {
public void foo() {
System.out.print("A");
}
}
class B extends A {
// foo メソッドを A から継承するが、 オーバーライドしない
}
class C extends B {
public void foo() {
System.out.print("C");
super.foo(); // invokespecial A.foo():V
}
}
public class Main {
public static void main(String[] args) {
new C().foo();
}
}
Java 1.1 での invokespecial
の仕様変更
ところが、「invokespecial
は指定されたメソッドを呼び出す」という仕様だと、オーバーライドが追加された場合に問題が起こることが発覚しました。
例えば、上記の B
クラスに以下のように foo()
メソッドが追加された場合、C
クラスの super.foo()
では追加された B.foo()
が実行されるべきですが、C
クラスをコンパイルし直さない限り invokespecial A.foo():V
となっているので 依然として A.foo()
が実行されてしまっていました。
class B extends A {
public void foo() {
System.out.print("B");
super.foo();
}
}
そこで、Java 1.1 で invokespecial
の仕様に手を加えて、invokevirtual
と同様にオーバーライドを考慮して呼び出すメソッドを決めるようになりました。 今回の場合だと、invokespecial A.foo():V
という命令で A.foo()
ではなく、C
クラスのスーパークラスから順に foo()
を探すようになり、 B.foo()
があるのでそれを呼び出すようになりました。
しかし、単純に仕様を変更すると些細な違いですが Java 1.0.2 と挙動が変わり、互換性がなくなってしまいます。
そのため、以下の仕様となりました。
- クラスファイルにアクセスフラグ
ACC_SUPER
を追加- Java 1.1 以降のコンパイラでは、このフラグを必ず設定する
-
ACC_SUPER
の有無で、そのクラス内のinvokespecial
命令の挙動を変更する-
ACC_SUPER
フラグが設定されている(= Java 1.1 以降でコンパイルした)- 指定されたメソッドのオーバーライドを考慮して呼び出す
- 上記の例では、
invokespecial A.foo():V
でB.foo()
を呼び出し、"CBA" という表示になる
- 上記の例では、
- 指定されたメソッドのオーバーライドを考慮して呼び出す
-
ACC_SUPER
フラグが設定されていない(= Java 1.0.2 でコンパイルした)- Java 1.0.2 と同じ挙動にする
- 指定されたメソッドをオーバーライドを考慮せず呼び出す
- 上記の例では、
invokespecial A.foo():V
でA.foo()
を呼び出し、"CA" という表示になる
- 上記の例では、
-
ACC_SUPER
の変更
このような経緯で誕生した ACC_SUPER
ですが、Java 7 Update 13 から(正式な仕様としては Java 8 から)クラスファイルの ACC_SUPER
が設定されていなくても、ACC_SUPER
フラグが設定されているものとして扱うようになりました。
つまり、Java 1.0.2 でコンパイルした(ACC_SUPER
フラグが設定されていない)クラスファイルの挙動が変わりました。具体的には、上記の C
クラスの invokespecial A.foo():V
で A.foo()
を呼び出していたのが、Java 7 Update 13 からは B.foo()
を呼び出すように変わりました。
この変更理由は明らかにされていませんが、上記の StackOverflow のコメントではおそらくセキュリティ上の問題があったからではないかと言われています。
具体的な例として、以下のブログでは Java 7 以降では Thread クラスのクローンができないように Thread.clone()
で CloneNotSupportedException
をスローするように実装されているにもかかわらず、それをスキップして Object.clone()
を呼べてしまうという点が指摘されています。
JDK 7 Thread Cloning Vulnerability - IKVM.NET Weblog
まとめ
基本的に Java 仮想マシン仕様では後方互換性を重視しているので、過去のどのバージョンでコンパイルされたクラスファイルであっても、最新の Java 仮想マシンで実行した際には変わらずに動くようになっています。
それにもかかわらず、一度入れた仕様を打ち消す変更をして挙動を変えるというのはとても珍しいです。もしかしたら、唯一かもしれません。
-
ちなみに、検証したところ Java 1.2 からは
invokespecial
の実行対象がB:foo()
に変わっていました。 ↩