web-dev-qa-db-ja.com

メソッドをオーバーライドしてJava列挙型のフィールドを使用するにはどうすればよいですか?

タスクは、Java enum:を使用して美しい戦略設計パターンを実装することです。

public enum MyEnum {

    FIRST {
        @Override
        public String doIt() {
            return "1: " + someField; //error
        }
    },
    SECOND {
        @Override
        public String doIt() {
            return "2: " + someField; //error
        }
    };

    private String someField;

    public abstract String doIt();

} 

しかし、someFieldを参照すると、

非静的フィールドsomeFieldへの静的参照を作成できません。

何が問題であり、それをより良くすることは可能ですか?

29
Mark

特殊なenumは、内部クラスのセマンティクスを持つサブクラスに他なりません。コンパイル後にバイトコードを見ると、コンパイラはプライベートフィールドを読み取るためのアクセサメソッドのみを挿入しますが、特殊な列挙型は独自のクラスとしてコンパイルされていることがわかります。 enumは次のように実装されていると考えることができます。

public abstract class MyEnum {

  private static class First extends MyEnum {

    @Override
    public String doIt() {
        return "1: " + someField; //error
    }
  }

  private static class Second extends MyEnum {

    @Override
    public String doIt() {
        return "2: " + someField; //error
    }
  }

  public static final MyEnum FIRST = new First();
  public static final MyEnum SECOND = new Second();

  private String someField;

  public abstract String doIt();
} 

ご覧のとおり、同じコンパイラエラーが発生します。事実上、問題はenumsではなく、それらの内部クラスのセマンティクスに関連しています。

しかし、コンパイラがコードの意図を推測し、意図したことが違法であることを警告しようとする境界的なケースを見つけました。一般に、someFieldフィールドは、特殊なenumに表示されます。ただし、内部クラスからprivateフィールドにアクセスする方法は2つあり、有効な方法は1つだけです。

  1. privateメンバーは継承されません。したがって、スーパークラスで定義されている場合、privateインスタンスからthisフィールドにアクセスすることはできません。

  2. 内部クラスの場合、外部クラスのメンバーは、privateであってもアクセスできます。これは、アクセサメソッドによってprivateフィールドを公開する外部クラスにアクセサメソッドを挿入することにより、コンパイラによって実現されます。非staticフィールドにアクセスできるのは、内部クラスが非staticの場合のみです。ただし、enumsの場合、内部クラスは常にstaticです。

後者の条件は、コンパイラが文句を言うものです。

非静的フィールドへの静的参照を作成できませんsomeField

static内部クラスから非staticフィールドにアクセスしようとしています。内部クラスのセマンティクスのためにフィールドが技術的に表示される場合でも、これは不可能です。たとえば、スーパークラスから値を読み取ることにより、値にアクセスするようにコンパイラに明示的に指示できます。

public String doIt() {
  MyEnum thiz = this;
  return thiz.someField;
}

これで、コンパイラは、(静的ではない)外部クラスインスタンス(存在しない)のsomeFieldフィールドに誤ってアクセスするのではなく、表示されている(外部)タイプのメンバーにアクセスしようとしていることを認識します。 (同様に、継承チェーンを下って外部インスタンスのフィールドにアクセスしないという同じ考えを表すsuper.someFieldを記述できます。)ただし、より簡単な解決策は、フィールドをprotectedにすることです。このようにして、コンパイラーは継承の可視性に満足し、元のセットアップをコンパイルします。

27

プライベートではなくsomeFieldを保護するか、代わりにsuper.someFieldを使用すると、アクセスできるようになります。

5
Alex

someFieldはプライベートです。プライベート修飾子を削除するか、抽象クラスに移動してください。

2
Tim B

プライベートフィールドには、サブクラスからアクセスできません。これは、インスタンスごとにMyEnum.doIt()抽象メソッドを実装するときに行うこととまったく同じです。 protectedに変更すると、機能します。

2
icza

あなたができることは次のとおりです。

_public enum MyEnum {
    FIRST,SECOND;

    private String someField;

    public String doIt(){
        switch(this){
            case FIRST:  return "1: " + someField; break;
            case SECOND: return "2: " + someField; break;
        }
    }

}
_

このように、あなたはまだEnumを継承し、MyEnum.values()Enumの派生から来る他の特典を使うことができます。

1
Zaq

どうやら問題はあなたが言うときそれです:

public enum MyEnum {
    ...
    public abstract String doIt();
} 

実装を提供する必要があるため、列挙型はabstract "class"である必要があります。したがって、あなたが言うとき

FIRST {
    @Override
    public String doIt() {
        return "1: " + this.someField; //error
    }
}

「基本クラス」MyEnumのプライベートフィールドにアクセスしようとしているため、エラーが発生します。プライベートフィールドであるため、暗黙的に作成された匿名サブクラスからは表示されません。そのため、protectedはサブクラスから表示されるため、問題が修正されます。

この問題について話しているStackOverflowに関するいくつかの質問があります。たとえば、 シングルトン、列挙型、匿名の内部クラス または なぜ匿名で列挙型をサブクラス化できますが、最終クラスではないのですか?

編集:明らかに、このステートメントのすべてが正しいわけではありません。フィールドがサブクラスから見えないため、this.someFieldは機能しませんが、issuper.someFieldとして表示されます。これは私が今まで見たことのない現象であり、これから調べてみます。

1
EpicPandaForce

列挙型が静的変数の場合、someFieldはプライベート変数です。この方法で非静的変数を静的変数に割り当てることはできません。

1
Szymon Krawczyk

列挙型を使用してStrategyパターンを実装することはありません。すべてのコードは同じunti(ファイル)になります。

アイデアは、コードを分離することです。インターフェイスを基本クラスとして使用し、各ストラテジーを個別のサブクラスとして実装します。きれいでいい。