Java 内部类原理剖析

1,059 阅读2分钟

1. 内部类的特点

Java内部类就是在一个类中定义另一个类,特点在于:

  • 内部类的方法可以访问外部类的变量,即使是外部类的私有变量。
  • 内部类能够对同一个包下的其它类隐藏。

2. 内部类使用示例

public class Outter {
    private boolean flag = true;

    public void init() {
        this.new Inner().innerDo();
    }

    public static void main(String[] args) {
        new Outter().init();
    }

    class Inner {
        void innerDo() {
            if (Outter.this.flag) {
                System.out.println("hello world");
            }
        }
    }
}

非静态内部类对象的创建方式是通过外部类的对象创建的,如上例所示,创建可以为new Outter().new Inner()。 在非静态内部类对象里面使用外部类的成员变量,可以直接使用或者如Outter.this.flag的方式。

3. 内部类原理

为什么内部类可以访问外部类的私有变量?这样是否会带来安全问题?这需要我们深入了解内部类的构造。内部类的解析时编译器变换处理,而非Jvm去特殊处理。

首先,编译上述外部类 javac Outter.java 可以看到产生了两个新文件Outter.classOutter$Inner.class。这说明内部类也是被编译成为了一个字节码文件。

反编译内部类 javap -private Outter\$Inner.class

因为笔者是linux环境,所以添加了转义符\

Compiled from "Outter.java"
class Outter$Inner {
  final Outter this$0;
  Outter$Inner(Outter);
  void innerDo();
}

可以看到编译器自动帮我们添加了外部类的成员变量,因此内部类对象持有外部类对象的引用。

反编译外部类 javap -private Outter.class

Compiled from "Outter.java"
public class Outter {
  private boolean flag;
  public Outter();
  public void init();
  public static void main(java.lang.String[]);
  static boolean access$000(Outter);
}

可以看到编译器自动添加了一个静态方法access$000(Outter)方法。我们具体看一看这个方法。 javap -v Outter.class

截取出该方法的汇编语句

Constant pool:
   #1 = Fieldref           #6.#25         // Outter.flag:Z
   
static boolean access$000(Outter);
  descriptor: (LOutter;)Z
  flags: ACC_STATIC, ACC_SYNTHETIC
  Code:
    stack=1, locals=1, args_size=1
       0: aload_0
       1: getfield      #1                  // Field flag:Z
       4: ireturn
    LineNumberTable:
      line 7: 0

翻译过来就是

static boolean access$000(Outter outter) {
    return outter.flag; // 等同于这个效果,实际这样写会编译报错。
}

最后我们看看innerDo()方法是怎样的

javap -v Outter\$Inner.class

Constant pool:
   #1 = Fieldref           #7.#20         // Outter$Inner.this$0:LOutter;

 void innerDo();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #1                  // Field this$0:LOutter;
         4: invokestatic  #3                  // Method Outter.access$000:(LOutter;)Z
         7: ifeq          18
        10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
        13: ldc           #5                  // String hello world
        15: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        18: return
      LineNumberTable:
        line 21: 0
        line 22: 10
        line 24: 18
      StackMapTable: number_of_entries = 1
        frame_type = 18 /* same */

可以看到内部方法取用flag成员变量的方式是使用内部类对象隐式的Outter对象,调用外部类隐式的静态方法取用。

至此,java非静态内部类的原理就一目了然了。