简体   繁体   English

私有实例成员从匿名静态实例访问

[英]Private instance member access from anonymous static instance

Consider the following code: 请考虑以下代码:

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();
}

I get compile-time errors on the first 2 enum constants declarations (A & B) but the last 2 compile fine (C & D). 我在前2个枚举常量声明(A和B)上得到编译时错误,但最后2个编译正常(C&D)。 The errors are: 错误是:

Error 1 on line A: non-static variable i cannot be referenced from a static context A行的错误1:非静态变量我无法从静态上下文中引用
Error 2 on line B: i has private access in E B行的错误2:我在E中有私人访问权限

Since get is an instance method, I don't understand why I can't access the instance variable i the way I want. 由于get是一个实例方法,我不明白为什么我不能访问实例变量i我想要的方式。

Note: removing the private keyword from the declaration of i also makes the code compilable, which I don't understand either. 注意:从i的声明中删除private关键字也会使代码可编译,我也不明白。

Using Oracle JDK 7u9. 使用Oracle JDK 7u9。

EDIT 编辑

As pointed out in the comments, this is not specific to enums and the code below produces the same behaviour: 正如评论中所指出的,这不是枚举特有的,下面的代码会产生相同的行为:

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;
}

The observed behavior is mandated by the Java Language Specification, in particular implicit access to fields of enclosing types, and the rule that private members are not inherited. 观察到的行为是由Java语言规范强制执行的,特别是对封闭类型字段的隐式访问,以及不继承私有成员的规则。

unqualified field access 不合格的现场访问

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

The spec mandates : 规范要求

The optional class body of an enum constant implicitly defines an anonymous class declaration (§15.9.5) that extends the immediately enclosing enum type. 枚举常量的可选类体隐式定义了一个匿名类声明(第15.9.5节),该声明扩展了直接封闭的枚举类型。 The class body is governed by the usual rules of anonymous classes; 班级团体由匿名班级的通常规则管理; in particular it cannot contain any constructors. 特别是它不能包含任何构造函数。

This makes the expression i somewhat ambiguous: Are we referring to the field of the enclosing instance, or the inner instance? 这使得表达i有些模棱两可:我们指的是类实例的字段或内部实例? Alas, the inner instance doesn't inherit the field : 唉,内部实例不继承该字段

Members of a class that are declared private are not inherited by subclasses of that class. 声明为private的类的成员不会被该类的子类继承。

Therefore, the compiler concludes we mean to access the field of the enclosing instance - but being in a static block, there is no enclosing instance, hence the error. 因此,编译总结我们的意思来访问外围实例的领域-但在静态块是, 没有外围实例,因此错误。

field access through this 现场访问通过this

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

The spec mandates : 规范要求

When used as a primary expression, the keyword this denotes a value that is a reference to the object for which the instance method was invoked (§15.12), or to the object being constructed. 当用作主表达式时,关键字this表示一个值,该值是对调用实例方法的对象(第15.12节)的引用,或者是对正在构造的对象的引用。

Therefore, it is quite clear we want the field of the inner class, not the outer one. 因此,很明显我们想要内部阶级的领域,而不是外部领域。

The reason the compiler rejects the field access expression this.i is : 编译器拒绝字段访问表达式this.i

Members of a class that are declared private are not inherited by subclasses of that class. 声明为private的类的成员不会被该类的子类继承。

That is, a private field can only be accessed through a reference of the type that declares the field, not a subtype thereof. 也就是说,私有字段只能通过声明字段的类型的引用来访问,而不能通过其子类型来访问。 Indeed, 确实,

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

compiles just fine. 编译得很好。

access through super 通过超级访问

Like this, super refers to the object the method was invoked on (or the object being constructed). 像这样,super 的是调用方法的对象(或正在构造的对象)。 It is therefore clear we mean the inner instance. 因此,很清楚我们的意思是内在的实例。

Additionally, super is of type E , and the declaration therefore visible. 另外,super是E类型,因此声明可见。

access through other field 通过其他领域访问

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

Here, D is an unqualified access to the static field D declared in E . 这里, D是对E声明的静态字段D的非限定访问。 As it is a static field, the question of which instance to use is moot, and the access valid. 因为它是一个静态字段,所以使用哪个实例的问题没有实际意义,并且访问有效。

It is however quite brittle, as the field is only assigned once the enum object is fully constructed. 然而它非常脆弱,因为只有在枚举对象完全构造时才分配字段。 Should somebody invoke get() during construction, a NullPointerException would be thrown. 如果有人在构造期间调用get(),则会抛出NullPointerException

Recommendation 建议

As we have seen, accessing private fields of other types is subject to somewhat complex restrictions. 正如我们所看到的,访问其他类型的私有字段受到一些复杂的限制。 As it is rarely needed, developers may be unaware of these subtleties. 由于很少需要,开发人员可能不知道这些微妙之处。

While making the field protected would weaken access control (ie permit other classes in the package to access the field), it would avoid these issues. 虽然protected字段会削弱访问控制(即允许包中的其他类访问字段),但它会避免这些问题。

Take a look at this piece of code: 看看这段代码:

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());
  }
}

this prints 这打印

0
1

and the only difference between i and j is the access level! 并且ij之间的唯一区别是访问级别!

This is surprising, but it is correct behavior. 这是令人惊讶的,但这是正确的行为。

Update 更新

It does appear that it is because it is defined in a static block. 看起来确实是因为它是在静态块中定义的。 Take a look at the following: 看看以下内容:

    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
            }

        };
    }

Original 原版的

I compiled the enum and then decompiled it using Jad to see what the code might look like: 我编译了枚举,然后使用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;
    }

}

This makes it clearer to me that A is an anonymous inner class defined in a static init block of type E . 这使我更清楚A E类型的静态init块定义匿名内部类 Looking around for private member visibility in anonymous inner classes, I found the following in this answer ( Why are only final variables accessible in anonymous class? ): 在匿名内部类中查找私有成员可见性,我在此答案中找到以下内容( 为什么在匿名类中只能访问最终变量? ):

When you create an instance of an anonymous inner class, any variables which are used within that class have their values copied in via the autogenerated constructor. 当您创建匿名内部类的实例时,该类中使用的任何变量都会通过自动生成的构造函数复制它们的值。 This avoids the compiler having to autogenerate various extra types to hold the logical state of the "local variables", as for example the C# compiler does 这避免了编译器必须自动生成各种额外类型以保持“局部变量”的逻辑状态,例如C#编译器

From this I take it that Ai refers to this copied variable in A, and not i declared in E. The only way to get the i in E is if it is either static or not private. 从此我认为Ai指的是A中的这个复制变量,而不是i在E中声明。在E中获取i的唯一方法是它是静态还是非私有。


private methods can be accessed in nested classes provided that are in the same class file. private方法可以在嵌套类中访问,只要它们位于同一个类文件中。

For this reason, the first example works even though A is an anonymous sub-class of E. It is curious as to why the second example doesn't compile, but I suspect it's mis-leading error message as you can do 出于这个原因,即使A是E的匿名子类,第一个示例仍然有效。很好奇第二个示例为什么不编译,但我怀疑它是错误的错误消息,因为你可以做

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

compile but 编译但是

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

gives

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

which is clearly incorrect given super.i would have no meaning if this were a static context. 如果这是一个静态的上下文,那么super.i就没有任何意义,这显然是不正确的。

As Marko notes 正如Marko所说

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

produces the error message 生成错误消息

error: i has private access in E

which may be more approriate. 这可能更合适。 Ie you can access this field explictly, but not implicitly. 即你可以明确地访问这个字段,但不能隐式地访问。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM