为什么在外部类方法内的内部类所用变量需要是final的

Gelsey ·
更新时间:2024-11-13
· 612 次阅读

     

参考资料:https://www.cnblogs.com/z-sm/articles/7058864.html

参考资料:https://www.iteye.com/blog/feiyeguohai-1500108

分析内部类对象的生命周期会超过局部变量的生命周期。                                                                                                              局部变量的生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时,退栈,这些局部变量全部死亡。而内部类对象生命周期与其它类一样:自创建一个匿名内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象的内存,它才会死亡(被JVM垃圾回收)。所以完全可能出现的一种情况是:成员方法已调用结束,局部变量已死亡,但匿名内部类的对象仍然活着。方法执行完后局部变量销毁了,但内部类对象可能仍要访问该局部变量,这时就会出错

解决方法:

Java解决方法是将局部变量复制一份到内部类,这样方法执行完后匿名内部类里仍可使用该变量。但这种实现方式还需要确保在程序员看来他们是同一个,即值始终一样,怎么做到?

法1:同步。当匿名内部类内对复制值做修改时同步回局部变量、在方法内的匿名内部类之后修改局部变量时复制值也跟着改,这种实现上困难且麻烦。

法2:不用同步,直接将局部变量声明为final的以使其不可变。Java就是用此法。

当变量是final时,通过将final局部变量"复制"一份,复制品直接作为局部内部中的数据成员.这样:当局部内部类访问局部变量 时,其实真正访问的是这个局部变量的"复制品"(即:这个复制品就代表了那个局部变量).因此:当运行栈中的真正的局部变量死亡时,局部内部类对象仍可以 访问局部变量(其实访问的是"复制品"),给人的感觉:好像是局部变量的"生命期"延长了。核心的问题是:怎么才能使得:访问"复制品"与访问真正的原始的局部变量,其语义效果是一样的呢?(语义效果就是虽然表面上两个变量同名,但实际上是不一样的,更改其中一个另一个不会有任何影响,除非能做到同步,但做到同步又很困难,所以使用final,都无法再改变,这样数据就保持一致性,语义上就是操作的是同一个变量,实际上并不是,但达到了这个效果。)

当变量是final时,若是基本数据类型,由于其值不变,因而:其复制品与原始的量是一样.语义效果相同.(若:不是final,就无法保证:复制品与原始变量保持一致了,因为:在方法中改的是原始变量,而局部内部类中改的是复制品)

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

一句话:这个规定是一种无可奈何.也说明:程序设计语言的设计是受到实现技术的限制的.这就是一例. 因为:我就看到不少人都持这种观点:设计与想法是最重要的,实现的技术是无关紧要的,只要你作出设计与规定,都能实现.
现在我们来看,如果我要实现一个在一个方法中匿名调用ABSClass的例子:

public static void test(final String s){      //或final String s = "axman";   ABSClass c = new ABSClass(){    public void m(){       int x = s.hashCode();       System.out.println(x);    }   };   //其它代码.  }


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

public static void test(final String s){      //或final String s = "axman";    class OuterClass$1 extends ABSClass{    private final String s;    public OuterClass$1(String s){       this.s = s;       }    public void m(){       int x = s.hashCode();       System.out.println(x);    }   };   ABSClass c = new OuterClass$1(s);   //其它代码.  }

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

public static void test(String s){      //或String s = "axman";   ABSClass c = new ABSClass(){    public void m(){      s = "other";    }   };   System.out.println(s);  }


 就会编译成:

  public static void test(String s){      //或String s = "axman";   class OuterClass$1 extends ABSClass{    private String s;    public OuterClass$1(String s){       this.s = s;       }    public void m(){      s = "other";    }   };    ABSClass c = new OuterClass$1 (s);   }

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

  public static void test(String s){      //或String s = "axman";   ABSClass c = new ABSClass(){    public void m(){      s = "other";    }   };   System.out.println(s);  }

在语法上是一个s,在内部类中被改变了,但结果打印的出来的你认为是同一的s却还是原来的"axman",
你能接收这样的结果吗?

所以final从语法上约束了实际上两个不同变量的一致性(表现为同一变量).

总结:对于外部类的方法中的内部类(局部内部类,匿名内部类)对象所访问的属于方法内的局部变量,都会拷贝成为该对象的一个数据成员,无论是否为final,都会拷贝,核心是保证数据一致性才必须添加final,同步实现上困难麻烦,所以Java直接用final来保证数据一致性。


作者:Mr.FoxzZZ



内部类 final 类方法 方法 变量

需要 登录 后方可回复, 如果你还没有账号请 注册新账号