【问题标题】:Private instance member access from anonymous static instance来自匿名静态实例的私有实例成员访问
【发布时间】:2012-12-17 19:11:37
【问题描述】:

考虑以下代码:

enum E {
    A { public int get() { return i; } },
    B { public int get() { return this.i; } },
    C { public int get() { return super.i; } },
    D { public int get() { return D.i; } };

    private int i = 0;
    E() { this.i = 1; }
    public abstract int get();
}

前 2 个枚举常量声明(A 和 B)出现编译时错误,但后 2 个编译正常(C 和 D)。错误是:

A 行错误 1:无法从静态上下文引用非静态变量 i
B 行的错误 2:我在 E 中有私人访问权限

既然get是一个实例方法,我不明白为什么我不能以我想要的方式访问实例变量i。

注意:从i 的声明中删除private 关键字也会使代码可编译,我也不明白。

使用 Oracle JDK 7u9。

编辑

正如 cmets 中所指出的,这并不特定于枚举,下面的代码会产生相同的行为:

class E {
    static E a = new E() { public int get() { return i; } };
    static E b = new E() { public int get() { return this.i; } };
    static E c = new E() { public int get() { return super.i; } };
    static E d = new E() { public int get() { return d.i; } };

    private int i = 0;
}

【问题讨论】:

  • 这里有同样的问题。过了很久,我还是骗自己说我知道java中的枚举 :)
  • 要获得更多乐趣,请尝试使用return this.i; 而不是A.i。
  • @MarkoTopolnik return this.i; 和 return i; 不编译,但 return super.i; 和 return A.i; 编译。
  • 没错,它们每个都因不同的错误而失败!那是“更有趣”的部分:)
  • assylias,请注意您的错误不是特定于枚举的。尝试 Evgeniy 答案中的代码,它会产生与常规类完全相同的编译器错误。

标签: java static enums private


【解决方案1】:

Java 语言规范要求观察到的行为,特别是对封闭类型字段的隐式访问,以及不继承私有成员的规则。

不合格的字段访问

A { public int get() { return i; } }

规范mandates:

枚举常量的可选类主体隐式定义了一个匿名类声明(第 15.9.5 节),它扩展了直接封闭的枚举类型。类主体由匿名类的通常规则管理;特别是它不能包含任何构造函数。

这使得表达式i 有点模棱两可:我们指的是封闭实例的字段还是内部实例?唉,内部实例doesn't inherit the field:

声明为私有的类的成员不会被该类的子类继承。

因此,编译器得出结论,我们的意思是访问封闭实例的字段 - 但是在静态块中,没有封闭实例,因此出现错误。

通过this进行字段访问

B { public int get() { return this.i; } },

规范mandates:

当用作主要表达式时,关键字 this 表示一个值,该值是对调用实例方法的对象(第 15.12 节)或正在构造的对象的引用。

因此,很明显我们想要的是内部类的字段,而不是外部类。

编译器拒绝字段访问表达式this.iis的原因:

声明为私有的类的成员不会被该类的子类继承。

也就是说,私有字段只能通过声明该字段的类型的引用来访问,而不是其子类型。确实,

B { public int get() { return ((E)this).i; } },

编译得很好。

通过超级访问

像这样,超refers 到调用该方法的对象(或正在构造的对象)。因此很明显,我们指的是内部实例。

此外,super 的类型为E,因此声明可见。

通过其他字段访问

D { public int get() { return D.i; } };

这里,D 是对 E 中声明的静态字段 D 的非限定访问。由于它是一个静态字段,使用哪个实例的问题是没有实际意义的,并且访问是否有效。

然而它非常脆弱,因为只有在枚举对象完全构造后才分配该字段。如果有人在构造过程中调用 get(),则会抛出 NullPointerException。

推荐

正如我们所见,访问其他类型的私有字段会受到一些复杂的限制。由于很少需要,开发人员可能没有意识到这些细微之处。

虽然设置字段protected 会削弱访问控制(即允许包中的其他类访问该字段),但可以避免这些问题。

【讨论】:

  • 我基本同意,除了“在静态块中,没有封闭实例,因此错误” => get 是一个实例方法。变量赋值发生在静态上下文中(A = new E() {...}),但语句 return i; 没有。
  • @assylias 我认为meriton 的逻辑是无可挑剔的:确实没有封闭的实例,并且return i 语句出现在没有继承i 的实例的上下文中。
  • @assylias:Marko 是否回答了你的问题,还是我应该详细说明某个特定方面?
  • @meriton 我想明天早上我需要冷静地重新阅读所有内容,但您的答案似乎是正确的。
  • @assylias 私有成员不被继承的规则和匿名类没有封闭实例的事实的结合解释了一切。我在写答案时忽略了这两个事实中的第一个。
【解决方案2】:

看看这段代码:

public class E 
{
  final int i;
  private final int j;
  final E a;

  E() { i = j = 0; a = null; }

  E(int p_i) {
    this.i = this.j = p_i;
    a = new E() {
      int getI() { return i; }
      int getJ() { return j; }
    };
  }

  int getI() { throw new UnsupportedOperationException(); }
  int getJ() { throw new UnsupportedOperationException(); }

  public static void main(String[] args) {
    final E ea = new E(1).a;
    System.out.println(ea.getI());
    System.out.println(ea.getJ());
  }
}

打印出来

0
1

和i和j之间的唯一区别是访问级别!

这令人惊讶,但这是正确的行为。

【讨论】:

    【解决方案3】:

    更新

    看起来确实是因为它是在静态块中定义的。请看以下内容:

        private E works = new E("A", 0) {
    
            public int get() {
                return i; // Compiles
            }
        };
    
        static {
            A = new E("A", 0) {
    
                public int get() {
                    return i; // Doesn't Compile
                }
    
            };
        }
    

    原创

    我编译了枚举,然后使用 Jad 对其进行反编译以查看代码的样子:

    static abstract class E extends Enum
    {
    
        public static E[] values()
        {
            return (E[])$VALUES.clone();
        }
    
        public static E valueOf(String s)
        {
            return (E)Enum.valueOf(Foo$E, s);
        }
    
        public abstract int get();
    
        public static final E A;
        private int i;
        private static final E $VALUES[];
    
        static
        {
            A = new E("A", 0) {
    
                public int get()
                {
                    return A.i;
                }
    
            }
    ;
            $VALUES = (new E[] {
                A
            });
        }
    
    
        private E(String s, int j)
        {
            super(s, j);
            i = 0;
            i = 1;
        }
    
    }
    

    这让我更清楚A 是在E 类型的静态初始化块中定义的匿名内部类。在匿名内部类中寻找私有成员的可见性,我在这个答案(Why are only final variables accessible in anonymous class?)中找到了以下内容:

    当您创建匿名内部类的实例时,该类中使用的任何变量都会通过自动生成的构造函数复制其值。这避免了编译器必须自动生成各种额外的类型来保存“局部变量”的逻辑状态,例如 C# 编译器

    据此,我认为A.i 指的是A 中的这个复制变量,而不是E 中声明的i。在E 中获取i 的唯一方法是它是静态的还是非私有的。


    【讨论】:

    • 1) 我们都知道它是一个匿名内部类。 2)在您的引文中,“变量”意味着“局部变量”,不适用于此处。
    • @MarkoTopolnik On (1) 我错过了一点打字。令我惊讶的是,它是在静态块中定义的匿名内部类。这就是让我认为 A 无法访问 E 的私有实例变量的原因。
    • 准确地说,它不是匿名的inner,而是nested 类(它没有封闭的实例),这就是为什么你的@示例中的 987654329@ 编译:它调用不同的访问规则,这些规则用于封闭实例的字段。这并不能解释为什么 OP 的代码会产生编译器错误的原因。
    • @JohnFarrelly 请注意,在我的第二个示例中,没有静态块。
    • @assylias 在您的第二个示例中,所有变量都被声明为静态,因此会有类似的问题。如果你让它们成为非静态的,它们都会编译。
    【解决方案4】:

    private 方法可以在同一个类文件中的嵌套类中访问。

    出于这个原因,即使 A 是 E 的匿名子类,第一个示例仍然有效。很好奇为什么第二个示例无法编译,但我怀疑这是误导性错误消息,因为您可以这样做

    A { public int get() { return super.i; } };
    

    编译但是

    A { public int get() { return i; } };
    

    给予

    error: non-static variable i cannot be referenced from a static context
    

    这显然是不正确的,因为super.i 如果这是一个静态上下文将毫无意义。

    正如马可所说

    A { public int get() { return this.i; } };
    

    产生错误信息

    error: i has private access in E
    

    这可能更合适。 IE。您可以显式访问该字段,但不能隐式访问。

    【讨论】:

    • 实际上return this.i; 也给了我一个编译错误。但是super.i 编译。
    • 所以你的解释是“这是一个编译器错误”?
    • this.i 将不起作用,因为i 是超类的私有成员。
    • this.i 没有给出“非静态”错误,但“我在 E 中有私有访问权限”
    • 也许是你的 IDE 是对的 :) 无论如何,达到完全相同的实例变量的各种方法之间不应该有区别。这是不正常的行为。
    猜你喜欢
    • 1970-01-01
    • 2013-10-28
    • 2021-08-19
    • 2021-11-26
    • 1970-01-01
    • 2019-07-13
    • 2015-08-23
    • 2023-04-10
    • 2020-09-19
    相关资源
    最近更新 更多