从垃圾回收机制解析为什么局部内部类只能访问final修饰的局部变量以及为什么加final能解决问题
程序员文章站
2022-04-11 18:44:45
...
我们先稍微看一下代码:
从这里的提示可以看到,必须要将a的修饰符变为final才行。
现在笔者就这一结果做出自己的分析:
首先来说,我们知道,方法被调用时会执行,当执行的时候,方法中的局部变量会加载到栈内存中,方法执
行完毕后局部变量会从栈中被释放(会被垃圾回收器立即回收)。其次,当一个对象被new出来后,new出来的
对象生存再堆(堆中的对象在用完后不会马上被回收),对象的引用存在于栈中,也会立即被收回。 结合以上的
代码我们可以发现,当test方法执行完成后,int类型的a变量立即被回收了,A对象的引用a1也被立即回收了,但
是new出来的A对象还是生存在堆上面,这个时候问题就出来了,一个活着对象持有一个被回收的变量。java作
为一种强类型语言,这种情况是肯定不允许的。
接下来我们分析,为什么加final后就可以了呢?
我们先看下面两段话:
final修饰局部变量
final修饰的局部变量一样需要被显式地赋初始值,因为Java本来就要求局部变量必须被显式地赋初始值。与普通变量不同的是,final修饰的局部变量被赋初始值之后,将不能再被重新赋值。
final修饰符的第一简单的功能就是一旦被赋初始值,将不可改变。
final的另一个简单的功能就是在定义了该final类变量时指定了初始值,且该初始值可以在编译时就被确定下来,系统将不会在静态初始化块中对该类变量赋初始值,而将是在类定义中直接使用该初始化值代替该final变量。
对于一个使用final修饰的变量而言,如果定义该final变量时就指定初始值,而且这个初始值可以在编译时就确定下来,那么这个final变量将不再是一个变量,系统会将其变成“宏变量”处理。所有出现该变量的地方,系统将直接把它当成对应的值处理。
执行“宏替换”的变量
final修饰符的一个重要用途就是定义“宏变量“,当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译的时候就确定下来,那么这个final变量本质上就是一个”宏变量“,编译器会把程序中所用到该变量的地方直接替换成该变量的值。如果被赋的表达式只是基本的算术运算表达式或字符串连接运算,没有访问普通变量,调用方法,Java编译器同样会将这种final变量当成”宏变量“处理。
对于实例变量而言,可以在定义该变量时赋初始值之外,还可以在非静态初始化块、构造器中对它赋初始值,在这三个地方指定初始值的效果基本一样。但对于final实例变量而言,只有在定义该变量时指定初始值才会有”宏变量“的效果,在非静态初始化块、构造器中为final实例变量指定初始值则不会有这种效果。对于普通类变量而言,在定义时指定初始值,在静态初始化块中赋初始值的效果基本一样。但对于final类变量而言,只有在定义final类变量时指定初始值,系统才会对该final类变量执行”宏替换“
文字总是没有代码直观,接着我把上面代码的class文件反编译后得到如下代码:
先将未编译的java代码贴上来以做比较:
package com.itheima;
// 这是测试类
public class Test {
public void test(){
final int a = 10;
A a1 = new A(){
public void show(){
System.out.println(a);
}
};
}
}
package com.itheima;
// 就一个show方法
public interface A {
public void show();
}
反编译字节码文件后得到以下代码:
package com.itheima;
import java.io.PrintStream;
public class Test
{
public void test()
{
int a = 10;
A a1 = new A()
{
public void show()
{
System.out.println(10); //在编译后,匿名内部类中的a直接被替换成了10
}
};
}
}
通过以上分析结合之前的两段问题,我认为,用final修饰后,匿名内部类中所引用的局部变量将以字面值常量的
形式存在,方法运行结束后,虽然方法中的局部变量被回收了,但是在匿名内部类并不受影响。所以加final能解
决之前我们提出的问题。
上一篇: 匿名内部类访问的局部变量为什么必须要用final修饰
下一篇: SpringMVC入门精讲