JavaSE8リリース記念!CGに載っていないマイナーなIncompatibilityを紹介してみる

さてさて,JavaSE8がリリースされてから1週間以上経ちましたね.
多くの企業がJavaSE8への移行を検討しているかと思います.

そういう企業が移行の際に気に掛けるものが非互換性(Incompatibilities)ではないでしょうか.
Javaは互換性を第一に考えているものの少なからず非互換性があります.
そして,それらはCompatibility Guide for JDK 8(以下,CG)としてまとめられています.

今回はそのCGに載っていないマイナーな非互換性を一つ紹介したいと思います.*1
ちなみに,この非互換性はjavacのコードベース自身にも影響があったという少し面白い逸話があります^^;

フォーマットはCGのパロディです.

JDK8とJDK7との非互換性

このセクションではjavacやHotSpot,Java SE APIのJDK8の非互換性について述べます.
いくつかのAPIがこのリリースにおいて非推奨(Deprecated)になり,いくつかの特徴が完全に除去されたことに注意してください.
これらの非互換性は別のリストに列挙されています.
より詳細な情報はDeprecated APIsFeatures Removed from JDK8を参照してください.

領域: ツール / javac

概要
以下のコードはJDK7においてはコンパイルできましたが,JDK8においてはコンパイルできません.

class Test {
    interface I {
        void m(int n);
    }

    class C implements I {
        public void m(int n) {}
    }

    public void m(int n1, int n2) {}

    C c = new C() {
        public void m(int n) {
            m(n, n);
        }
    };
}

JDK8においてコンパイルした場合,以下のようなエラーが発生します.

Test.java:20: エラー: mに適切なメソッドが見つかりません(int,int)
            m(n, n);
            ^
    メソッド Test.C.m(int)は使用できません
      (実引数リストと仮引数リストの長さが異なります)
    メソッド <anonymous Test$1>.m(int)は使用できません
      (実引数リストと仮引数リストの長さが異なります)
エラー1個

クラスCに対する匿名クラス内でメソッドmへの単純名呼び出し(JLS Java SE 8版 6.5.7.1参照)がおこなわれています.
mは既に最も内側である匿名クラス(及びその親クラス)において定義されており,エンクロージングクラスで定義されているmは検索されません(JLS Java SE 8版 15.12.1参照).
今回の場合はmという名前で発見されたメソッドはm(int n)のみであり,メソッド呼び出し式のm(n, n)を適用することができないため,コンパイルエラーとなります.

より詳細な情報は以下のメーリングリストを参照してください.
[non-308] Scoping problem with Resolve and javac 6 vs. 7 vs. Eclipse]

Nature of Incompatibility
ソース

補足

これは本来はコンパイルエラーになるコードがコンパイルできてしまうというJDK7(OpenJDK)のjavacにおけるバグが修正されたために発生した非互換性です.
ちなみに,JDK6(OpenJDK)やEclipseのjavacでは元々エラーとなっていました.

上で上げた例の場合はTest.this.m(n, n)とすれば問題なくコンパイルできるようになるので大きな問題にはなりません.
ですが,実行したいメソッドがあるエンクロージングのクラスが匿名クラスでもって実装されている場合はそれでは上手く行きません.
例えば以下の例:

class Test {
    interface Dummy {
        void m(int n1, int n2);
    }

    interface I {
        void m(int n);
    }

    class C implements I {
        public void m(int n) {}
    }

    public void makeError() {
        Dummy dummy = new Dummy() {
            public void m(int n1, int n2) {}

            C c = new C() {
                public void m(int n) {
                    m(n, n); // call Dummy's m
                }
            };
        };
    }
}

この場合はCへの匿名クラスにおける外のメソッドmへの呼び出しの際にレシーバを指定することができないため,メソッド名の変更をするなどといった美しくない修正をしないといけません.

ちなみにこのバグの修正による非互換性の影響を一番初めに受けたのは奇しくもjavacでした.
なんと,なんと,javac内において上記の様な仕様に反したメソッドの呼び出しをしていたのです.
そのコードは既に修正されていて問題なくコンパイルできるようになっています.

*1:確認漏れで実際は掲載されているかもしれません.もしそうであればコメント等で教えてください.