从垃圾回收机制解析为什么局部内部类只能访问final修饰的局部变量以及为什么加final能解决问题

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 从垃圾回收机制解析为什么局部内部类只能访问final修饰的局部变量以及为什么加final能解决问题

我们先稍微看一下代码:

微信图片_20221110144258.png

从这里的提示可以看到,必须要将a的修饰符变为final才行。


现在笔者就这一结果做出自己的分析:


      首先来说,我们知道,方法被调用时会执行,当执行的时候,方法中的局部变量会加载到栈内存中,方法执


行完毕后局部变量会从栈中被释放(会被垃圾回收器立即回收)。其次,当一个对象被new出来后,new出来的


对象生存再堆(堆中的对象在用完后不会马上被回收),对象的引用存在于栈中,也会立即被收回。 结合以上的


代码我们可以发现,当test方法执行完成后,int类型的a变量立即被回收了,A对象的引用a1也被立即回收了,但


是new出来的A对象还是生存在堆上面,这个时候问题就出来了,一个活着对象持有一个被回收的变量。java作


为一种强类型语言,这种情况是肯定不允许的。


接下来我们分析,为什么加final后就可以了呢?


我们先看下面两段话:

image.png

final修饰的局部变量一样需要被显式地赋初始值,因为Java本来就要求局部变量必须被显式地赋初始值。与普通变量不同的是,final修饰的局部变量被赋初始值之后,将不能再被重新赋值。


final修饰符的第一简单的功能就是一旦被赋初始值,将不可改变。

final的另一个简单的功能就是在定义了该final类变量时指定了初始值,且该初始值可以在编译时就被确定下来,系统将不会在静态初始化块中对该类变量赋初始值,而将是在类定义中直接使用该初始化值代替该final变量。


对于一个使用final修饰的变量而言,如果定义该final变量时就指定初始值,而且这个初始值可以在编译时就确定下来,那么这个final变量将不再是一个变量,系统会将其变成“宏变量”处理。所有出现该变量的地方,系统将直接把它当成对应的值处理。

image.png

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能解

决之前我们提出的问题。

相关文章
|
2月前
|
域名解析 网络协议 开发工具
阿里云DNS常见问题之访问重定向的url访问有问题如何解决
阿里云DNS(Domain Name System)服务是一个高可用和可扩展的云端DNS服务,用于将域名转换为IP地址,从而让用户能够通过域名访问云端资源。以下是一些关于阿里云DNS服务的常见问题合集:
|
2月前
|
缓存 负载均衡 网络协议
阿里云DNS常见问题之某个地域访问不到如何解决
阿里云DNS(Domain Name System)服务是一个高可用和可扩展的云端DNS服务,用于将域名转换为IP地址,从而让用户能够通过域名访问云端资源。以下是一些关于阿里云DNS服务的常见问题合集:
|
1天前
|
存储 算法 Java
性能优化:Java垃圾回收机制深度解析 - 让你的应用飞起来!
Java垃圾回收自动管理内存,防止泄漏,提升性能。GC分为标记-清除、复制、标记-整理和分代收集等算法。JVM内存分为堆、方法区等区域。常见垃圾回收器有Serial、Parallel、CMS和G1。调优涉及选择合适的GC、调整内存大小和使用参数。了解和优化GC能提升应用性能。
11 3
|
11天前
|
JavaScript 前端开发 算法
【JavaScript】JavaScript 垃圾回收机制深度解析:内存管理的艺术
JavaScript的内存管理和垃圾回收机制涉及栈内存与堆内存、引用计数与标记-清除算法。栈内存存储基本类型和函数调用时的局部变量,而堆内存用于复杂数据类型,如对象和数组。垃圾回收主要通过标记-清除策略,处理不再被引用的对象。现代引擎如V8使用分代收集和增量标记等优化方法,减少停顿并提升性能。开发者应注意避免内存泄漏,如及时解除引用、管理DOM引用和定时器,使用WeakMap和WeakSet等。理解这些原理和最佳实践对于编写高效代码至关重要。
24 5
|
2月前
|
存储 Java 程序员
【Python 的内存管理机制专栏】深入解析 Python 的内存管理机制:从变量到垃圾回收
【5月更文挑战第18天】Python内存管理关乎程序性能与稳定性,包括变量存储和垃圾回收。变量存储时,如`x = 10`,`x`指向内存中值的引用。垃圾回收通过引用计数自动回收无引用对象,防止内存泄漏。了解此机制可优化内存使用,避免循环引用等问题,提升程序效率和稳定性。深入学习内存管理对成为优秀Python程序员至关重要。
【Python 的内存管理机制专栏】深入解析 Python 的内存管理机制:从变量到垃圾回收
|
6天前
|
算法 Java 数据库连接
Java垃圾回收机制的深入解析
Java垃圾回收机制的深入解析
|
2月前
|
C++
C++ 类的访问修饰符:深入解析
C++ 类的访问修饰符:深入解析
14 1
|
2月前
|
存储 编译器 C语言
C陷阱:数组越界遍历,不报错却出现死循环?从内存解析角度看数组与局部变量之“爱恨纠葛”
在代码练习中,通常会避免数组越界访问,但如果运行了这样的代码,可能会导致未定义行为,例如死循环。当循环遍历数组时,如果下标超出数组长度,程序可能会持续停留在循环体内。这种情况的发生与数组和局部变量(如循环变量)在内存中的布局有关。在某些编译器和环境下,数组和局部变量可能在栈上相邻存储,数组越界访问可能会修改到循环变量的值,导致循环条件始终满足,从而形成死循环。理解这种情况有助于我们更好地理解和预防这类编程错误。
36 0
|
2月前
|
安全 Java 编译器
接口之美,内部之妙:深入解析Java的接口与内部类
接口之美,内部之妙:深入解析Java的接口与内部类
46 0
接口之美,内部之妙:深入解析Java的接口与内部类
|
2月前
|
存储 缓存 算法
深度解析JVM世界:垃圾判断和垃圾回收算法
深度解析JVM世界:垃圾判断和垃圾回收算法

推荐镜像

更多