【问题标题】:Enum's anonymous implementation cannot reference the Enum's private static toString with argumentEnum 的匿名实现不能用参数引用 Enum 的私有静态 toString
【发布时间】:2020-02-06 12:43:41
【问题描述】:

鉴于此示例代码...

package example;

public class Main {
    public static void main(String[] args) {
        System.out.println(MyEnum.X.getValue());
    }

    private enum MyEnum {
        X(){
            @Override
            String getValue() {
                return toString("XYZ"); //error here
            }
        };

        abstract String getValue();

        private static String toString(String output) {
            return output;
        }
    }
}

产生以下编译器错误:

Error:(12, 40) java: method toString in class java.lang.Enum<E> cannot be applied to given types;
  required: no arguments
  found: java.lang.String
  reason: actual and formal argument lists differ in length

IntelliJ 有一个不同的问题:来自toString("XYZ") 的toString 带有红色下划线,并显示消息“'toString(java.lang.String)' has private access in 'example.Main.MyEnum'” ,通过“Make 'MyEnum.toString' package-private”的解决方案。

让我感到奇怪的是,以下任何一项都解决了这个问题:

  • 通过枚举引用调用方法:X.toString("XYZ")。
  • 通过类引用调用方法:MyEnum.toString("XYZ")。
  • 通过 super:super.toString("XYZ") 调用方法。 (但this.toString("XYZ") 不起作用)
  • 将方法包设为私有或公有
  • 将方法命名为“toString2”

现在,对于任何生产代码,我可能会将该方法命名为其他名称(可能更能描述我对它的处理方式)并继续前进,但我仍然想知道,为什么会发生这种情况?为什么 IntelliJ 和 javac 的错误信息不同?

这个问题可能类似于Cannot make a static reference to the non-static field memberVariable with private variable,但我觉得它并不能完全解释问题——为什么重命名会起作用?

【问题讨论】:

  • @Michael 我不是在寻找快速解决方案,而是在寻找解释;为什么我需要在前面添加 MyEnum 只是因为方法名为“toString”?单独的参数应该足以区分默认 toString(0 个参数)和我自己的 toString(1 个参数)

标签: java enums


【解决方案1】:

首先,这个问题并不特定于枚举。它适用于任何内部类。我已经重构了您的示例以删除枚举,这表明内部类和匿名内部类存在相同的问题。

class Main {
    public static void main(String[] args) {
        Main main = new Main() {
            {
                System.out.println(toString("XYZ")); // same error
            }  
        };
    }

    class Foo {
        {
            System.out.println(toString("XYZ")); // same error
        }
    }

    private static String toString(String output) {
        return output;
    }
}

JLS 中有说明here:

示例 6.5.7.1-1。简单的方法名称

以下程序演示了作用域的作用: 确定调用哪个方法。

class Super {
    void f2(String s)       {}
    void f3(String s)       {}
    void f3(int i1, int i2) {}
}

class Test {
    void f1(int i) {}
    void f2(int i) {}
    void f3(int i) {}

    void m() {
        new Super() {
            {
                f1(0);  // OK, resolves to Test.f1(int)
                f2(0);  // compile-time error
                f3(0);  // compile-time error
            }
        };
    }
} 

对于调用f1(0),只有一个名为 f1 的方法在范围内。它是方法Test.f1(int),其声明在范围内 贯穿整个 Test 主体,包括匿名类声明。 §15.12.1选择在类Test中搜索,因为匿名类 声明没有名为 f1 的成员。最终,Test.f1(int) 是 解决了。​​

对于f2(0) 的调用,两个名为f2 的方法在范围内。第一的, 方法Super.f2(String) 的声明在整个范围内 匿名类声明。二、方法的声明 Test.f2(int) 在整个测试主体的范围内,包括 匿名类声明。 (请注意,声明都没有阴影 另一个,因为在每个声明的时候,另一个是 不在范围内。)§15.12.1 选择在 Super 类中搜索,因为它 有一个名为f2 的成员。但是,Super.f2(String) 不适用于 f2(0),所以会发生编译时错误。请注意,Test 类不是 搜索。

对于f3(0) 的调用,三个名为f3 的方法在范围内。第一的 第二,方法Super.f3(String)的声明和 Super.f3(int,int) 在整个匿名类的范围内 宣言。三、方法Test.f3(int)的声明在 整个测试主体的范围,包括匿名类 宣言。 §15.12.1 选择在 Super 类中搜索,因为它有 名为f3 的成员。但是,Super.f3(String) 和 Super.f3(int,int) 是 不适用于f3(0),因此会发生编译时错误。注意 未搜索类 Test。

选择在嵌套类的超类层次结构之前搜索 词法封闭范围称为“梳状规则”(§15.12.1)。

【讨论】:

  • 您能解释一下为什么在 Eclipse OP 代码中有效并且您的第一个 System.out.println 有效吗?
  • @user7294900 Eclipse 编译器是与javac 不同的实现。要么是 Eclipse 作者决定故意“改进”这种行为,要么它是一个足够晦涩的用例,以至于没有人注意到其中的区别。
  • 您的示例与enum 的情况不同,因为在那里,嵌套类也是外部类的子类。两个toString 方法在超类中都可用,但显然javac 假定private 方法是可访问的(super.toString("XYZ") 可以工作),但不能被继承。 has been noticed 的区别,但是 JDK 团队花了四年时间才确定 Eclipse 是错误的,关闭了 this bug,显然,没有人告诉 Eclipse 团队重新打开他们的错误……
  • @Holger 不错的侦探。再给它10年,也许它会得到修复。
猜你喜欢
  • 1970-01-01
  • 2012-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多