匿名内部类使用外面的类为什么要用final型
- 从程序设计语言的理论上:局部内部类(即:定义在方法中的内部类),由于本身就是在方法内部(可出现在形式参数定义处或者方法体处),因而访问方法中的局部变量(形式参数或局部变量)是天经地义的.是很自然的
- 为什么JAVA中要加上一条限制:只能访问final型的局部变量?
- JAVA语言的编译程序的设计者当然全实现:局部内部类能访问方法中的所有的局部变量(因为:从理论上这是很自然的要求),但是:编译技术是无法实现的或代价极高.
- 困难在何处?到底难在哪儿?
局部变量的生命周期与局部内部类的对象的生命周期的不一致性!
- 设方法f被调用,从而在它的调用栈中生成了变量i,此时产生了一个局部内部类对象inner_object,它访问了该局部变量i .当方法f()运行结束后,局部变量i就已死亡了,不存在了.但:局部内部类对象inner_object还可能 一直存在(只能没有人再引用该对象时,它才会死亡),它不会随着方法f()运行结束死亡.这时:出现了一个"荒唐"结果:局部内部类对象inner_object要访问一个已不存在的局部变量i!
- 如何才能实现? 当变量是final时,通过将final局部变量"复制"一份,复制品直接作为局部内部中的数据成员.这样:当局部内部类访问局部变量时,其实真正访问的是这个局部变量的"复制品"(即:这个复制品就代表了那个局部变量).因此:当运行栈中的真正的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是"复制品"),给人的感觉:好像是局部变量的"生命期"延长了.
局部变量的生命周期与局部内部类的对象的生命周期的不一致性!
那么:核心的问题是:怎么才能使得:访问"复制品"与访问真正的原始的局部变量,其语义效果是一样的呢?
当变量是final时,若是基本数据类型,由于其值不变,因而:其复制品与原始的量是一样.语义效果相同.(若:不是final,就无法保证:复制品与原始变量保持一致了,因为:在方法中改的是原始变量,而局部内部类中改的是复制品)
当变量是final时,若是引用类型,由于其引用值不变(即:永远指向同一个对象),因而:其复制品与原始的引用变量一样,永远指向同一个对象(由于是final,从而保证:只能指向这个对象,再不能指向其它对象),达到:局部内部类中访问的复制品与方法代码中访问的原始对象,永远都是同一个即:语义效果是一样的.否则:当方法中改原始变量,而局部内部类中改复制品时,就无法保证:复制品与原始变量保持一致了(因此:它们原本就应该是同一个变量.)
一句话:这个规定是一种无可奈何.也说明:程序设计语言的设计是受到实现技术的限制的.这就是一例. 因为:我就看到不少人都持这种观点:设计与想法是最重要的,实现的技术是无关紧要的,只要你作出设计与规定,都能实现.
现在我们来看,如果我要实现一个在一个方法中匿名调用ABSClass的例子:
public static void test(final String s){
ABSClass c = new ABSClass(){
public void m(){
int x = s.hashCode();
System.out.println(x);
}
};
//其它代码.
}
从代码上看,在一个方法内部定义的内部类的方法访问外部方法内局部变量或方法参数,是非常自然的事,但内部类编译的时候如何获取这个变量,因为内部类除了它的生命周期是在方法内部,其它的方面它就是一个普通类。那么它外面的那个局部变量或方法参数怎么被内部类访问?编译器在实现时实际上是这样的:
为什么匿名内部类和局部内部类只能访问final变量
是变量的作用域的问题,因为匿名内部类是出现在一个方法的内部的,如果它要访问这个方法的参数或者方法中定义的变量,则这些参数和变量必须被修饰为final。因为虽然匿名内部类在方法的内部,但实际编译的时候,内部类编译成Outer.Inner,这说明内部类所处的位置和外部类中的方法处在同一个等级上,外部类中的方法中的变量或参数只是方法的局部变量,这些变量或参数的作用域只在这个方法内部有效。因为编译的时候内部类和方法在同一级别上,所以方法中的变量或参数只有为final,内部类才可以引用。
Java代码:
public class MyClass {
public MyClass() {
final int finalValue = 10;
int not$Final = 20;
MyInterface myInterface = new MyInterface() {
public void functionWithoutPara() {
//compile Error
//System.out.println(noFinal);
System.out.println(finalValue);
}
public void functionWithPara(int num) {
System.out.println("The parameter " + num
+ " has been passed by the method");
}
};
myInterface.functionWithoutPara();
myInterface.functionWithPara(not$Final);
System.out.println(myInterface.getClass().getName());
}
public static void main(String[] args) {
new MyClass();
}
}
二、为什么局部内部类只能访问final变量
简单的来说是作用域的问题。就好像方法外面做的事情并不能改变方法内才定义的变量,因为你并不知道方法里面这个时候已经存在了这个局部变量了没有。在这个内部类中方法里面的本地变量是失效的,也就是不在作用域内,所以是不能够访问的
但是为什么这里用final却又可以访问呢?
因为Java采用了一种copy->local->variable的方式来实现,也就是说把定义为final的局部变量拷贝过来用,而引用的也可以拿过来用,只是不能重新赋值。从而造成了可以access->local->variable的假象,而这个时候由于不能重新赋值,所以一般不会造成不可预料的事情发生
三、如果定义一个局部内部类,并且局部内部类使用了一个在其外部定义的对象,为什么编译器会要求其参数引用是final呢?
注意:局部内部类,包括匿名内部类。
原因如下:
abstract class ABSClass{
public abstract void print();
public class Test2{
public static void test(final String s){//一旦参数在匿名类内部使用,则必须是final
ABSClass c=new ABSClass(){
public void print(){
System.out.println(s);
}
};
c.print();
}
public static void main(String[] args){
test("Hello World!");
}
}
}
- JVM中每个进程都会有多个根,每个static变量,方法参数,局部变量,当然这都是指引用类型.基础类型是不能作为根的,根其实就是一个存储地址.垃圾回收器在工作时先从根开始遍历它引用的对象并标记它们,如此递归到最末梢,所有根都遍历后,没有被标记到的对象说明没有被引用,那么就是可以被回收的对象(有些对象有finalized方法,虽然没有引用,但JVM中有一个专门的队列引用它们直到finalized方法被执行后才从该队列中移除成为真正没有引用的对象,可以回收,这个与本主题讨论的无关,包括代的划分等以后再说明).这看起来很好.
- 但是在内部类的回调方法中,s既不可能是静态变量,也不是方法中的临时变量,也不是方法参数,它不可能作为根,在内部类中也没有变量引用它,它的根在内部类外部的那个方法中,如果这时外面变量s重指向其它对象,则回调方法中的这个对象s就失去了引用,可能被回收,而由于内部类回调方法大多数在其它线程中执行,可能还要在回收后还会继续访问它.这将是什么结果?
- 而使用final修饰符不仅会保持对象的引用不会改变,而且编译器还会持续维护这个对象在回调方法中的生命周期.所以这才是final变量和final参数的根本意义.
匿名内部类使用外面的类为什么要用final型的更多相关文章
- 构造方法为private与类修饰符为final
构造方法为private的:在这个类外1:不能继承这个类2:不能用new来产生这个类的实例 在这个类内:1:可以继承这个类2:可以用new来产生这个类的实例 类修饰符为final的:在这个类外1:不能 ...
- java定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积
需求如下:(1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积. (2)定义一个类PassObject,在类中定义一个方法pri ...
- 类,接口,final
继承extends 子类实例化过程: 子类中所有的构造方法默认都会访问父类中空参的构造方法 因为每一个子类的构造方法的提议行都有一条默认的语句super(); this super final修饰类, ...
- 在类的外面调用类的private函数
将基类中的虚函数定义为public,在派生类中将该虚函数定义为private,则可以通过基类指针调用派生类的private函数 #include <iostream> #include & ...
- 匿名内部类为什么访问外部类局部变量必须是final的?
1.内部类里面使用外部类的局部变量时,其实就是内部类的对象在使用它,内部类对象生命周期中都可能调用它,而内部类试图访问外部方法中的局部变量时,外部方法的局部变量很可能已经不存在了,那么就得延续其生命, ...
- 为什么匿名内部类只能访问其所在方法中的final类型的局部变量?
大部分时候,类被定义成一个独立的程序单元.在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类. class Outer { priv ...
- 匿名内部类可以访问的变量---静态成员变量和final修饰的局部变量
在学习多线程的时候用到了匿名内部类,匿名内部类可以访问static静态成员变量或者final修饰的局部变量. 匿名内部类在编译之后会生成class文件,比如Test内的第一个匿名内部类编译之后就是Te ...
- 匿名内部类中不能修改int变量时、final int i 不能改变i的值时、或 i++线程不安全。使用AtomicInteger;
在匿名内部类或某某情况下中引入的变量必须是Final最终型的:这时还想要去修改这个变量就需要使用到AtomicInteger这个类了: AtomicInteger CarSize = new Atom ...
- [多问几个为什么]为什么匿名内部类中引用的局部变量和参数需要final而成员字段不用?(转)
昨天有一个比较爱思考的同事和我提起一个问题:为什么匿名内部类使用的局部变量和参数需要final修饰,而外部类的成员变量则不用?对这个问题我一直作为默认的语法了,木有仔细想过为什么(在分析完后有点印象在 ...
随机推荐
- jvm(三):对象
关于对象,我们需要面对的问题主要有对象的创建,对象在内存中的布局,对象的结构,对象的访问定位. 对象的创建 对象的创建过程如下图所示: 其主要步骤有:给对象分配内存,初始化对象,执行构造方法. 在对象 ...
- Linux 软件安装到 /usr,/usr/local/ 还是 /opt 目录区别
Linux 的软件安装目录是也是有讲究的,理解这一点,在对系统管理是有益的 /usr:系统级的目录,可以理解为C:/Windows/, /usr/lib理解为C:/Windows/System32./ ...
- 背景重复样式background-repeat
一.background-repeat属性 在CSS中,使用background-repeat属性可以设置背景图像是否平铺,并且可以设置如何平铺. 语法: background-repeat:取值; ...
- 点(x1, y1)关于点(x0, y0)逆时针旋转a度后的坐标求解
问题描述: 求点(x1, y1)关于点(x0, y0)逆时针旋转a度后的坐标 思路: 1.首先可以将问题简化,先算点(x1, y1)关于源点逆时针旋转a度后的坐标,求出之后加上x0,y0即可. 2.关 ...
- 轻松理解AOP问题
先说一个Spring是什么吧,大家都是它是一个框架,但框架这个词对新手有点抽象,以致于越解释越模糊,不过它确实是个框架的,但那是从功能的角度来定义的,从本质意义上来讲,Spring是一个库,一个Jav ...
- AsyncLocal 与 async await
大家来看一张图 先猜猜看为什么会这样 关于async await的原理 建议查看 https://blog.csdn.net/brook_shi/article/details/50803957 这篇 ...
- lodop打印收费小票
//收费系统打印机功能:收费/退费,需要使用到lodop var LODOP;//打印机 $(function () { //初始化 $("body").append('<o ...
- iOS Push详述,了解一下?
WeTest 导读 本文主要对iOS Push的在线push.本地push及离线(远程)push进行梳理,介绍了相关逻辑,测试时要注意的要点以及相关工具.小小的Push背后蕴藏着大大的逻辑! Push ...
- PHP 常用函数集合
PHP is_numeric() 函数 由 陈 创建, 最后一次修改 2016-12-02 定义和用法 is_numeric() - 检测变量是否为数字或数字字符串 语法 bool is_numeri ...
- python学习之路网络编程篇(第四篇)
python学习之路网络编程篇(第四篇) 内容待补充