一个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()方法。