内部类访问局部变量为什么必须要用final修饰

看了大概五六篇博客, 讲的内容都差不多, 讲的内容也都很对, 但我觉得有些跑题了

略叙一下

String s = "hello";
class Inner implements Runnable {
public void run() {
System.out.println(s);
}
}
// s = "world"; Thread t = new Thread(new Inner());
t.start();
  • jdk1.8及后续版本可以不显式声明final

所以以上代码在Java8是可以编译运行的, 但是如果在Inner类声明后再对s赋值, 就会报此错误

Local variable s defined in an enclosing scope must be final or effectively final

所以, 尽管Java8以后不用显式声明为final, 但是内部类引用的变量实际上还是final的

  • 内部类中存在要用到的外部变量的引用, 存于内部类成员变量中, 在内部类构造时传入

这点可以通过反射或者直接查看字节码证实

内部类中有了所需变量的引用, 那么在方法执行完之后, 依然可以访问到此变量

可以说是摆脱了方法内部变量作用域对内部类变量使用的限制

(啰嗦解释一句, 如果内部没有保存引用, 那么方法执行完毕, 方法内所有本地变量随方法所在栈帧销毁, 内部类将无法获取这一引用)

  • 为了保证内部的引用与外部的引用指向一致, 所以必须是final

这一点最接近题意

但是他们大多只解释了为了使内部与外部保持一致

  • 我的理解

使内部与外部保持一致不是必要的

public static void main(String[] args) {
String name = "lengchu";
class Inner {
String name; Inner(String name) {
this.name = name;
} void sayHi() {
System.out.println("hello " + name);
}
} name = "lenchu";
new Inner(name).sayHi();
}

开头那段代码编译成字节码后和这段其实差不多

内部的name字段和构造器是编译器为我们加上的, 实例化的时候构造器里的值也是编译器给我们传过去的

如果把编译器为我们做的这些我们自己手动实现了, 再来看这个所谓的使内部与外部保持一致的需求是多么的无理, 完全就没必要嘛

  • 所以到底为什么呢

个人理解

为了消除代码的不确定性, 保持代码的简单易懂

怎么说呢? 我们把上面实例化和调用那段代码稍作修改

Inner i = new Inner(name);
name = "lenchu";
i.sayHi();

因为构造器和内部字段是我们自己实现的, 所以我们可以轻松判断此时修改name对i的值是没有影响的

但是如果我们不自己实现这些呢

public static void main(String[] args) {
String name = "lengchu";
class Inner {
void sayHi() {
System.out.println("hello " + name);
}
} Inner i = new Inner();
// name = "lenchu";
i.sayHi();
}

假如可以对name重新赋值

那么name运行的时候name到底是谁呢

从上面的分析我们可以知道name的值应该是内部类实例化时的值

但是从执行顺序上看难免多少会有些歧义

干脆就不让他改了吧--!(个人见解不代表官方意见)


强行对比一波js的上下文环境

function fn() {
let i = 0
let ret = function() {
return i++
}
return ret
} let i = 10
let inc = fn()
inc() // 0

Java的运行是基于方法栈, 而js则是基于执行上下文栈

二者有很多相似之处, 略叙一下

Java的每个方法调用都会往方法栈里推入一帧,

这一栈帧里存放着方法内部声明的局部变量, 方法参数等,

方法执行完毕时, 栈帧出栈, 栈帧里的内容随之销毁

js的上下文栈也与之类似

  • js的栈帧未必会随着上下文执行完毕而销毁

如上例,

fn执行完毕, 他的上下文就不会销毁,

如果销毁, 那么ret执行的时候变量i就找不到其所指了

栈帧销毁, 变量找不到引用, 像极了Java方法栈帧销毁, 内部类变量找不到引用

  • 栈帧未必销毁, 那怎么知道当前上下文所用的帧呢

标记, 标记当前上下文所用的栈帧为active

同时还会标记每一帧的上一帧,

这样就以另一种方式解决了类似的问题

内部类访问局部变量为什么必须要用final修饰的更多相关文章

  1. 为什么内部类访问的外部变量需要使用final修饰

    因为生命周期的原因.方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象.首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而 ...

  2. 为什么Java匿名内部类访问的外部局部变量或参数需要被final修饰

    大部分时候,类被定义成一个独立的程序单元.在某些情况下,也会把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类也被称为外部类. class Outer { priv ...

  3. 内部类访问局部变量的时候,为什么变量必须加上final修饰

    这里的局部变量就是在类方法中的变量,能访问方法中变量的类当然也是局部内部类了.我们都知道,局部变量在所处的函数执行完之后就释放了,但是内部类对象如果还有引用指向的话它是还存在的.例如下面的代码: cl ...

  4. final运用于内部类访问局部变量

    final运用于内部类访问局部变量 public void mRun( final String name){ new Runnable() { @Override public void run() ...

  5. 匿名/局部内部类访问局部变量时,为什么局部变量必须加final

    我们都知道方法中的匿名/局部内部类是能够访问同一个方法中的局部变量的,但是为什么局部变量要加上一个final呢? 首先我们来研究一下变量生命周期的问题,局部变量的生命周期是当该方法被调用时在栈中被创建 ...

  6. 为什么java内部类访问局部变量必须声明为final?

    https://blog.csdn.net/z55887/article/details/49229491 先抛出让我疑惑了很久的一个问题 编程时,在线程中使用局部变量时候经常编译器会提示:局部变量必 ...

  7. java-内部类访问特点-私有成员内部类-静态成员内部类-局部内部类访问局部变量

    1.内部类访问特点: - 内部类可以直接访问外部类的成员,包括私有. - 外部类要访问内部类的成员,必须创建对象. - 外部类名.内部类名 对象名 = 外部类对象.内部类对象: - 例: class ...

  8. 为什么内部类调用的外部变量必须是final修饰的?

    感谢原文:https://blog.csdn.net/u010393325/article/details/80643636 因为生命周期的原因.方法中的局部变量,方法结束后这个变量就要释放掉,fin ...

  9. 内部类访问局部变量时,为什么需要加final关键字

    是变量的作用域的问题,因为匿名内部类是出现在一个方法的内部的,如果它要访问这个方法的参数或者方法中定义的变量,则这些参数和变量必须被修饰为final.因为虽然匿名内部类在方法的内部,但实际编译的时候, ...

随机推荐

  1. 33 ArcToolBox学习系列之数据管理工具箱——投影与变换(Projections and Transformations)未完待续……

    工具箱位置 打开ArcToolBox,找到工具集Projections and Transformations,位置如下:ArcToolbox--Data Management Tools--Proj ...

  2. js中的观察者模式

    什么事观察者模式: 这是一种创建松散耦合代码的技术.它定义对象间 一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知.由主体和观察者组成,主体负责发布事件,同时观察者通过 ...

  3. App功能测试

    App功能测试 1.  运行 1)App安装完成后的试运行,可正常打开软件. 2)App打开测试,是否有加载状态进度提示. 3)App页面间的切换是否流畅,逻辑是否正确. 4)注册 同表单编辑页面 用 ...

  4. 获取安卓应用APK包名的方法

    应用商店按照符合Android标准的原则进行设计,使用包名(Package Name)作为应用的唯一标识.即:包名必须唯一,一个包名代表一个应用,不允许两个应用使用同样的包名.包名主要用于系统识别应用 ...

  5. Batch入门教程丨第一章:部署与Hello World!(下)

    在上期分享的内容中,我们已经掌握了基础理论知识,今天我们将继续了解和学习与Windows Batch有关的知识和编程方法,如何编写和运行Windows Batch程序,脚本语言的入门方式等,从而能够更 ...

  6. JavaWeb开发SSM框架搭建详解

    1.需要用到的jar包:由于很多的jar包不好下载,我直接上传到百度网盘: 很多,而且不好下载,我已经整理好好了: 链接:https://pan.baidu.com/s/1iIFprmstp86uKz ...

  7. [Swift]LeetCode305. 岛屿的个数 II $ Number of Islands II

    A 2d grid map of m rows and n columns is initially filled with water. We may perform an addLand oper ...

  8. Java Jvm运行机制原理

    一:简介 在学习Java虚拟机之前,也就是Jvm之前,我想大家能够带着问题去学习,这样的话,大家学习起来也会比较有所获! 1.Java虚拟机(Jvm)是什么? 2.Java虚拟机是用来干什么的? 3. ...

  9. 【Storm篇】--Storm从初始到分布式搭建

    一.前述 Storm是一个流式处理框架,相比较于SparkStreaming是一个微批处理框架,hadoop是一个批处理框架. 二 .搭建流程 1.集群规划 Nimbus    Supervisor  ...

  10. Mac下 .bash_profile 和 .zshrc 两者之间的区别

    这是我碰到的需要 source 之后才能使用环境变量的问题,我就不细究了,说说我的看法. .bash_profile 中修改环境变量只对当前窗口有效,而且需要 source ~/.bash_profi ...