前言

如果你想更多的了解更多的扩展知识,请查阅:

  1. Android中非静态内部类造成内存泄漏

  2. Java虚拟机规范第四章,以了解更多的关于class文件格式

  3. 在2的基础上,你还可以查看字节码操作库,比如ASM等。

Java内部类

Java中允许在类中定义另一个类,类里面的类叫做内部类,也叫作嵌套类。同时在内部类里面可以访问外部类的private属性或方法。

public class OuterClass {

    private String outer = OuterClass.class.getSimpleName();

    private void outer(){
        System.out.println("OuterClass outer method invoked...");
    }

    class InnerClass{

        public void print(){
            System.out.println(outer);
        }

        public void inner(){
            outer();
        }
    }

    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        InnerClass inner = outer.new InnerClass();
        inner.print();
        inner.inner();
    }
}

这里内部类InnerClass为什么可以访问OuterClass外部类的private属性或方法呢?

Class文件

找到源文件生成的class文件位置,使用javap命令查看一下生成的两个class文件。

命令行输入:

javap -c OuterClass

得到以下输出:

Compiled from "OuterClass.java"
public class OuterClass {
  public OuterClass();
    Code:
       0: aload_0
       1: invokespecial #10                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: ldc           #1                  // class OuterClass
       7: invokevirtual #12                 // Method java/lang/Class.getSimpleName:()Ljava/lang/String;
      10: putfield      #18                 // Field outer:Ljava/lang/String;
      13: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #1                  // class OuterClass
       3: dup
       4: invokespecial #40                 // Method "<init>":()V
       7: astore_1
       8: new           #41                 // class OuterClass$InnerClass
      11: dup
      12: aload_1
      13: dup
      14: invokevirtual #43                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      17: pop
      18: invokespecial #47                 // Method OuterClass$InnerClass."<init>":(LOuterClass;)V
      21: astore_2
      22: aload_2
      23: invokevirtual #50                 // Method OuterClass$InnerClass.print:()V
      26: aload_2
      27: invokevirtual #53                 // Method OuterClass$InnerClass.inner:()V
      30: return

  static java.lang.String access$0(OuterClass);
    Code:
       0: aload_0
       1: getfield      #18                 // Field outer:Ljava/lang/String;
       4: areturn

  static void access$1(OuterClass);
    Code:
       0: aload_0
       1: invokespecial #62                 // Method outer:()V
       4: return
}

可以看到静态access$0和静态access$1方法并不是我们自己定义的,从后面的注释可以了解到这两个方法分别返回了OuterClass的outer属性和outer方法。这两个方法都支持将OuterClass实例作为参数传入。

接下来查看一下内部类的class文件结构。

在命令行中输入:

javap -c OuterClass$InnerClass

得到以下输出:

Compiled from "OuterClass.java"
class OuterClass$InnerClass {
  final OuterClass this$0;

  OuterClass$InnerClass(OuterClass);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #10                 // Field this$0:LOuterClass;
       5: aload_0
       6: invokespecial #12                 // Method java/lang/Object."<init>":()V
       9: return

  public void print();
    Code:
       0: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #10                 // Field this$0:LOuterClass;
       7: invokestatic  #26                 // Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;
      10: invokevirtual #32                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      13: return

  public void inner();
    Code:
       0: aload_0
       1: getfield      #10                 // Field this$0:LOuterClass;
       4: invokestatic  #39                 // Method OuterClass.access$1:(LOuterClass;)V
       7: return
}

在print方法中,会执行access$0方法,目的是拿到外部类的outer属性,如上所述:

invokestatic  #26                 // Method OuterClass.access$0:(LOuterClass;)Ljava/lang/String;

在inner方法中,会执行access$1方法,目的是拿到外部类的outer方法,如上所述:

invokestatic  #39                 // Method OuterClass.access$1:(LOuterClass;)V

注意:在内部类构造的时候,会将外部类的引用传递进来,并且作为内部类的一个属性,所以内部类会持有一个其外部类的引用。

this$0就是内部类持有的外部类引用,通过构造方法传递引用并赋值。

外部类访问内部类

public class Outer {

    public static void main(String[] args) {
        Inner inner = new Outer().new Inner();
        System.out.println(inner.x);
    }

    class Inner {
        private int x = 10;

        private Inner(){}

    }
}

依然采用上面的方式查看class文件,先查看Inner的class文件。

在命令行中输入:

javap -c Outer$Inner

得到以下输出:

Compiled from "Outer.java"
class Outer$Inner {
  final Outer this$0;

  Outer$Inner(Outer);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #12                 // Field this$0:LOuter;
       5: aload_0
       6: invokespecial #14                 // Method java/lang/Object."<init>":()V
       9: aload_0
      10: bipush        10
      12: putfield      #17                 // Field x:I
      15: return

  static int access$0(Outer$Inner);
    Code:
       0: aload_0
       1: getfield      #17                 // Field x:I
       4: ireturn
}

可以看到内部类提供了一个access$0静态方法来访问属性x,接下来查看Outer的class文件。

在命令行中输入:

javap -c Outer

得到以下输出:

Compiled from "Outer.java"
public class Outer {
  public Outer();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #16                 // class Outer$Inner
       3: dup
       4: new           #1                  // class Outer
       7: dup
       8: invokespecial #18                 // Method "<init>":()V
      11: dup
      12: invokevirtual #19                 // Method java/lang/Object.getClass:()Ljava/lang/Class;
      15: pop
      16: invokespecial #23                 // Method Outer$Inner."<init>":(LOuter;)V
      19: astore_1
      20: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
      23: aload_1
      24: invokestatic  #32                 // Method Outer$Inner.access$0:(LOuter$Inner;)I
      27: invokevirtual #36                 // Method java/io/PrintStream.println:(I)V
      30: return
}

外部类通过内部类的实例获取私有属性x的操作如下代码所示:

invokestatic #32 // Method Outer$Inner.access$0:(LOuter$Inner;)I

根据Java官方文档可知,如果(内部类的)成员或构造方法设定成了私有修饰符,当且仅当其外部类访问时是允许的。

如何让内部类私有成员不能被外部类访问?

使用匿名内部类可以避免外部类访问其私有成员。

public class RejectOuter {

    Runnable runnable = new Runnable() {

        private int x = 10;

        @Override
        public void run() {
            System.out.println(x);
        }
    };

    public static void main(String[] args) {
        RejectOuter outer = new RejectOuter();
        outer.runnable.run();
        // 不允许这样访问
        // System.out.println(outer.runnable.x);
    }

}

因为runnable属于Runnable类型,而不是匿名内部类类型,而Runnable中没有x这个属性,所以runnable.x是不被允许的。

注意:

使用匿名内部类时,会生成一个对应的class文件。如上述例子中会生成一个 RejectOuter$1文件,在这个文件中会持有外部类的引用。

Compiled from "RejectOuter.java"
class RejectOuter$1 implements java.lang.Runnable {
  final RejectOuter this$0;

  RejectOuter$1(RejectOuter);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #14                 // Field this$0:LRejectOuter;
       5: aload_0
       6: invokespecial #16                 // Method java/lang/Object."<init>":()V
       9: aload_0
      10: bipush        10
      12: putfield      #19                 // Field x:I
      15: return

  public void run();
    Code:
       0: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #19                 // Field x:I
       7: invokevirtual #32                 // Method java/io/PrintStream.println:(I)V
      10: return
}

这种写法在Android中可能会引起内存泄漏。

下面是变通写法:

public class MyRunnable implements Runnable{

    private int x = 10;

    @Override
    public void run() {
        System.out.println(x);
    }

}
public class RejectOuter {

    Runnable runnable2 = new MyRunnable();

    public static void main(String[] args) {
        RejectOuter outer = new RejectOuter();
        outer.runnable2.run();
    }

}

看到这里相信您在分析Android中非静态内部类造成内存泄漏时会更得心应手。

results matching ""

    No results matching ""