为什么外部类可以通过内部类对象访问内部类的私有变量

我们知道 Java 中类的 private 成员正常情况下只能在定义类的内部访问,那么下图为什么可以?又是如何实现的呢?

如下图所示:

上图是 JDK 1.8 中 ThreadLocal#get() 方法的源码,从标红的部分可以看到,getEntry() 是 静态内部类 ThreadLocalMap 的 private 方法,为什么在外部类中可以通过对象的方式访问?

回答:

一下回答参考 R大在知乎上的回答

总结就是,

  • Java语言规范里允许 enclosing class 访问 inner classprivate/protected成员,也允许inner class访问 enclosing classprivate/protected 成员。

  • javac 的做法是,在 enclosing / inner class 之间要访问对方的 private /protected 成员时,javac 会生成合适的 access method(即 access$xxx 形式的方法)来提供合适的可访问性,这样就绕开了原本的成员的可访问性不足的问题。

如何实现上面规定的?

虽然 Java 语言规范是允许相互访问 private/protected 成员,但是没有规定如何实现这种访问

JVM 规范则在大多数时候都把每个Class都看作等价于top-level的,也就是说不关心enclosing / inner class之间的嵌套关系。对JVM来说,enclosing classinner class在大部分情况下都是“不相关的两个类”,所以它们之间相互是不能访问对方的private/protected成员的。

在实现中,衔接 Java 语言规范与 JVM 规范的就是 Java 源码级编译器(例如 javac,ECJ等)。既然规范没有规定死要如何实现,各个编译器可以自己发明自己的办法。

javac的做法:

  • enclosing / inner class 之间要访问对方的 private /protected 成员时,javac 会生成合适的 access method(即 access$xxx 形式的方法)来提供合适的可访问性,这样就绕开了原本的成员的可访问性不足的问题。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Outer {
private int outProp = 8;
class Inner {
private int inProp = 5;
}

public void accessInnerProp() {
System.out.println(new Inner().inProp);
}

public static void main(String[] args) {
Outer p = new Outer();
p.accessInnerProp();
}
}

javac 会把上面的代码解糖为类似下面的形式:(下面的代码是)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Outer {
private int outProp;

public Outer() {
super();
this.outProp = 8;
}

public void accessInnerProp() {
System.out.println(Outer$Inner.access$000(new Outer$Inner(this)));
}

public static void main(String[] args) {
Outer p = new Outer();
p.accessInnerProp();
}
}

// javac 编译后内部类是单独的文件
class Outer$Inner {
private int inProp;
final /* synthetic */ Outer this$0;

Outer$Inner(Outer outer) {
this.this$0 = outer;
super();
this.inProp = 5;
}

static /* synthetic */ int access$000(Outer$Inner self) {
return self.inProp;
}
}

从上面代码可以看出来 javac 编译后,自动生成了合适的 access$xxx 形式的方法

  • 问题,如何得到解糖后的代码?
  • 答:1. javac编译后,javap 看字节码人肉反编译;2. 调试 javac 直接把变换后的 AST 输出成文本。

我们可以javap 查看字节码确认:

1
2
3
4
5
6
7
11: invokestatic  #6                  // Method com/spoonli/mall/OuterAccessInnerTest$Inner.access$100:(Lc
om/spoonli/mall/OuterAccessInnerTest$Inner;)I


static int access$100(com.spoonli.mall.OuterAccessInnerTest$Inner);

上面内容分别从调用 private 成员的外部类和生成access$xxx方法的内部类中反编译得到
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2023 ligongzhao
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信