javap反编译探寻内部类如何捕获final变量

final 关键字的用法很多,其中有一种是修饰局部变量,使之能在内部类中使用。

import java.util.Random;

public class Outer {
    public static void main(String[] args) {
        new Outer().sayHi();
    }

    private void sayHi() {
        final int id = new Random().nextInt();
        Inner innerObject = new Inner() {
            @Override
            public int getId() {
                return id;
            }
        };
        innerObject.say();
    }
}

abstract class Inner {
    public abstract int getId();

    public void say() {
        System.out.println("hello, I am an Inner, my id is " + getId());
    }
}

显然,为了能在内部类中使用,final 变量被内部类捕获了,但捕获之后保存到哪里去了呢,我们来反编译一下匿名内部类的class文件:javap -p Outer$1.class

javap -p 会列出类的所有成员

输出:

Compiled from "Outer.java"
class Outer$1 extends Inner {
  final Outer this$0; // 内部类对象持有的外部类对象的 this 引用
  private final int val$id; // 这个字段保存对应外部类方法中局部的 final 变量
  Outer$1(Outer, int); // 第一个参数给 this$0 赋值
  public int getId();
}

注意第二个字段 private final int val$id; 这个就是我们要找的 final 变量,再观察 Outer$1(Outer, int); 构造方法有一个 int 类型的参数,应该就是在这里赋值给 val$id 字段的了。

稍微改下代码,内部类不再使用外部变量:

        Inner innerObject = new Inner() {
            @Override
            public int getId() {
                return 123;
            }
        };

反编译 class 文件,得到:

Compiled from "Outer.java"
class Outer$1 extends Inner {
  final Outer this$0;
  Outer$1(Outer);
  public int getId();
}

可以看到 private final int val$id; 消失了,构造方法中的 int 参数也消失了。说明 final 变量保存在内部类对象的字段上。所谓的“捕获”,就是在创建内部类对象的时候将 final 局部变量传递给内部匿名类的构造方法并保存起来。

你可能感兴趣的:(javap反编译探寻内部类如何捕获final变量)