最近在写一些匿名内部类的时候会感觉有些难以理解,所以重新复习了一下这部分内容。 发现了一些之前没注意到的点——匿名内部类的形参必须加final前缀(除非匿名内部类没使用它)

那么,怎么理解这个事情的背后原理呢?

内部类运作方式

首先,思考内部类是怎么运行的。我们知道在内部类编译成功后,它会产生一个新的class文件。

该class文件仅仅只保留了对外部类的引用。

举个例子,当外部类传入的参数需要被内部类调用时,直接看起来好像就是被直接调用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class OuterClass{
public void method(final String name,final int age){
class InnerClass{
public void show(){
System.out.println("your name is " + name + " and age is " + age);

}
}
InnerClass in = new InnerClass();
in.show();
}
}

但是实际上name并不是被内部类直接调用的,实际上java编译后它长这样:

1
2
3
4
5
6
7
8
9
10
public class OuterClass$InnerClass {
public InnerClass(String name,int age){
this.InnerClass$name = name;
this.InnerClass$age = age;
}

public void show(){
System.out.println("your name is " + this.InnerClass$name + " and age is " + this.InnerClass$age );
}
}

所以,从以上代码看来,内部类并不是直接调用方法传进来的参数,而是内部类将传进来的参数通过自己的构造器备份到了自己的内部,自己内部的方法调用的实际是自己的属性而不是外部类方法的参数!
这样理解就很容易得出为什么要用final了,假设内部类修改这些参数的值,结果原参数的值却没有变化,这就影响了参数的一致性。从编程人员的角度来看这个参数是一样的,但是假设在内部类中改掉参数的值,但是外部调用的时候又发现值其实没有被改掉,这可能让人感到很困惑,所以为了避免这种尴尬的问题存在,所以编译器设计人员把内部类能够使用的参数设定为必须是final的。

匿名内部类运作方式

OK,匿名内部类相比内部类有什么区别呢——它没有名字。

没名字,所以它是用默认的无参构造器构造,如果需要参数,那么就给他带参数的构造器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Outer { 
    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner("Inner"); 
        System.out.println(inner.getName()); 
    } 
 
    public Inner getInner(final String name) { 
        return new Inner(name) { 
            private String nameStr = name; 
 
            public String getName() { 
                return nameStr; 
            } 
        }; 
    } 

 
abstract class Inner { 
    Inner(String name) { 
        System.out.println(name); 
    } 
 
    abstract String getName()


看,这里getInner方法的参数加了final,理由和之前内部类部分加final的理由是一样的。如果内部类的改变不能影响到外部,那就干脆别动它!

有时候也可以不加final

什么时候可以不加final呢?如果内部类没有使用它就可以不加final了,这很好理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Outer { 
    public static void main(String[] args) { 
        Outer outer = new Outer(); 
        Inner inner = outer.getInner(1); 
        System.out.println(inner.f()); 
    } 
 
    public static Inner getInner(int i) { 
        return new Inner(i) { 
            public void f(){
System.out.println("f()");
}
        }; 
    } 

 
abstract class Inner { 
    Inner(int i) { 
        System.out.println(i); 
    } 
 
    abstract void f()