なつかしの Java PUZZLER 風で、
- 作者: ジョシュア・ブロック,ニール・ガフター,柴田芳樹
- 出版社/メーカー: ピアソン・エデュケーション
- 発売日: 2005/11/14
- メディア: 大型本
- 購入: 3人 クリック: 92回
- この商品を含むブログ (56件) を見る
【問題】 ヒューズが半分おかしくなった
変体的な enum 内のインナーインターフェースを使ったコードがあります。各 enum 毎に Op インターフェースの apply メソッドの振る舞いを定義してます。enum 値は定数固有メソッドにてインターフェースを返却するように定義しています。
package etc9; public enum Foo { PLUS { public Op getOp() { return new Op() { public int apply(int arg) { return arg + 1; } }; } }, MINUS{ public Op getOp() { return new Op() { public int apply(int arg) { return arg - 1; } }; } }, ; public abstract Op getOp(); interface Op { int apply(int arg); } }
上のコードを以下のように実行した場合、何が表示されるでしょうか?
package etc9.pkg; import etc9.Foo; public class Main { public static void main(String[] args) { System.out.println( Foo.PLUS.getOp().apply(1) ); System.out.println( Foo.MINUS.getOp().apply(1) ); } }
【回答】
コードは特に問題なく以下のように表示されるように見えます。
2 0
しかし実際にはコンパイルエラーが発生します。いくぶん変体的ではありますが、文法的にも問題がなさそうに見えます。コンパイラは main メソッド中の apply が不可視と文句を言っています。インナーインターフェースを enum 内で定義することができないのでしょうか。
インナーインターフェースは Map.Entry などで十分一般的であるはずです。例えば以下のコードを見てください。
package etc9; public interface Foo { public static Op plus = new Op() { public int apply(int arg) { return arg + 1; } }; public static Op minus = new Op() { public int apply(int arg) { return arg - 1; } }; interface Op { int apply(int arg); } }
Mainメソッドは以下のように書けます。
package etc9.pkg; import etc9.Foo; public class Main { public static void main(String[] args) { System.out.println( Foo.plus.apply(1) ); System.out.println( Foo.minus.apply(1) ); } }
問題なくコンパイルされて「2」と「0」が出力されます。
もう少し形を近づけてみて、問題のコードを以下のように定義してみます。
package etc9; public enum Foo { ; public static Op plus = new Op() { public int apply(int arg) { return arg + 1; } }; public static Op minus = new Op() { public int apply(int arg) { return arg - 1; } }; interface Op { int apply(int arg); } }
ほぼ同じ形となりました。実行してみましょう。
package etc9.pkg; import etc9.Foo; public class Main { public static void main(String[] args) { System.out.println( Foo.plus.apply(1) ); System.out.println( Foo.minus.apply(1) ); } }
ところがこちらもコンパイルエラーとなります。やはり enum 内にはインナーインターフェースを定義できないのでしょうか?
通常のクラスではどうか?
インターフェースはOKでenumはNG。念のためクラスについても見ておきましょう。
package etc9; public class Foo { public static Op plus = new Op() { public int apply(int arg) { return arg + 1; } }; public static Op minus = new Op() { public int apply(int arg) { return arg - 1; } }; interface Op { int apply(int arg); } }
package etc9.pkg; import etc9.Foo; public class Main { public static void main(String[] args) { System.out.println( Foo.plus.apply(1) ); System.out.println( Foo.minus.apply(1) ); } }
おっと、、コンパイルエラーです??
何となく見えてきました。クラス内のインナーインターフェースにアクセス修飾子が付いていないのが気になります。
public interface Op {
ですね。
インターフェースのメンバはデフォルトで public になるのに対して、enum やクラスでアクセス修飾子を省略した場合はパッケージ外から見られないのは当然でした。
以下のように修正すれば、当然のごとく動きます。
package etc9; public enum Foo { PLUS { public Op getOp() { return new Op() { public int apply(int arg) { return arg + 1; } }; } }, MINUS{ public Op getOp() { return new Op() { public int apply(int arg) { return arg - 1; } }; } }, ; public abstract Op getOp(); public interface Op { int apply(int arg); } }
「だからヒューズの半分だって言ったでしょう」*1
ちなみに
問題の enum は変体的で使いたくないのでもう少し簡素にしてみます。enum はインターフェースを実装できるので以下の形の方がきれいかと思います。
package etc9; public enum Foo implements Foo.Op { PLUS { public int apply(int arg) { return arg + 1; } }, MINUS { public int apply(int arg) { return arg - 1; } }, ; interface Op { int apply(int arg); } }
しかしこれでは循環参照でコンパイルできません。
コンパイルを通すにはインターフェースを外に出す必要があります。
package etc9; interface Op { int apply(int arg); } public enum Foo implements Op { PLUS { public int apply(int arg) { return arg + 1; } }, MINUS { public int apply(int arg) { return arg - 1; } }, ; }
すると以下のように使えます。
package etc9.pkg; import etc9.Foo; public class Main { public static void main(String[] args) { System.out.println( Foo.PLUS.apply(1) ); System.out.println( Foo.MINUS.apply(1) ); } }
*1:コンサルタントの道具箱 1969年の雪嵐