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. python语法基础-常用模块-re模块

    ###############     re模块   ################ 正则表达式的规则: # re模块 # 正则表达式,就是做字符串匹配的,在re模块出现之前就有这个正则表达式了,任 ...

  2. ORs-5-OR Subgenomes Variation among Birds, Sea Turtle and Alligator

    OR Subgenomes Variation among Birds, Sea Turtle and Alligator 由 该图数据计算每种鸟的relative percentage,得到下图: ...

  3. 错误修改.bashrc文件导致所有命令无法使用解决方法

    export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

  4. Spring常见的两种增强方式

    一.编程式增强 不借助spring的配置,通过自己实例化对象来实现的增强方式 创建增强类,需要实现你需要的增强接口,(只有实现了该接口,这个类就是一个通知)) /** * 增强类 */ public ...

  5. spring5.0.7.RELEASE配置jackson2.9.5

    概述 Jackson框架是基于Java平台的一套数据处理工具,被称为“最好的Java Json解析器”. 1.环境: jdk版本:jdk1.8spring版本:5.0.7.RELEASE jackso ...

  6. considerate|considerable|content|Contact|Consult|deceived|

    ADJ-GRADED 替人着想的;体贴的Someone who is considerate pays attention to the needs, wishes, or feelings of o ...

  7. iOS应用程序开发——解决iOS7之前版本与之后版本下app启动图片跳动问题

    之前开发的app都是针对iOS6(兼容iOS5),所以在开发之初也出现了启动图片向下跳动的现象,通过网上的解决方法,代码中做了处理(“-20”): _imageView.frame = CGRectM ...

  8. 控制webbrowser滚动到指定位置

    在构造函数中添加事件: webBrowser.DocumentCompleted+=new WebBrowserDocumentCompletedEventHandler(webBrowser_Doc ...

  9. 不疯“模”不成活,海尔阿里II代电视将极致进行到底

    我去过很多现场,经历过很多新品发布,各种概念,各种颠覆,有点见怪不怪.这次受邀海尔阿里II代电视发布会,本也是带着一颗平常心. 2点30分发布会准时开场,当 "智慧模块"在讲解员手 ...

  10. 吴裕雄--天生自然 R语言开发学习:高级数据管理(续三)

    #-----------------------------------# # R in Action (2nd ed): Chapter 5 # # Advanced data management ...