Annonymous Class

Table of Contents

1. Annonymous Class

1.1. "捕获" 自由变量

public class Test {
    public static void main(String[] args) {
        final int x = 1;
        final Integer y = 1;
        new Thread() {
            @Override
            public void run() {
                System.out.println(x);
                System.out.println(y);
            }
        }.start();
    }
}

对应的 bytecode:

Test:

.method public static main([Ljava/lang/String;)V
    //.....    
    const/4 v0, 0x1

    invoke-static {v0}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
    move-result-object v0

    new-instance v1, LTest$1;

    invoke-direct {v1, v0}, LTest$1;-><init>(Ljava/lang/Integer;)V

    //.....
.end method


Thread:

.class final LTest$1;

.field final synthetic val$y:Ljava/lang/Integer;

.method constructor <init>(Ljava/lang/Integer;)V
    iput-object p1, p0, LTest$1;->val$y:Ljava/lang/Integer;

    invoke-direct {p0}, Ljava/lang/Thread;-><init>()V

    return-void
.end method

.method public run()V
    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    const/4 v1, 0x1

    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(I)V

    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    iget-object v1, p0, LTest$1;->val$y:Ljava/lang/Integer;

    invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V

    return-void
.end method

可以看到, 对于自由变量, annonymous class 是把它作为 "参数" 传递给构造函数. 同时, 对于 int 这种基本类型还应用了常量传播.

它与下面的手写代码是完全相同的:

class MyThread extends Thread {
    Integer mY;
    MyThread(Integer y) {
        this.mY = y;
    }
    public void run() {
        System.out.println(1);
        System.out.println(this.mY);
    }
}

public class Test {
    public static void main(String[] args) {
        int x = 1;
        Integer y = 2;
        new MyThread(y).start();
    }
}

1.2. 自由变量必需是 final

由于自由变量本质是作用构造函数的参数传递的, 所以它必须是 final 的, 因为参数传递本质是一种 copy, 任何一方对自由变量的修改 (对于 primitive 类型, 是修改它的值, 对于引用类型, 是修改它的引用) 都无法被另一方看见, 为了避免困扰, 索性声明为 final 类型

1.3. effective final

Java 1.8 开始, 对于本身就是 "不可变" 类型的自由变量 (即为 effective final), 不必标明 final. 上面的例子中, int 和 Integer 前可以不加 final. 但是, 任何对这些自由变量的修改都会导致编译错误.

public class Test {
    public static void main(String[] args) {
        int x = 1;
        Integer y = 1;
        new Thread() {
            @Override
            public void run() {
                // x = 2; // error: local variables referenced from an inner class must be final or effectively final
                System.out.println(x);
                System.out.println(y);
            }
        }.start();
        // x = 2; // error: local variables referenced from an inner class must be final or effectively final
    }
}

Author: [email protected]
Date: 2019-08-16 Fri 00:00
Last updated: 2022-02-26 Sat 00:02

知识共享许可协议