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

2022-09-03 02:43:15

请考虑以下代码:

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:i 在 E 中具有私有访问权限

由于 是 实例 方法,我不明白为什么我不能以我想要的方式访问实例变量。geti

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

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

答案 1

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

不合格的现场访问

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

该规范要求

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

这使得表达式有些含糊不清:我们指的是封闭实例的字段,还是内部实例?唉,内部实例不会继承字段i

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

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

通过此字段进行现场访问

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

该规范要求

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

因此,很明显,我们想要的是内在类的场,而不是外在的类。

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

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

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

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

编译刚刚好。

通过超级访问

与此类似,super 是指调用方法的对象(或正在构造的对象)。因此,很明显,我们指的是内在的实例。

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

通过其他字段访问

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

此处,是对 中声明的静态字段的不合格访问。由于它是一个静态字段,因此使用哪个实例的问题是没有意义的,并且访问有效。DDE

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

建议

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

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


答案 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

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

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


推荐