17

考虑以下代码:

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以我想要的方式访问实例变量。

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

使用 Oracle JDK 7u9。

编辑

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

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

4 回答 4

6

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

不合格的现场访问

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

规范要求

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

这使得表达式i有点模棱两可:我们指的是封闭实例的字段,还是内部实例?唉,内部实例不继承字段

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

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

通过现场访问this

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

规范要求

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

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

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

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

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

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

编译就好了。

通过超级访问

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

此外, super 是 type E,因此声明可见。

通过其他字段访问

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

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

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

推荐

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

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

于 2013-01-03T17:39:05.043 回答
3

看一下这段代码:

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

和之间唯一区别是访问级别!ij

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

于 2013-01-03T14:36:22.680 回答
1

更新

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

    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是在type的静态 init 块中定义的匿名内部类E。在匿名内部类中寻找私有成员的可见性,我在这个答案中找到了以下内容(为什么在匿名类中只有最终变量可以访问?):

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

从这里我认为它A.i引用了 A 中的这个复制变量,而不是i在 E 中声明。获得 E 中的唯一方法i是它是静态的还是非私有的。


于 2013-01-03T13:18:10.397 回答
0

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

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

于 2013-01-03T12:22:45.347 回答