[关闭]
@ghimi 2018-09-15T09:26:35.000000Z 字数 3390 阅读 911

Java 中的匿名内部类是如何实现的?

Java


先定义一个接口:

  1. public interface MyInterface{
  2. void doSomething();
  3. }

然后创建这个接口的匿名子类:

  1. public class TryUsingAnonymousClass{
  2. public void useMyInterface(){
  3. final Integer number = 123;
  4. System.out.println(number);
  5. MyInterface myInterface = new MyInterface(){
  6. @Override
  7. public void doSomething(){
  8. System.out.println(number);
  9. }
  10. };
  11. myInterface.doSomegthing();
  12. System.out.println(number);
  13. }
  14. }

这个匿名子类会被编译成一个单独的类,反编译的结果是这样的:

  1. // 匿名内部类的反编译结果
  2. class TryUsingAnonymousClass$1
  3. implements MyInterface{
  4. private final TryUsingAnonymousClass this$0;
  5. private fianl Integer paramInteger;
  6. TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0,Integer paramInteger){
  7. this.this$0 = this$0;8| this.paramInteger = paramInteger;
  8. }
  9. public void doSomething(){
  10. System.out.println(this.paramInteger);
  11. }
  12. }

可以看到外部类和名为 number 的局部变量是作为构造方法的参数传入匿名内部类的

为什么局部变量要作为内部类构造方法的参数传入?

为了解决:局部变量的生命周期与局部内部类的对象的生命周期不一致的问题
a. 问题:设方法 useMyInterface 被调用,从而在它的调用栈中生成了变量 number ,此时产生了一个局部内部类对象 myInterface ,它访问了该局部变量 number.当方法 useMyInterface 运行结束后,局部变量 number 就已经死亡了,不存在了.但是局部内部类对象 myInterface 还可能一直存在(只能没有人在引用该对象时,它才会死亡),它不会随着方法 useMyInterface 运行结束死亡,这是出现了一个荒唐的"结果":局部内部类对象 myInterface 要访问一个已不存在的局部变量 number

b. 解决方法: 通过讲局部变量 "复制" 一份,复制品直接作为局部内部类的数据成员.这样:当局部内部类访问局部变量时,其实真正访问的是这个局部变量的"复制品"(即:这个复制品就代表了那个局部变量).因此:当运行栈中的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是复制品),给人的感觉:好像是局部变量的"生命期"延长了.

c. 为什么会与 fianl 有关系?
为了保证局部变量和内部类中的复制品的数据一致性
若是 基本数据类型,当变量是 final 时,由于其值不变,因而:其复制品与原始的量是一样的.语义效果相同.
若:不是 final ,就无法保证:复制品语原始变量保持一致了,因为:在方法中改的是原始变量,而局部内部类中改的是复制品.若是引用类型,当变量是 final 时,由于其引用值不变(即:永远指向同一个对象),因而:其复制品与原始的引用变量一样,永远指向同一个对象(由于是 final ,从而保证:只能指向这个对象,再不能指向其他),达到:局部内部类中访问的复制品与方法代码中的原始对象,永远都是同一个即:语义效果是一样的.否则:当方法中原始变量,而局部内部类中改复制品时,就无法保证:复制品与原始变量保持一致了(因此:它们原本就应该是同一个变量.)

例子

现在我们来看,如果我要实现一个在一个方法中匿名调用 ABCClass 的例子:

  1. public static void test(final String str){
  2. // 或 final String str = "axman";
  3. ABSClass c = new ABCClass(){
  4. public void m(){
  5. int x = s.hashCode();
  6. System.out.println(x);
  7. }
  8. }
  9. //其他代码
  10. }

从代码上看,在一个方法内部定义的内部类的方法访问外部方法内局部变量或方法参数,是非常自然的的事,但内部类变异的时候如何获取这个变量,因为内部类除了它的生命周期实在方法内部,其他的方面它就是一个普通类.那么它外面的那个局部变量或方法参数怎么被内部类访问?编译器在实现时实际上是这样的:

  1. public static void test(final String s){
  2. // 或 final String s = "axman";
  3. class OuterClass$1 extends ABSClass {
  4. private final String s;
  5. public OuterClass$1(String s){
  6. this.s = s;
  7. }
  8. public void m(){
  9. int x = s.hashCode();
  10. System.out.println(x);
  11. }
  12. }
  13. };
  14. ABSClass c = new OuterClass$1(s);
  15. // 其他代码

即外部类的变量作为构造方法的参数传给了内部类的私有成员.
假如没有 final , 那么:

  1. public static void test(String s){
  2. // 或 String s = "axman";
  3. ABSClass c = new ABSClass(){
  4. public void m(){
  5. s = "other";
  6. }
  7. };
  8. System.out.println(s);
  9. }

就会编译成:

  1. public static void test(String s){
  2. // 或 String s = "axman";
  3. class OuterClass$1 extends ABSClass{
  4. private String s;
  5. public OuterClass$1(String s){
  6. this.s = s;
  7. }
  8. public void m(){
  9. s = "other";
  10. }
  11. };
  12. ABSClass c = new OuterClass$1(s);
  13. }

内部类的 s 重新指向 "other" 并不影响 test 的参数或外部定义的那个 s.同理如果外部的 s 重新复制内部类的 s 也不会跟着改变.
而你看到的

  1. public static void test(String s){
  2. // 或 Stirng s = "axman";
  3. ABSClass c = new ABSClass(){
  4. public void m(){
  5. s = "other";
  6. }
  7. };
  8. System.out.prinln(s);
  9. }

在语法上是一个 s ,在内部类中被改变了,但结果打印出来的你认为是同一的 s 却还是原来的 "axman",你能接受这样的结果吗?
所以final 从语法上约束类实际上这两个不同变量的一致性(表现为同一常量)

jdk1.8版本下的新变化

1.8以后,并非不用是 final 的,而是在编译期间要求值不发生变化.在你的代码中,如果 user 的值发生变化,就会出错.
编译如下代码:

  1. public class TestFinal{
  2. // 这样代码就不能通过编译了
  3. //这里不用 final User user,但是 user 不能改变
  4. public void test(User user){
  5. user = new User();//user 变化了,所以报错
  6. user.setName("zhaoyang");
  7. (new Thread(){
  8. public void run(){
  9. System.out.println("user.name--->"+user.name);// 报错
  10. }
  11. }
  12. ).start();
  13. }
  14. public static void main(String[] args){
  15. User user = new User();
  16. user.setId(007);
  17. user.setName("zhaoyang");
  18. TestFinal testFinal = new TestFinal();
  19. testFinal.test(user);
  20. }
  21. }

总结:由于局部内部类的生命周期和局部变量的生命周期不相同,导致局部内部类在使用局部变量时需要在局部内部类中新建一份副本,而为了保证在方法内局部变量和内部类中它的副本的一致性,需要将该变量声明为 final 常量.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注