内部类访问局部变量为什么必须要用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. python爬虫第三天

        DebugLog实战         有时候我们需要在程序运行时,一边运行一边打印调试日志.此时需要开启DebugLog.         如何开启:         首先将debugleve ...

  2. IDEA环境下SSM整合------注解开发

    根据前一篇文章的步骤,目前项目进度应该是:核心过滤器配置完成.DispatcherServlet和ContextLoader配置完成.数据库dataSource配置完成.视图解析器配置完成.Mappe ...

  3. python Ajax

    Ajax一.准备知识JSON1.什么是json JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation) JSON 是轻量级的文本数据交换格式 JSO ...

  4. Raiden Charge

    2017年10月22 周日 这是个元气满满 值得纪念的一天(不好意思走错片场了) 虽然有各种乱遭的客观元素 但我们队确确实实地打铁了 那些我们轻视的 野鸡(误)大学 都在我们前面 都说知耻而后勇 虽然 ...

  5. QEMU KVM Libvirt(12): Live Migration

    由于KVM的架构为 Libvirt –> qemu –> KVM 所以对于live migration有两种方式,一种是qemu + KVM自己的方式,一种是libvirt的方式,当然li ...

  6. 【XSS】利用 onload 事件监控流量劫持

    说到跨站资源监控,首先会联想到『Content Security Policy』.既然 CSP 好用,我们何必自己再搞一套呢.那就先来吐槽下 CSP 的缺陷. 目前的 CSP 日志不详细 用过 CSP ...

  7. [Zephyr] 1、在linux上安装Zephyr-OS并跑DEMO

    星期五, 14. 九月 2018 02:18上午 - BEAUTIFULZZZZ 0) 前言 Zephyr™项目是一个采用Apache 2.0协议许可,Linux基金会托管的协作项目.为所有资源受限设 ...

  8. [Swift]LeetCode526. 优美的排列 | Beautiful Arrangement

    Suppose you have N integers from 1 to N. We define a beautiful arrangement as an array that is const ...

  9. [Swift]LeetCode566. 重塑矩阵 | Reshape the Matrix

    In MATLAB, there is a very useful function called 'reshape', which can reshape a matrix into a new o ...

  10. [Swift]LeetCode763. 划分字母区间 | Partition Labels

    A string S of lowercase letters is given. We want to partition this string into as many parts as pos ...