Fork me on GitHub

内部匿名类

    什么是内部匿名类,为什么它引用外部变量得加final?并且不能重新赋值?

什么是内部匿名类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class NiMing {
public static void main(String[] args) {
final String name = "Haha";
new FunLisenter() {
@Override
public void fun() {
System.out.println(name);
}
}.fun();
}
}
// 外部接口
public interface FunLisenter {
void fun();
}

    什么是内部匿名类呢?上述demo中的:

1
2
3
4
5
6
new FunLisenter() {
@Override
public void fun() {
System.out.println(name);
}
}

    这就是内部匿名类。
    接口不能new,为什么这里被new出来了?因为它是匿名内部类,它是特殊的存在。
    看它的编译后的.class文件。
1
    发现两个Java文件编译后出来了3个class文件。
    这就是特殊的存在,我们反编译这个特别的NiMing$1.class文件:

1
2
3
4
5
6
7
8
final class NiMing$1 implements FunLisenter {
Main$1() {
}
public void fun() {
System.out.println("Haha");
}
}

    发现一个奇怪的类实现了我们的FunLisenter接口,难道new的FunLisenter就是new的这个奇怪的NiMing$1类么?
    深入思考下,为什么叫做匿名?
    对于我们Java层面来说,这个类根本看不到。
    之所以被称之为内部匿名类,是因为:在编译阶段,编译器帮我们以内部类的形式,帮我们implement我们的接口,因此我们才可以以new的方式使用。

内部匿名类引用外部变量为什么得加final

先来一段代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
NiMing main = new NiMing();
main.fun();
}
public void fun() {
// 这里为什么赋值为null,因为避免String常量对效果的影响
final String nameInner = null;
new FunLisenter() {
@Override
public void fun() {
System.out.println(nameInner);
}
}.fun();
}

    为什么内部匿名类能够访问到namelnner?一个方法,就是一个栈帧。对于局部变量来说,方法结束,栈帧弹出,局部变量烟消云散。为什么内部匿名类可以访问?
    直接看反编译的class文件:

1
2
3
4
5
6
7
8
9
class NiMing$1 implements FunLisenter {
NiMing$1(NiMing var1, String var2) {
this.this$0 = var1;
this.val$nameInner = var2;
}
public void fun() {
System.out.println(this.val$nameInner);
}
}

    这个例子不光解释了内部匿名类为什么能够访问局部变量,还展示了持有外部引用的问题。局部变量nameInner,被我们在编译期在生成匿名内部类的时候以参数的形式赋值给了我们内部持有的外部变量了。因此我们调用fun()方法时,就直接使用this.val$nameInner
    为什么要加final呢?
    从Java代码上来看局部变量nameInner和匿名内部类的nameInner是同一个对象。
    但是我们反编译了字节码,发现这二者并非是同一个对象。
    设想一下,如果不加final。在Java的这种设计下,一定会造成这种情况:我们在内部匿名类中重新赋值,但是局部变量并不会同步发生变化。因为按照这种设计,重新赋值,完全就是两个变量!因此为了避免这种情况,索性加上了final。

Your support will encourage me to continue to create!