なんだか不思議なJDK6とJDK7との差異
内部クラスのメソッドからアウタークラスの同名異シグニチャのメソッドを呼び出す際の、
JDK6とJDK7でのコンパイラの振る舞いの違いについて。
以下でのJDKのバージョンはJDK6が"1.6.0_29"、JDK7が"1.7.0_01"で、ともにWindows x86版。
Outerクラスは二つのクラスメソッドmethod(int)とn()を持つ。
メソッドn()はObjectクラスを継承する匿名クラスのインスタンスを一つ生成する。
この匿名クラスはm()という新たに定義したメソッドを持つ。
メソッドm()はOuterクラスのメソッドmethod(int)を呼び出す。
public class Outer { public static void method(int i) {} public static void n() { new Object() { public void m() { method(10); } }; } }
これはもちろんJDK6でもJDK7でもコンパイルできる。
では、method(int)の名前を匿名クラスで定義されているメソッドm()と同じにするとどうだろう。
public class Outer { public static void m(int i) {} public static void n() { new Object() { public void m() { m(10); } }; } }
匿名クラス内のm()はメソッド名は同じだがシグニチャが異なるので、
m(10)に適合するOuterクラスのメソッドm(int)を見つけてくれてもいいようなものだが、
残念ながらJDK6でもJDK7でもコンパイラは匿名クラスのm()と適合しない旨だけ告げてエラーとなる。
クラスメソッドm(int)を呼び出すためには、
...snip new Object() { public void m() { Outer.m(10); } }; ...snip
のようにクラス名付きのm(int)にすればいい。
Outerというクラス名を匿名クラス内で明示的に使いたくないことがあるかもしれない。
そういった用途向きには、
public class Outer { public static void m(int i) {} private static void m_(int i) { m(i); } public static void n() { new Object() { public void m() { m_(10); } }; } }
のように、異なる名前のメソッドなら呼べることを利用すれば明示する必要はなくなる。
クラスメソッドであることは本質的ではなく、インスタンスメソッドでも同様である。
public class Outer { public void m(int i) {} public void n() { new Object() { public void m() { Outer.this.m(10); } }; } }
のようにアウタークラスのインスタンスを明示するか、
仲介用のインスタンスメソッドm_(int)を定義してやるかでいける。
と、ここまではJDK6とJDK7とで差が出ないのだが、
以下のようなコードに対するコンパイラの振る舞いが異なってくる。
public class Outer { public static void m(int i) {} public static void n() { new Object() { public void m() { m(10); } private void m(boolean b) {} }; } }
単に、m(int)でもm()でもないシグニチャをもつ同名メソッドm(boolean)を匿名クラスに定義しているだけである。
m()内で呼んでいるm(int)には無関係であるから当然何か差異がでるはずはないと思われる。
実際、JDK6ではm(int)を見つけられない旨のエラーが今までと同じように出るだけである。
ところが奇怪なことにJDK7のコンパイラはこのコードを何も言わずに通すのである。
javapで匿名クラスのコードを覗いてみると、
Compiled from "Outer.java" final class Outer$1 { Outer$1(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void m(); Code: 0: bipush 10 2: invokestatic #2 // Method Outer.m:(I)V 5: return private void m(boolean); Code: 0: return }
ちゃんとOuterのm(int)を呼び出している。
つまり、Outerクラスの名前付きでm(int)を呼び出す書き方をしているかのようにコンパイラが扱っているのだ。
JDK7になってからのJava言語仕様のメソッド呼び出しに関する部分の変更がどうなっているのかきちんと読んでいないのだが、
m(boolean)という無関係なメソッドを定義しただけなのに扱いが変わるというのはなんとも不思議である。
JDK6とJDK7の違いというよりJDK7が奇妙だという話といった方がいいのか。
まだJDK7はUpdate1なのでアップデートが出たりすると振る舞いが変わったりするような、
メソッドの呼び出しまわりのコンパイラの変更に伴う副作用的な徒花だったりするのだろうか。
あと、ついでに、上では匿名クラスであったが、これをローカルクラスにしても同様である。
public class Outer { public static void m(int i) {} public static void n() { class Local { public void m() { m(10); } private void m(boolean b) {} } } }