细说匿名内部类引用方法局部变量时为什么需要声明为final
一、前言
在研究公司某个项目的源码,看到前人使用了挺多内部类,内部类平时我用的比较多的是匿名内部类,平时用的多的是匿名内部类,其他形式的用的比较少,然后我就有个疑惑:到底内部类是基于什么样的考虑,才让java设计者搞这么一套实现?还有,平时在什么情境下会考虑使用内部类呢?这个我将在另外一篇博文进行介绍,详情参见:xxxxxx,本篇博文是在查阅内部类相关资料时,看到很多博文在解释为什么匿名内部类在引用局部变量时需要将局部变量指定为final(不可变)说的不清楚,所以这边我将我的理解写出来,以供参考~
二、正文
本篇博文基于的Java环境是:jdk1.6,在jdk1.8中,匿名内部类引用局部变量时,已经废弃使用final修饰局部变量,但这并不意味着jdk1.8在实现方面有什么很大的变动,这个博文最后也会给予说明。
在讨论本篇博文之前,需要与看官达成一些共识:
1)Java中数据类型分为两大类:基本数据类型和引用数据类型。相应的,变量也分为基本类型和引用类型。基本类型的变量保存原始值,即它代表的值就是数值本身,而引用类型的变量保存引用值,"引用值"指向内存空间的地址(也就是地址值),代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置
2)final修饰的变量,赋予过初值之后,不允许变更。对基本类型而言,其保存的原始值不能变更,对引用类型而言,其引用的地址值不允许变更,也就是说不能引用其他对象
final int a = 1;
a = 2;//编译报错 final Integer b = new Integer(1);
b = new Integer(2);//编译报错
先来个匿名内部类使用局部变量的例子:
public class OutClass {
private String msg = "OutClass's msg"; public void check(final String m){
(new Thread(){
public void run(){
System.out.println(m);
System.out.println(msg);
}
}).start();
}
public static void main(String[]args){
OutClass oc = new OutClass();
oc.check("Need to pass to inner Class");
} }
//输出
Need to pass to inner Class
OutClass's msg
编译器编译的时候,匿名内部类会生成一个:外部类$x.class,其中x是正整数,这个实例中匿名内部类为:OutClass$1.class,我们可以使用jdk自带的反编译工具,将生成的这个class反编译出来看下,在dos窗口执行:javap -c OutClass$1.class,
我相信,只要熟悉内部类的童鞋,都知道,如果要实例化内部类,那么必须先实例化外部类,这就是原因所在,内部类需要持有外部类的一个引用,这也是内部类能无条件访问外部类所有成员方法和成员变量的根本原因。
通过上面反编译出来的内容可以看出,如果匿名内部类使用了局部变量,那么编译器会将使用的值拷贝一份,作为构造函数的一个参数传递进来(构造函数是编译器自动添加)。因为局部变量在方法或者代码块执行完毕,就会被销毁,所以编译器在编译的时候,就拷贝了一份局部变量存储的字面值或者地址值,这样局部变量被销毁时,匿名内部类依然拥有之前传递进来的值。现在我们从语义上来理解下Java设计者的考虑:假如传递到匿名内部类的局部变量,不加final修饰,那么意味着局部变量可以改变,这就意味着匿名内部类里面值的变更和外部的变量的变更不能同步,虽然内部类持有的是局部变量值的拷贝,但是语义应该保持一致,语义保持一致的前提是值要能同步,因为java编译器的设计无法提供两边同步变更的机制,所以直接锁死,不允许内外变更~
package com.finalparams.main; public class Main {
public static void main(String[]args){
Main m = new Main();
m.show();
} public void show(){
int a = 1;
(new innerClassInterface(){
public void doChange(){
a = 3;//编译报错:Cannot refer to the non-final local variable a defined in an enclosing scope
}
}).doChange();
System.out.println(a);
}
} interface innerClassInterface{
public void doChange();
}
以上代码第13行编译报错,假设编译器允许编译过去,按照编译器现有的实现,内部类里面的变更是没有办法反应到外面的, 也就是里面变更了,但是外部还是没变,这样是不能接受的,所以直接锁死,不让变更~
特别说明:在jdk1.8之后,匿名内部类使用局部变量的时候,局部变量已经不需要使用final修饰了,可以编译通过,那是不是说编译器的实现机制变了呢?非也,只是编译器在编译的时候不允许对这个变量进行变更,也就是说,如果你内外只是使用这个变量,而不进行重新赋值,那么就编译通过,如果内外有重新赋值,那么还是会报编译错误:
......................................................................................
public void show(){
int a = 1;
(new innerClassInterface(){
public void doChange(){
System.out.println(a);//jdk1.8之前编译报错,1.8之后,不报错,因为没有改变变量的值
}
}).doChange();
System.out.println(a);
}
......................................................................................
public void show(){
int a = 1;
(new innerClassInterface(){
public void doChange(){
System.out.println(a);//jdk1.8之前编译报错,1.8之后,不报错,因为没有改变变量的值
a = 4;//jdk1.8编译报错
}
}).doChange();
System.out.println(a);
}
三、参考链接
https://www.zhihu.com/question/21395848
https://zhidao.baidu.com/question/266984801594415645.html
四、联系本人
为方便没有博客园账号的读者交流,特意建立一个企鹅群(纯公益,非利益相关),读者如果有对博文不明之处,欢迎加群交流:261746360,小杜比亚-博客园
细说匿名内部类引用方法局部变量时为什么需要声明为final的更多相关文章
- 内部类访问局部变量时,为什么需要加final关键字
是变量的作用域的问题,因为匿名内部类是出现在一个方法的内部的,如果它要访问这个方法的参数或者方法中定义的变量,则这些参数和变量必须被修饰为final.因为虽然匿名内部类在方法的内部,但实际编译的时候, ...
- 为什么java内部类访问局部变量必须声明为final?
https://blog.csdn.net/z55887/article/details/49229491 先抛出让我疑惑了很久的一个问题 编程时,在线程中使用局部变量时候经常编译器会提示:局部变量必 ...
- [多问几个为什么]为什么匿名内部类中引用的局部变量和参数需要final而成员字段不用?(转)
昨天有一个比较爱思考的同事和我提起一个问题:为什么匿名内部类使用的局部变量和参数需要final修饰,而外部类的成员变量则不用?对这个问题我一直作为默认的语法了,木有仔细想过为什么(在分析完后有点印象在 ...
- 局部内部类访问它所在方法的局部变量时,要求该局部变量必须声明为final的原因
这是java的一条规则.那么为什么会有这条规则呢?要想弄懂这个问题,就需要弄懂局部内部类对象和局部变量的生命周期的谁更长的问题. 首先,看一段代码,以没有将变量声明为final的代码作为例子,代码如下 ...
- uni-app 父组件引用子组件时怎么调用子组件的方法
1.写一个简单的子组件main/index.vue: <template> <view> </view> </template> <script& ...
- 匿名内部类不能访问外部类方法中的局部变量,除非变量被声明为final类型
1. 这里所说的"匿名内部类"主要是指在其外部类的成员方法内定义,同时完成实例化的类,若其访问该成员方法中的局部变量,局部变量必须要被final修饰.2. 原因是编译程序实现上的困 ...
- 【细说Java】方法重载的简单介绍
1. 什么是重载 方法名称相同,但它们的参数类型或个数不同,这样,方法在被调用时编译器就可以根据参数的类型与个数的不同加以区分,这就是方法的重载. 既然可以通过参数类型或参数个数来作为重载条件,那返回 ...
- 匿名内部类访问方法成员变量需要加final的原因及证明(转)
https://blog.csdn.net/wjw521wjw521/article/details/77333820 在java编程中,没用的类定义太多对系统来说也是一个负担,这时候我们可以通过定义 ...
- C#中引用类型的变量做为参数在方法调用时加不加 ref 关键字的不同之处
一直以为对于引用类型做为参数在方法调用时加不加 ref 关键字是没有区别的.但是今天一调试踪了一下变量内存情况才发现大有不同. 直接上代码,结论是:以下代码是使用了 ref 关键字的版本.它输出1 ...
随机推荐
- JavaSE思维导图
Java基础知识: 面向对象: 集合: 多线程.网络编程.反射.设计模式: 常用API: 转载 https://blog.csdn.net/qq_34983808/article/detai ...
- vue相关ajax库的使用
相关库: vue-resource: vue插件, 多用于vue1.x axios: 第三方库, 多用于vue2.x vue-resource使用 // 引入模块 import VueResource ...
- Canvas在移动端设备上模糊出现锯齿边
在绘制的过程中画布内容的实际大小是根据 canvas 的 width 与 height 属性设置的,而 style 或者CSS设置的width 与 height 只是简单的对画布进行缩放. canva ...
- Android开发——Context类的各种细节问题
0. 前言 Context相信所有的Android开发人员基本上每天都在接触,因为它太常见了.但实际上Context有太多小的细节并不被大家所关注,那么今天我们就来学习一下那些你所不知道的细节. ...
- 深圳Uber优步司机奖励政策(12月28日到1月3日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- 西安Uber优步司机奖励政策(8月10日到8月16日)
1) 工作日(周一到周五)早高峰时间段(7点到9:30点).晚高峰时间段(5点到8点)车费 2.0 倍,每单奖励部分上限35元 例:在高峰时段中,假设行程基本车费为¥15,只要达到奖励前提,最后你将获 ...
- python字符串格式化符号及转移字符含义
博文出自鱼C论坛文章 http://bbs.fishc.com/thread-39140-1-1.html
- iOS 中的正则表达式符号
最近重新看了一遍 iOS 的正则文档,简单翻译下文档中涉及到的符号 1.正则表达式元字符 符号 说明 \a 响铃, \u0007 \A 匹配输入的开始,只匹配第一行,也就是忽略多行选项 \b 不在[] ...
- Rails导出CSV
版本 ruby 1.9 rails 3.2 完整代码 #引入CSV标准类库 require 'csv' class PeopleController < ApplicationControl ...
- onenet基础通信套件加B300移植
1. 遇到的第一个问题,说是少了文件,但是明明有这个文件的啊? scons: warning: Ignoring missing SConscript 'build_scons\arm\Hi2115\ ...