Java基础-内部类-为什么局部和匿名内部类只能访问局部final变量

先看下面这段代码:

public class Test {
    public static void main(String[] args)  {

    }

    public void test(final int b) {
        final int a = 10;
        new Thread(){
            public void run() {
                System.out.println(a);
                System.out.println(b);
            };
        }.start();
    }
}

  这段代码会被编译成两个class文件:Test.class和Test1.class。默认情况下,编译器会为匿名内部类和局部内部类起名为Outterx.class(x为正整数)。

  根据上图可知,test方法中的匿名内部类的名字被起为 Test$1。

   上段代码中,如果把变量a和b前面的任一个final去掉,这段代码都编译不过。我们先考虑这样一个问题:

  当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制  的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容:

我们看到在run方法中有一条指令:

bipush 10

  这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

  

  下面再看一个例子:

public class Test {
    public static void main(String[] args)  {

    }

    public void test(final int a) {
        new Thread(){
            public void run() {
                System.out.println(a);
            };
        }.start();
    }
}

  反编译得到:

 我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参a以参数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

  也就说如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

  从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

  对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

  到这里,想必大家应该清楚为何 方法中的局部变量和形参都必须用final进行限定了。

时间: 2024-08-01 07:50:41

Java基础-内部类-为什么局部和匿名内部类只能访问局部final变量的相关文章

java基础内部类(毕向东老师)

内部类//特点:内部类可以直接访问外部类的成员,//外部类要访问内部类中的成员必须创建内部类的对象. //为什么要定义内部类呢?类是用于描述事物的,而事务中如果还有具体的事物,而且这个内部的事物在访问着所属事物中的内容,这时这个内部的事物,也需要用到类来描述.这个类就是内部类.为什么内部类可以直接访问外部类的成员?因为内部类都持有一个外部类的引用.外部类名.this. static class Inner{int num=5;void method(){{int num=6;System.out

黑马程序员——java基础——内部类

 黑马程序员--java基础--内部类 ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 内部类 如果A类需要直接访问B类中的成员,而B类又需要建立A类的对象.这时,为了方便设计和访问,直接将A类定义在B类中.就可以了.A类就称为内部类.内部类可以直接访问外部类中的成员.而外部类想要访问内部类,必须要建立内部类的对象. 内部类的访问规则 1,内部类可以直接访问外部类中的成员,包括私有. 之所以可以直接访问外部类中的成员,是因为内部类中持有

JAVA基础——内部类详解

JAVA内部类详解 在我的另一篇java三大特性的封装中讲到java内部类的简单概要,这里将详细深入了解java内部类的使用和应用. 我们知道内部类可分为以下几种: 成员内部类 静态内部类 方法内部类 匿名内部类 这里我们先将以这个分类来详细了解各个内部类的情况.然后给内部类作出总结. 一.成员内部类 内部类中最常见的就是成员内部类,也称为普通内部类.我们来看如下代码: 运行结果为: 从上面的代码中我们可以看到,成员内部类的使用方法: 1. Inner 类定义在 Outer 类的内部,相当于 O

Java基础—内部类

在Java语言中,可以把一个类定义到另一个类的内部,在类里面的这个类就叫作内部类,外面的类叫作外部类.在这种情况下,这个内部类可以被看成外部类的是一个成员(与类的属性和方法类似).还有一种类被称为顶层(Top-level)类,指的是类定义代码不嵌套在其他类定义中的类. 内部类主要有以下四种:静态内部类.成员内部类.局部内部类和匿名内部类. 1.静态内部类 class outerClass{ static class innerClass{} //静态内部类 } 静态内部类是指被声明为static

【Java基础06】包装类、toString、equals、final、import、static

1 包装类 1.1 包装类(Java8) Wrapper Class Java为8个基本类型提供了对应的包装类,通过这些包装类可以把8个基本类型的值包装成对象来使用.JDK1.5提供了自动装箱和自动拆箱功能,允许把基本类型值直接赋给对应的包装类引用变量,也允许把包装类对象直接赋给对应的基本类型变量. 1.2 自动装箱(Autoboxing)和自动拆箱(AutoUnboxing) 1.自动装箱:可以把一个基本类型变量直接赋给对应的包装类变量,或赋给Object变量:(低->高) 2.自动拆箱:可以

Java 虚拟机内部类静态字段的初始化与访问

要明白 Java 虚拟机如何访问类的静态变量,首先要明白下面几个问题: 虚拟机内部是如何表示一个 Java 类的 静态变量存储在哪里 虚拟机如何访问到这些静态变量 这篇文章也从这围绕这三个问题展开,并结合 OpenJDK 中 HotSpot 的源代码作分析. Java 虚拟机内部如何表示类 HotSpot 虚拟机在内部使用两组类来表示 Java 的类和对象 OOP(ordinary object pointer),用来描述对象实例信息 Klass,用来描述Java类,是虚拟机内部Java类的对等

黑马程序员-java基础-内部类和匿名内部类

外部类中如果含有一个接口,那个接口默认是static 内部的访问特点: A:内部类可以直接访问外部类的成员(成员变量.成员方法),包括私有成员. 之所以可以访问外部成员,是因为内部类持有外部对象的引用 B:外部类要访问内部类的成员,必须创建对象. 内部类位置 成员位置:在成员位置定义的类,被称为成员内部类. 局部位置(成员方法里面):在局部位置定义的类,被称为局部内部类. 成员内部类: 可以直接访问外部类成员 如何直接访问内部类的成员. 外部类名.内部类名 对象名 = 外部类对象.内部类对象;

Java基础-内部类

在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类.广泛意义上的内部类一般来说包括这四种:成员内部类.局部内部类.匿名内部类和静态内部类. 下面就先来了解一下这四种内部类的用法. 1.成员内部类 成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式: class Circle { double radius = 0; public Circle(double radius) { this.radius = radius; } class Draw

java基础第七天_匿名内部类、异常、包和jar

1.利用白富美接口案例,土豪征婚使用匿名内部类对象实现. 2.定义三角形类Trianle,里面包含三个int类型属性,分别表示三条边的长度, 构造三角形对象时,任意两边之和是否大于第三边,如若不成立,抛出自定义异常. 3.Person类中增加birthday属性,对setBirthday(int ,int , int )方法进行异常处理, 要求年有效.月有效.日有效.年月日指定的具体日期有效,对不同情况分别抛出不同的异常. 4.将类定义到指定的包下.com.it18zhang,编译之后,打成ja