如何通过重写方法在java枚举中使用字段?

How to use fields in java enum by overriding the method?

本问题已经有最佳答案,请猛点这里访问。

任务是用Java EDCOX1 0实现漂亮的策略设计模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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时,我明白

Cannot make a static reference to the non-static field someField.

怎么了?有没有可能做得更好?


专门化的enum只不过是具有内部类语义的子类。如果在编译后查看字节代码,您会注意到编译器只插入访问器方法来读取私有字段,但任何专用枚举都编译为自己的类。您可以将您的enum视为实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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();
}

如您所见,同样的编译器错误也会发生。实际上,您的问题并不与enum有关,而是与它们的内部类语义有关。

但是,您发现了编译器的一个边界案例,它猜测您的代码的意图,并试图警告您,您的意图是非法的。一般来说,任何专门的enum都可以看到someField字段。但是,从内部类访问private字段有两种方法,只有一种是合法的:

  • private成员不继承。因此,在超级类中定义this实例时,不能从该实例访问private字段。

  • 对于内部类,外部类的成员可以访问,即使它们是private。这是由编译器通过向外部类插入访问器方法来实现的,外部类通过访问器方法公开private字段。只有当内部类为非static时,才能访问非static字段。但对于enums,内部类始终是static类。

  • 后一种情况是编译器抱怨的:

    Cannot make a static reference to the non-static field someField

    您试图从static内部类访问非static字段。这是不可能的,尽管由于内部类语义,字段在技术上是可见的。您可以指示编译器通过从超级类中读取值来显式地访问该值,例如:

    1
    2
    3
    4
    public String doIt() {
      MyEnum thiz = this;
      return thiz.someField;
    }

    现在编译器知道您正试图访问可见(外部)类型的成员,而不是错误地访问(非静态)外部类实例(不存在)的someField字段。(同样,您也可以编写super.someField,它表达了同样的想法,即您希望沿着继承链向下,而不是访问外部实例的字段。)然而,更简单的解决方案是简单地使字段protected。这样,编译器对继承可见性感到满意,并编译原始设置。


    如果您使someField受到保护而不是私有的,或者使用super.someField,您将能够访问它。


    私有字段不能从子类中访问,这正是您在每个实例上实现MyEnum.doIt()抽象方法时所做的。换成protected就可以了。


    someField是私有的,请删除私有修饰符或将其移动到抽象类中。


    你可以做的是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
    2
    3
    4
    public enum MyEnum {
        ...
        public abstract String doIt();
    }

    它隐式地需要枚举是一个abstract类,因为您必须为它提供一个实现。因此,当你说

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

    它给出了一个错误,因为您正试图访问"基类"MyEnum的私有字段,由于它是私有的,所以从隐式创建的匿名子类中看不到它。因此,从子类中可以看到protected,因此它解决了问题。

    有一些关于栈溢出的问题会讨论这个问题,比如单例、枚举和匿名内部类,或者为什么我可以匿名地将枚举子类化,而不是最终类?.

    编辑:显然,这条语句中的所有内容都不正确,因为虽然this.someField不起作用,因为该字段在子类中不可见,但它作为super.someField可见访问。这是一个我以前从未见过的现象,现在我将尝试研究。


    当枚举是静态变量时,somefield是一个私有变量。不能以这种方式将非静态变量赋给静态变量。


    我不会使用枚举实现策略模式。所有代码最终都在同一个unti(文件)中。

    想法是把代码分开。使用接口作为基类,然后将每个策略作为单独的子类实现。干净整洁。