枚举常量特定 class 体是静态的还是非静态的?
Is an enum constant-specific class body static or non-static?
我有一个枚举类型 class:
public enum Operation {
PLUS() {
@Override
double apply(double x, double y) {
// ERROR: Cannot make a static reference
// to the non-static method printMe()...
printMe(x);
return x + y;
}
};
private void printMe(double val) {
System.out.println("val = " + val);
}
abstract double apply(double x, double y);
}
正如您在上面看到的,我已经定义了一个 enum
类型,其值为 PLUS
。它包含一个常量特定的主体。在它的主体中,我试图调用printMe(val);
,但我得到了编译错误:
Cannot make a static reference to the non-static method printMe().
为什么会出现此错误?我的意思是我正在重写 PLUS
主体中的抽象方法。为什么它在 static
范围内?如何摆脱它?
我知道在 printMe(){...}
上添加 static
关键字可以解决问题,但我想知道如果我想保持 printMe()
非静态,是否还有其他方法?
另一个问题,与上面的问题非常相似,但这次错误消息听起来是相反的,即 PLUS(){...}
具有非静态上下文:
public enum Operation {
PLUS() {
// ERROR: the field "name" can not be declared static
// in a non-static inner type.
protected static String name = "someone";
@Override
double apply(double x, double y) {
return x + y;
}
};
abstract double apply(double x, double y);
}
我尝试声明一个 PLUS
-specific static
变量,但最终出现错误:
the field "name" can not be declared static in a non-static inner type.
如果 PLUS
是匿名 class,为什么我不能在 PLUS
中定义静态常量?这两条错误消息听起来相互矛盾,因为第一条错误消息说 PLUS(){...}
有 static 上下文,而第二条错误消息说 PLUS(){...}
有 非静态 上下文。我现在更糊涂了
我想我不知道错误的性质,但也许我可以为讨论贡献一点力量。
当 Java 编译器编译您的枚举代码时,它会生成一个合成的 class,其内容大致如下:
class Operation {
protected abstract void foo();
private void bar(){ }
public static final Operation ONE;
static {
ONE = new Operation() {
@Override
protected void foo(){
bar();
}
};
}
}
您可以通过 运行 javap 在您的枚举 classes 之一中验证枚举代码看起来有点像这样。
上面的代码给出了与您在枚举中遇到的完全相同的错误:"error: non-static method bar() cannot be referenced from a static context"。
所以这里编译器认为您不能从定义匿名 class 的静态上下文中调用 bar()
方法,它是一个实例方法。
这对我来说毫无意义,它应该可以访问或被拒绝访问,但错误似乎不准确。我仍然很困惑,但这似乎是实际发生的事情。
如果编译器说匿名 class 无法访问 foo
会更有意义,因为它在其父 class 上是私有的,但编译器是改为触发此其他错误。
printMe
不应该是 private
,因为您使用 PLUS 派生了一个新的匿名 class。
protected void printMe(double val) {
至于错误的性质,enum/Enum,有点人为;我现在还不知道:一个内部 class 可能会访问私人的东西...
将 printMe 方法设为静态解决了编译错误:
private static void printMe(long val) {
System.out.println("val = " + val);
}
PLUS()
的类型是什么?
嗯,这基本上是 enum Operation
的类型。
如果您想将它与 java class
进行比较,它基本上是 class 本身的 object
。
现在 enum Operation
有抽象方法 apply
这意味着任何这种类型(即操作)都应该实现这种方法。到目前为止,一切都很好。
现在是出现错误的棘手部分。
如你所见,PLUS()
基本上是Operation
的一种。 Operation
有一个 private
方法 printMe()
意味着只有 enum Operation
本身可以看到它,而不是任何其他枚举,包括子枚举(就像 sub class 和 super class 在 java 中)。此方法也不是 static
,这意味着您不能调用它,除非您实例化 class。所以你最终有两种选择来解决这个问题,
- 按照编译器的建议
printMe() method static
- 制作方法
protected
所以任何 sub-enum
inherits
这个方法 .
在这种情况下,使其静态工作的原因是自动合成访问器功能。如果它是 private static,您仍然会收到以下编译器警告。
Access to enclosing method printMe(double) from the type Operation is
emulated by a synthetic accessor method
在这种情况下唯一不起作用的是私有非静态。所有其他安全工作,例如private static、protected non-static 等。正如其他人提到的,PLUS 是 Operation 的一个实现,因此 private 在技术上不起作用,Java 会自动使用自动合成访问器功能为您修复它。
您更新后的问题很容易回答。匿名 class 永远不允许静态成员。
至于你原来的问题,理解发生了什么的最清晰的方法是尝试 this.printMe();
。那么错误信息就更容易理解了,给出了 real 原因 printMe();
不起作用:
'printMe(double)' has private access in 'Operation'
你不能使用 printMe
的原因是因为它是 private
并且 this
引用的编译时类型是 class 的匿名扩展Operation
,而不是 Operation
本身(参见 Edwin Dalorzo 的回答)。当您只写 printMe();
时,您会收到一条不同的错误消息,因为出于某种原因,编译器甚至没有意识到您正在尝试调用 this
上的实例方法。如果您尝试在根本没有实例上调用 printMe
(即好像它是静态方法),它会给出有意义的错误消息。如果您通过编写 Operation.printMe();
.
来明确说明,错误消息不会改变
解决这个问题的两种方法是使 printMe
受到保护,或者编写
((Operation) this).printMe();
嗯,这是一个奇怪的案例。
看来问题是:
在这种情况下,私有成员应该可以访问 (6.6.1):
Otherwise, the member or constructor is declared private
, and access is permitted if and only if it occurs within the body of the top level class that encloses the declaration of the member or constructor.
但是私有成员不被继承(8.2):
Members of a class that are declared private
are not inherited by subclasses of that class.
因此,printMe
不是匿名 subclass 的成员,编译器会在 superclass* Operation
中搜索它(15.12.1):
If there is an enclosing type declaration of which that method is a member, let T be the innermost such type declaration. The class or interface to search is T.
This search policy is called the "comb rule". It effectively looks for methods in a nested class's superclass hierarchy before looking for methods in an enclosing class and its superclass hierarchy.
这就是奇怪的地方。因为 printMe
在 也包含 PLUS
的 class 中找到,所以调用该方法的对象被确定为封闭实例Operation
,不存在 (15.12.4.1):
Otherwise, let T be the enclosing type declaration of which the method is a member, and let n be an integer such that T is the n'th lexically enclosing type declaration of the class whose declaration immediately contains the method invocation. The target reference is the n'th lexically enclosing instance of this
.
It is a compile-time error if the n'th lexically enclosing instance of this
does not exist.
所以简而言之,因为 printMe
只是 Operation
的成员(而不是继承的),编译器被迫调用 printMe
-存在外部实例.
但是,该方法仍然可以访问,我们可以通过限定调用来找到它:
@Override
double apply(double x, double y) {
// now the superclass is searched
// but the target reference is definitely 'this'
// vvvvvv
super.printMe(x);
return x + y;
}
The two error messages sound contradictory to each other [...].
是的,这是语言的一个令人困惑的方面。一方面,匿名 class 永远不会是静态的 (15.9.5), on the other hand, an anonymous class expression can appear in a static context and therefore has no enclosing instance (8.1.3)。
An anonymous class is always an inner class; it is never static
.
An instance of an inner class I
whose declaration occurs in a static context has no lexically enclosing instances.
为了帮助理解这是如何工作的,这里有一个格式化的例子:
class Example {
public static void main(String... args) {
new Object() {
int i;
void m() {}
};
}
}
<i>斜体</i>
中的所有内容都是静态上下文。从 <b>bold</b>
中的表达式派生的匿名 class 被认为是内部的和非静态的(但没有 [=25 的封闭实例=]).
由于匿名 class 是非静态的,因此它不能声明静态非常量成员,尽管它本身是在静态上下文中声明的。
* 除了稍微模糊一点,Operation
是一个枚举这一事实完全无关紧要 (8.9.1):
The optional class body of an enum constant implicitly defines an anonymous class declaration that extends the immediately enclosing enum type. The class body is governed by the usual rules of anonymous classes [...].
我在这个问题上纠结了很久,但我认为理解它的最好方法是看一个不涉及 enum
:
的类似案例
public class Outer {
protected void protectedMethod() {
}
private void privateMethod() {
}
public class Inner {
public void method1() {
protectedMethod(); // legal
privateMethod(); // legal
}
}
public static class Nested {
public void method2() {
protectedMethod(); // illegal
privateMethod(); // illegal
}
}
public static class Nested2 extends Outer {
public void method3() {
protectedMethod(); // legal
privateMethod(); // illegal
}
}
}
class的一个对象Inner
是一个内部的class;每个这样的对象都包含对封闭 Outer
class 对象的隐藏引用。这就是为什么调用 protectedMethod
和 privateMethod
是合法的。它们在包含 Outer
的对象上被调用,即 hiddenOuterObject.protectedMethod()
和 hiddenOuterObject.privateMethod()
.
class的一个对象Nested
是静态嵌套的class;没有 Outer
class 的对象与之关联。这就是为什么对 protectedMethod
和 privateMethod
的调用是非法的——没有 Outer
对象可供它们处理。错误消息是 non-static method <method-name>() cannot be referenced from a static context
。 (请注意,此时 privateMethod
仍然可见。如果 method2
有一个类型为 Outer
的不同对象 outer
,它可以合法地调用 outer.privateMethod()
。但是在示例代码,没有它可以处理的对象。)
class Nested2
的对象类似地是一个静态嵌套 class,但它扩展了 Outer
。由于 Outer
的受保护成员将被继承,因此调用 protectedMethod()
是合法的;它将对 class Nested2
的对象进行操作。但是,私有方法 privateMethod()
不是继承的。所以编译器对待它的方式与对待 Nested
的方式相同,这会产生相同的错误。
enum
案例与 Nested2
案例非常相似。每个带有主体的枚举常量都会导致创建一个新的匿名 subclass of Operation
,但它实际上是一个静态嵌套 class(即使匿名 classes 通常是内部 classes)。 PLUS
对象没有对 class Operation
对象的隐藏引用。这样,继承的public和protected成员就可以被引用,并且会对PLUS
对象进行操作;但是对 Operation
中私有成员的引用不会被继承,并且无法访问它们,因为没有隐藏的对象可以处理。错误消息 Cannot make a static reference to the non-static method printMe()
与 non-static method cannot be referenced from a static context
几乎相同,只是单词的顺序不同。 (我并不是说所有的语言规则都像 Nested2
的情况;但在这种情况下,它确实帮助我将它们视为几乎相同的结构。)
这同样适用于对受保护和私有字段的引用。
我有一个枚举类型 class:
public enum Operation {
PLUS() {
@Override
double apply(double x, double y) {
// ERROR: Cannot make a static reference
// to the non-static method printMe()...
printMe(x);
return x + y;
}
};
private void printMe(double val) {
System.out.println("val = " + val);
}
abstract double apply(double x, double y);
}
正如您在上面看到的,我已经定义了一个 enum
类型,其值为 PLUS
。它包含一个常量特定的主体。在它的主体中,我试图调用printMe(val);
,但我得到了编译错误:
Cannot make a static reference to the non-static method printMe().
为什么会出现此错误?我的意思是我正在重写 PLUS
主体中的抽象方法。为什么它在 static
范围内?如何摆脱它?
我知道在 printMe(){...}
上添加 static
关键字可以解决问题,但我想知道如果我想保持 printMe()
非静态,是否还有其他方法?
另一个问题,与上面的问题非常相似,但这次错误消息听起来是相反的,即 PLUS(){...}
具有非静态上下文:
public enum Operation {
PLUS() {
// ERROR: the field "name" can not be declared static
// in a non-static inner type.
protected static String name = "someone";
@Override
double apply(double x, double y) {
return x + y;
}
};
abstract double apply(double x, double y);
}
我尝试声明一个 PLUS
-specific static
变量,但最终出现错误:
the field "name" can not be declared static in a non-static inner type.
如果 PLUS
是匿名 class,为什么我不能在 PLUS
中定义静态常量?这两条错误消息听起来相互矛盾,因为第一条错误消息说 PLUS(){...}
有 static 上下文,而第二条错误消息说 PLUS(){...}
有 非静态 上下文。我现在更糊涂了
我想我不知道错误的性质,但也许我可以为讨论贡献一点力量。
当 Java 编译器编译您的枚举代码时,它会生成一个合成的 class,其内容大致如下:
class Operation {
protected abstract void foo();
private void bar(){ }
public static final Operation ONE;
static {
ONE = new Operation() {
@Override
protected void foo(){
bar();
}
};
}
}
您可以通过 运行 javap 在您的枚举 classes 之一中验证枚举代码看起来有点像这样。
上面的代码给出了与您在枚举中遇到的完全相同的错误:"error: non-static method bar() cannot be referenced from a static context"。
所以这里编译器认为您不能从定义匿名 class 的静态上下文中调用 bar()
方法,它是一个实例方法。
这对我来说毫无意义,它应该可以访问或被拒绝访问,但错误似乎不准确。我仍然很困惑,但这似乎是实际发生的事情。
如果编译器说匿名 class 无法访问 foo
会更有意义,因为它在其父 class 上是私有的,但编译器是改为触发此其他错误。
printMe
不应该是 private
,因为您使用 PLUS 派生了一个新的匿名 class。
protected void printMe(double val) {
至于错误的性质,enum/Enum,有点人为;我现在还不知道:一个内部 class 可能会访问私人的东西...
将 printMe 方法设为静态解决了编译错误:
private static void printMe(long val) {
System.out.println("val = " + val);
}
PLUS()
的类型是什么?
嗯,这基本上是 enum Operation
的类型。
如果您想将它与 java class
进行比较,它基本上是 class 本身的 object
。
现在 enum Operation
有抽象方法 apply
这意味着任何这种类型(即操作)都应该实现这种方法。到目前为止,一切都很好。
现在是出现错误的棘手部分。
如你所见,PLUS()
基本上是Operation
的一种。 Operation
有一个 private
方法 printMe()
意味着只有 enum Operation
本身可以看到它,而不是任何其他枚举,包括子枚举(就像 sub class 和 super class 在 java 中)。此方法也不是 static
,这意味着您不能调用它,除非您实例化 class。所以你最终有两种选择来解决这个问题,
- 按照编译器的建议
printMe() method static
- 制作方法
protected
所以任何sub-enum
inherits
这个方法 .
在这种情况下,使其静态工作的原因是自动合成访问器功能。如果它是 private static,您仍然会收到以下编译器警告。
Access to enclosing method printMe(double) from the type Operation is emulated by a synthetic accessor method
在这种情况下唯一不起作用的是私有非静态。所有其他安全工作,例如private static、protected non-static 等。正如其他人提到的,PLUS 是 Operation 的一个实现,因此 private 在技术上不起作用,Java 会自动使用自动合成访问器功能为您修复它。
您更新后的问题很容易回答。匿名 class 永远不允许静态成员。
至于你原来的问题,理解发生了什么的最清晰的方法是尝试 this.printMe();
。那么错误信息就更容易理解了,给出了 real 原因 printMe();
不起作用:
'printMe(double)' has private access in 'Operation'
你不能使用 printMe
的原因是因为它是 private
并且 this
引用的编译时类型是 class 的匿名扩展Operation
,而不是 Operation
本身(参见 Edwin Dalorzo 的回答)。当您只写 printMe();
时,您会收到一条不同的错误消息,因为出于某种原因,编译器甚至没有意识到您正在尝试调用 this
上的实例方法。如果您尝试在根本没有实例上调用 printMe
(即好像它是静态方法),它会给出有意义的错误消息。如果您通过编写 Operation.printMe();
.
解决这个问题的两种方法是使 printMe
受到保护,或者编写
((Operation) this).printMe();
嗯,这是一个奇怪的案例。
看来问题是:
在这种情况下,私有成员应该可以访问 (6.6.1):
Otherwise, the member or constructor is declared
private
, and access is permitted if and only if it occurs within the body of the top level class that encloses the declaration of the member or constructor.但是私有成员不被继承(8.2):
Members of a class that are declared
private
are not inherited by subclasses of that class.因此,
printMe
不是匿名 subclass 的成员,编译器会在 superclass*Operation
中搜索它(15.12.1):If there is an enclosing type declaration of which that method is a member, let T be the innermost such type declaration. The class or interface to search is T.
This search policy is called the "comb rule". It effectively looks for methods in a nested class's superclass hierarchy before looking for methods in an enclosing class and its superclass hierarchy.
这就是奇怪的地方。因为
printMe
在 也包含PLUS
的 class 中找到,所以调用该方法的对象被确定为封闭实例Operation
,不存在 (15.12.4.1):Otherwise, let T be the enclosing type declaration of which the method is a member, and let n be an integer such that T is the n'th lexically enclosing type declaration of the class whose declaration immediately contains the method invocation. The target reference is the n'th lexically enclosing instance of
this
.It is a compile-time error if the n'th lexically enclosing instance of
this
does not exist.
所以简而言之,因为 printMe
只是 Operation
的成员(而不是继承的),编译器被迫调用 printMe
-存在外部实例.
但是,该方法仍然可以访问,我们可以通过限定调用来找到它:
@Override
double apply(double x, double y) {
// now the superclass is searched
// but the target reference is definitely 'this'
// vvvvvv
super.printMe(x);
return x + y;
}
The two error messages sound contradictory to each other [...].
是的,这是语言的一个令人困惑的方面。一方面,匿名 class 永远不会是静态的 (15.9.5), on the other hand, an anonymous class expression can appear in a static context and therefore has no enclosing instance (8.1.3)。
An anonymous class is always an inner class; it is never
static
.
An instance of an inner class
I
whose declaration occurs in a static context has no lexically enclosing instances.
为了帮助理解这是如何工作的,这里有一个格式化的例子:
class Example { public static void main(String... args) { new Object() { int i; void m() {} }; } }
<i>斜体</i>
中的所有内容都是静态上下文。从 <b>bold</b>
中的表达式派生的匿名 class 被认为是内部的和非静态的(但没有 [=25 的封闭实例=]).
由于匿名 class 是非静态的,因此它不能声明静态非常量成员,尽管它本身是在静态上下文中声明的。
* 除了稍微模糊一点,Operation
是一个枚举这一事实完全无关紧要 (8.9.1):
The optional class body of an enum constant implicitly defines an anonymous class declaration that extends the immediately enclosing enum type. The class body is governed by the usual rules of anonymous classes [...].
我在这个问题上纠结了很久,但我认为理解它的最好方法是看一个不涉及 enum
:
public class Outer {
protected void protectedMethod() {
}
private void privateMethod() {
}
public class Inner {
public void method1() {
protectedMethod(); // legal
privateMethod(); // legal
}
}
public static class Nested {
public void method2() {
protectedMethod(); // illegal
privateMethod(); // illegal
}
}
public static class Nested2 extends Outer {
public void method3() {
protectedMethod(); // legal
privateMethod(); // illegal
}
}
}
class的一个对象Inner
是一个内部的class;每个这样的对象都包含对封闭 Outer
class 对象的隐藏引用。这就是为什么调用 protectedMethod
和 privateMethod
是合法的。它们在包含 Outer
的对象上被调用,即 hiddenOuterObject.protectedMethod()
和 hiddenOuterObject.privateMethod()
.
class的一个对象Nested
是静态嵌套的class;没有 Outer
class 的对象与之关联。这就是为什么对 protectedMethod
和 privateMethod
的调用是非法的——没有 Outer
对象可供它们处理。错误消息是 non-static method <method-name>() cannot be referenced from a static context
。 (请注意,此时 privateMethod
仍然可见。如果 method2
有一个类型为 Outer
的不同对象 outer
,它可以合法地调用 outer.privateMethod()
。但是在示例代码,没有它可以处理的对象。)
class Nested2
的对象类似地是一个静态嵌套 class,但它扩展了 Outer
。由于 Outer
的受保护成员将被继承,因此调用 protectedMethod()
是合法的;它将对 class Nested2
的对象进行操作。但是,私有方法 privateMethod()
不是继承的。所以编译器对待它的方式与对待 Nested
的方式相同,这会产生相同的错误。
enum
案例与 Nested2
案例非常相似。每个带有主体的枚举常量都会导致创建一个新的匿名 subclass of Operation
,但它实际上是一个静态嵌套 class(即使匿名 classes 通常是内部 classes)。 PLUS
对象没有对 class Operation
对象的隐藏引用。这样,继承的public和protected成员就可以被引用,并且会对PLUS
对象进行操作;但是对 Operation
中私有成员的引用不会被继承,并且无法访问它们,因为没有隐藏的对象可以处理。错误消息 Cannot make a static reference to the non-static method printMe()
与 non-static method cannot be referenced from a static context
几乎相同,只是单词的顺序不同。 (我并不是说所有的语言规则都像 Nested2
的情况;但在这种情况下,它确实帮助我将它们视为几乎相同的结构。)
这同样适用于对受保护和私有字段的引用。