https://blog.csdn.net/z55887/article/details/49229491

先抛出让我疑惑了很久的一个问题

编程时,在线程中使用局部变量时候经常编译器会提示:局部变量必须声明为final

package test;

public class ThreadTest {

public void function(String a) {

new Thread(){
@Override
public void run() {
System.out.println(a);
}
}.start();
}

public static void main(String[] args) {
new ThreadTest().function("a");

}
}

上图中由于方法function中的形参a没有声明为final,编译抛出异常:Cannot refer to the non-final local variable a defined in an enclosing scope

这个问题我特意问过老师,也百度过,都没有给出满意的解答。今天看安卓视频无意发现了答案,真是意外之喜啊!

其实原因就是一个规则:java内部类访问局部变量时局部变量必须声明为final。

那为什么要这样呢?还有线程为什么和内部类一样?接下来我们慢慢揭秘。

public class Out {

public void test(final String a) {
class In{

public void function() {
System.out.println(a);
}
}
new In().function();
}

public static void main(String[] args) {
new Out().test("hi");
}
}
编译这个类后发现产生了两个class文件

也就是说内部类和外部类各一个class文件,这样就产生了一个问题,调用内部类方法的时候如何访问外部类方法中的局部变量呢?

实际上编译后的内部类的构造方法的里面,传了对应的外部类的引用和所有局部变量的形参。

(由于外部类方法执行完后局部变量会消亡,所以内部类构造函数中的局部变量实际是一份“复制”。而为了访问外部类中的私有成员变量,外部类编译后也产生了访问类似与getXXX的方法。)

这时产生了一个不一致的问题,如果局部变量不设为final,那内部类构造完毕后,外部类的局部变量又改变了那怎么办?

public class Out {

public void test(String a) {
class In{
public void function() {
System.out.println(a);
}
}
a="hello";
new In().function();
}

public static void main(String[] args) {
new Out().test("hi");
}
}

如代码中所示,这样调用内部类方法时会造成外部类局部变量和内部类中对应的变量的不一致。(注意内部类编译成class文件与new无关,a="hello"放在new In()前后不影响不一致关系,new在jvm运行class文件时才起效)

理解完内部类必须访问final声明的局部变量原因,我们回到最开始的问题:为什么线程和内部类一样

因为线程也是一个类,所以new Thread也相当于创建了一个内部类啦

我们编译一下最开始的ThreadTest.java文件

发现线程编译后也是产生了单独的class文件。

至此,问题全部解决啦~~

最后说明一下java1.8和之前版本对这个规则编译的区别。

如果在1.8的环境下,会很神奇的发现我们最开始的ThreadTest.java文件编译和运行是完全没有问题的,也就是说内部类使用的局部变量是可以不声明为final?!

且慢,如果我们给局部变量再赋下值会发现编译又会出现同样的错误

public class ThreadTest {

public void function(String a) {
a="b";
new Thread(){
@Override
public void run() {
System.out.println(a);
}
}.start();
}

public static void main(String[] args) {
new ThreadTest().function("a");

}
}
在a="b"这一行报错:Local variable a defined in an enclosing scope must be final or effectively final
也就是说规则没有改变,只是java1.8的编译变得更加智能了而已,在局部变量没有重新赋值的情况下,它默认局部变量为final型,认为你只是忘记加final声明了而已。如果你重新给局部变量改变了值或引用,那就无法默认为final了,所以报错。

参考网站:

详细原理版:java内部类访问局部变量时的final问题 https://blog.csdn.net/xiancaieeee/article/details/8834352

对照原理版:关于java里方法的内部类只能访问被final修饰的局部变量和... http://bbs.itheima.com/thread-136974-1-1.html

jdk不同带来的区别:Java中方法内定义的内部类可以访问方法中的局部变量的问题 https://bbs.csdn.net/topics/390918289?page=1

为什么java内部类访问局部变量必须声明为final?的更多相关文章

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

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

  2. 局部内部类访问它所在方法的局部变量时,要求该局部变量必须声明为final的原因

    这是java的一条规则.那么为什么会有这条规则呢?要想弄懂这个问题,就需要弄懂局部内部类对象和局部变量的生命周期的谁更长的问题. 首先,看一段代码,以没有将变量声明为final的代码作为例子,代码如下 ...

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

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

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

    内部类访问局部变量为什么必须要用final修饰 看了大概五六篇博客, 讲的内容都差不多, 讲的内容也都很对, 但我觉得有些跑题了 略叙一下 String s = "hello"; ...

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

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

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

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

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

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

  8. JAVA中内部类(匿名内部类)访问的局部变量为什么要用final修饰?

    本文主要记录:在JAVA中,(局部)内部类访问某个局部变量,为什么这个局部变量一定需要用final 关键字修饰? 首先,什么是局部变量?这里的局部是:在方法里面定义的变量. 因此,内部类能够访问某局部 ...

  9. 什么是Java内部类?

    如果大家想了解更多的知识和技术,大家可以 搜索我的公众号:理想二旬不止 (尾部有二维码)或者访问我的 个人技术博客 www.ideal-20.cn 这样阅读起来会更加舒适一些 非常高兴与大家交流,学习 ...

随机推荐

  1. 吴裕雄--天生自然 HADOOP大数据分布式处理:CenterOS 7 多台物理机、虚拟机相互桥连接ping通,并且能够成功连接外网

    选择用于桥接模式下的虚拟交换机,并且要选择对应的有线或者无线的网卡,如果主机是插网线联网的,那就选择有线网卡,如果主机是连无线网络的就选择无线网卡.Realtek PCIe GBE Family Co ...

  2. 用dfs序处理线段树的好题吗?

    https://www.cnblogs.com/mountaink/p/9878918.html 分析:每次的选取必须选最优的一条链,那我们考虑一下选择这条链后,把这条路上的点的权值更新掉,再采取选最 ...

  3. python学习笔记(3)数据类型-列表list

    序列是Python中最基本的数据结构.序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推. Python有6个序列的内置类型,但最常见的是列表和元组. 序列 ...

  4. SQL中的子查询

    目录 WHERE子查询 HAVING子查询 FROM子查询 SELECT子查询 EXISIT子查询 查询薪资排名的员工信息(面试) z子查询就是将一个查询(子查询)的结果作为另一个查询(主查询)的数据 ...

  5. 剖析String,StringBuffer,StringBuilder异同

    近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下,便 ...

  6. algorithm-question

    主键都相同,选择排序和插入排序谁快 选择排序:比较N*(N-1)/2,交换0:插入排序:比较N-1,交换0:插入排序更 大专栏  algorithm-question快 逆序数组,插入排序与选择排序 ...

  7. CDC与HDC的区别以及相互转换

    CDC是MFC的DC的一个类  HDC是DC的句柄,API中的一个类似指针的数据类型.  MFC类的前缀都是C开头的  H开头的大多数是句柄  这是为了助记,是编程读\写代码的好的习惯.  CDC中所 ...

  8. [转]cookie 和 session

    原文:https://github.com/alsotang/node-lessons/tree/master/lesson16 读别人源码教程的时候,看到了这个,觉得写的很透彻,转. 众所周知,HT ...

  9. python djangjo 文件上传

    视图: request.GET 获取数据 request.POST   提交数据 request.FILES  获取文件用 checkbox 等多选的内容   request.POST.getlist ...

  10. IP地址0.0.0.0表示什么

    参考RFC文档: 0.0.0.0/8 - Addresses in this block refer to source hosts on "this"network. Addre ...