読者です 読者をやめる 読者になる 読者になる

enum インナーインターフェース PUZZLER


なつかしの Java PUZZLER 風で、

Java Puzzlers 罠、落とし穴、コーナーケース

Java Puzzlers 罠、落とし穴、コーナーケース


【問題】 ヒューズが半分おかしくなった

変体的な 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年の雪嵐