最近在写一些匿名内部类的时候会感觉有些难以理解,所以重新复习了一下这部分内容。 发现了一些之前没注意到的点——匿名内部类的形参必须加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 () ; }