一个 private
属性只能被它所在的类访问,这件事地球人都知道。但是,你有没有想过,这条规则有没有可能在某种情况下,会变得不成立?
本文将通过一个小例子,来演示怎么让 private
修饰符 “失效”,以及它为什么会 “失效”。
示例代码 废话不多说,先写一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class OuterClass { private String outerClassName = "outerClass" ; public class InnerClass { public void printOuterClassName () { System.out.println(outerClassName); } } public static void main (String[] args) { OuterClass outerClass = new OuterClass (); OuterClass.InnerClass innerClass = outerClass.new InnerClass (); innerClass.printOuterClassName(); } }
上面的代码是不是感觉有一丝异样?为什么在内部类里,能直接访问到外部类的 private
属性?难道 private
修饰符真的 “失效” 了?
别急,待我们把这个 class 反编译了,从字节码层面来看看它到底有什么猫腻。毕竟,字节码可不会骗人。
反编译外部类 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 34 35 36 $ javap -c OuterClass.class Compiled from "OuterClass.java" public class com.boris1993.OuterClass { public com.boris1993.OuterClass(); Code: 0: aload_0 1: invokespecial 4: aload_0 5: ldc 7: putfield 10: return public static void main(java.lang.String[]); Code: 0: new 3: dup 4: invokespecial 7: astore_1 8: new 11: dup 12: aload_1 13: dup 14: invokevirtual 17: pop 18: invokespecial 21: astore_2 22: aload_2 23: invokevirtual 26: return static java.lang.String access$000 (com.boris1993.OuterClass); Code: 0: aload_0 1: getfield 4: areturn }
有没有发现,78 行出现了一个我们没有写过的方法 access$000
?而且从注释来看,它接受一个 OuterClass
类型的参数,而且返回的正是外部类的 outerClassName
的值。
既然我们没定义这个方法,那就是编译器偷偷的给咱整了点活。至于为啥编译器要这么干,结合上面这个例子,也不难猜出来:这就是给内部类访问它的 private
属性用的。
反编译内部类 但是咱不能光猜啊,咱还得有证据。证据哪来?当然是内部类的字节码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 $ javap -c OuterClass$InnerClass .class Compiled from "OuterClass.java" public class com.boris1993.OuterClass$InnerClass { final com.boris1993.OuterClass this$0 ; public com.boris1993.OuterClass$InnerClass (com.boris1993.OuterClass); Code: 0: aload_0 1: aload_1 2: putfield 5: aload_0 6: invokespecial 9: return public void printOuterClassName(); Code: 0: getstatic 3: aload_0 4: getfield 7: invokestatic 10: invokevirtual 13: return }
嗯,果然没错,在第 20 行这一条指令里,它调用了上面我们看到的那个 access$000()
方法。