任务是用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.
怎么了?有没有可能做得更好?
- 尝试键入this.someField并查看它是否有效,而且我认为您可能需要使用构造函数初始化它。
- 这很奇怪-我很惊讶重写被视为静态上下文。请注意,使字段受保护会消除错误,这也是奇怪的…
- @朱丹:这就把错误改成了someField has private access in MyEnum。
- @是的,很奇怪。通常,同一个.java文件中的类可以看到对方的私有字段,但是这里发生了一些不寻常的事情。我试着同时保护和删除私人,他们都工作。
- @jonskeet很好地给出了错误实际上是什么,我个人并不知道枚举在为抽象函数提供实现时是原始MyEnum类的子类,尽管它确实有意义。这很有趣。
- @Timb删除private使它成为package可见性,这在子类中也是明显可见的。
- @但是,准定子类可以看到私有成员变量。
- 我在这里问了一个更大的问题:stackoverflow.com/questions/25011061/…,因为这个问题得到了解答。
- @Timb我敢肯定,当你从一个基类中extend时,那么private只在基类中可见,而protected是在基类及其所有子类中可见的。但是如果在枚举或匿名子类的情况下这是不同的,那么我可能是错的。
- 所以基本上,FIRST和SECOND可以被视为覆盖MyEnum的静态内部类?如果您这样认为,它将解释为什么编译器认为someField是一个静态引用@jonsket感到惊讶。添加this将使它显式地查找基类中的成员,从而将错误更改为someField has private access in MyEnum。我说的对吗?另外,它还解释了为什么在代码中我们可以将枚举值称为MyEnum.FIRST。
- 您可以使用((MyEnum)this).someField访问private字段,因为只有实例具有外部类类型时,内部类才能访问private字段。当然,super.someField要短一些……看这里
- 注意,FIRST.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。这样,编译器对继承可见性感到满意,并编译原始设置。
- 如果是这样的话,那么为什么:doit()方法中的first.someValue是有效的,即使someValue声明为private?
- 因为内部类的语义。类内的私有属性或方法不是该外部类的私有属性或方法。
- "静态内部"是一个矛盾的术语。你需要在整个过程中澄清这一点。
如果您使someField受到保护而不是私有的,或者使用super.someField,您将能够访问它。
- 我不认为这有什么问题(事实上,被否决的人已经取消了被否决的投票)。真奇怪,超级。Somefield也能用。
- +0为什么此解决方案有效?
私有字段不能从子类中访问,这正是您在每个实例上实现MyEnum.doIt()抽象方法时所做的。换成protected就可以了。
- 我是否是唯一一个不明显的人,这将使枚举成为一个没有抽象标识符的抽象类,并且创建一个抽象方法的实现将隐式地子类原始枚举类型?这是有道理的,但乍一看并不明显。
- Zhuinden是Java枚举的标准特征,也是使它们强大的原因之一。这并不明显,但是所有优秀的Java枚举教程/文档都覆盖了它。
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的福利。
- 是的,但是你错过了策略模式,同样,如果有20个枚举会发生什么?那将是一个丑陋的,无法修复的开关,快!
- @Ed接受的答案对每个枚举使用更多的代码,并且不是正确的枚举。在我看来,这比我的解决方案更难维护。使用开关可以利用fall-through,并且不需要重写方法头。
- 是的,但是有了IDE自动完成功能,很多样板文件都可以用鼠标右键填写。我的意思是,我同意,它是冗长的,但这是Java的本质。另外,还有另一个"好处"(因为这是有争议的引用),即枚举实际上被编译为对象,而不是被视为原语。所以你可以用Java来做这样的事情,而不是很多其他语言。
显然,问题是当你说:
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可见访问。这是一个我以前从未见过的现象,现在我将尝试研究。
- 匿名内部类可以看到私有字段。参见stackoverflow.com/questions/25011061/…
- 编译器试图猜测您的意图。通过编写super,您向编译器表示您不相信字段是继承的,因此它不会抱怨。请看下面我的答案。
当枚举是静态变量时,somefield是一个私有变量。不能以这种方式将非静态变量赋给静态变量。
- 是的,但是为什么删除private修饰符时它会工作?
- 如果使用私有构造函数初始化枚举中的非静态变量,例如public class MyEnum { FIRST("String"); private String myString; private MyEnum(String string) { this.string = string; },则可以使用这些变量。
- 我不知道你为什么要限制@zhinden类的实例化…当您想要限制该类的实例数时,它在singleton模式中很有用…但在这种情况下,我不同意你的意见
我不会使用枚举实现策略模式。所有代码最终都在同一个unti(文件)中。
想法是把代码分开。使用接口作为基类,然后将每个策略作为单独的子类实现。干净整洁。
- 有时候最好把所有的东西都放在一起。通过枚举,您可以在一个地方看到API的所有可设置性。但是使用Java 8和CousUs,你可以简单地做它。
- 如果状态简单的话,我可能会将枚举用于状态机,但是对于策略,我肯定会选择单独的类。