`

为什么“局部内部类能访问所在方法中的final类型的参数和变量”

    博客分类:
  • java
阅读更多
2009-5-21

Thinking In Java里面的说法(唯一正确的说法): 如果定义一个匿名内部类,并且希望它使用一个在其外部定的对象,那么编译器会要求其参数引用是final 的。


Java代码
public class Tester {  
    public static void main(String[] args) {  
        A a = new A();  
        C c = new C();  
        c.shoutc(a.shout(5));  
    }  
}  
////////////////////////////////////////////////////////  
class A {  
    public void shouta() {  
        System.out.println("Hello A");  
    }  
 
    public A shout(final int arg) {  
        class B extends A {  
            public void shouta() {  
                System.out.println("Hello B" + arg);  
            }  
        }  
        return new B();  
    }  
}  
////////////////////////////////////////////////////////  
class C {  
    void shoutc(A a) {  
        a.shouta();  
    }  

public class Tester {
public static void main(String[] args) {
A a = new A();
C c = new C();
c.shoutc(a.shout(5));
}
}
////////////////////////////////////////////////////////
class A {
public void shouta() {
System.out.println("Hello A");
}

public A shout(final int arg) {
class B extends A {
public void shouta() {
System.out.println("Hello B" + arg);
}
}
return new B();
}
}
////////////////////////////////////////////////////////
class C {
void shoutc(A a) {
a.shouta();
}
}















第5行c.shoutc(a.shout(5)),在a.shout(5)得到返回值后,a的shout()方法栈被清空了,即arg不存在了,而c.shoutc()却又调用了a.shouta()去执行System.out.println("Hello B" + arg)。

再来看Java虚拟机是怎么实现这个诡异的访问的:有人认为这种访问之所以能完成,是因为arg是final的,由于变量的生命周期,事实是这样的吗?方法栈都不存在了,变量即使存在,怎么可能还被访问到?试想下:一个方法能访问另一个方法的定义的final局部变量吗(不通过返回值)?

研究一下这个诡异的访问执行的原理,用反射探测一下局部内部类 。编译器会探测局部内部类中是否有直接使用外部定义变量的情况,如果有访问就会定义一个同类型的变量,然后在构造方法中用外部变量给自己定义的变量赋值,而后局部内部类所使用的变量都是自己定义的变量,所以就可以访问了。见下:
class   A$1$B
{
A$1$B(A,   int);

private   final   int   var$arg;
private   final   A   this$0;
}
A$1$B类型的对象会使用自定义的var$arg变量,而不是shout()方法中的final int arg变量,当然就可以访问了。

那么为什么外部变量要是final的呢?即使外部变量不是final,编译器也可以如此处理:自己定义一个同类型的变量,然后在构造方法中赋值就行了。原因就是为了让我们能够挺合逻辑的直接使用外部变量,而且看起来是在始终使用 外部的arg变量(而不是赋值以后的自己的字段)。

考虑出现这种情况:在局部内部类中使用外部变量arg,如果编译器允许arg不是final的,那么就可以对这个变量作变值操作(例如arg++),根据前面的分析,变值操作改变的是var$arg,而外部的变量arg并没有变,仍然是5(var$arg才是6)。因此为了避免这样如此不合逻辑的事情发生:你用了外部变量,又改变了变量的值,但那个变量却没有变化,自然的arg就被强行规定必须是final所修饰的,以确保让两个值永远一样,或所指向的对象永远一样(后者可能更重要)。



还有一点需要注意的是内部类与方法不是同时执行的,比如实现ActionListener,只有当事件发生的时候才会执行,而这时方法已经结束了。

转自http://qiuqiao.iteye.com/blog/393761

分享到:
评论

相关推荐

    Java中局部内部类可以访问它所在方法中定义的final修饰的局部变量的合理解释.doc

    - 当局部内部类需要访问所在方法的局部变量时,JVM会自动为这些局部变量创建副本,并存储在局部内部类的隐式成员变量中。 - 这一行为仅适用于final修饰的局部变量。 #### 示例分析 考虑以下代码示例: ```java ...

    内部类分类及应用

    方法内部类对象不能使用该内部类所在方法的非final局部变量。 四、匿名内部类 匿名内部类是指不具有名称的内部类。匿名内部类适合使用场景包括:只用到类的一个实例、类在定义后马上用到、类非常小、给类命名并...

    Java内部类访问规则.doc

    - 局部内部类可以直接访问外部类的成员,并且可以访问该方法中的局部变量,但这些局部变量必须是最终的(`final`)或者本质上不可变的。 **5. 匿名内部类:** - 匿名内部类是没有名称的内部类,通常用于实现接口或...

    Java内部类总结.pdf

    局部内部类不能有访问修饰符,也不能访问外部类的静态成员,但可以访问方法参数的final变量以及所在代码块内的变量。局部内部类的主要用途是提供有限的封装,通常用于实现回调或匿名回调函数。 3. 静态内部类: ...

    Java内部类详解.pdf

    局部内部类可以访问其所在方法或块的局部变量,但这些变量必须声明为final。 7. **嵌套类**: 嵌套类是所有内部类的总称,包括静态和非静态内部类。嵌套类的概念比内部类更宽泛,内部类是嵌套类的一个子集。 8. *...

    java类修饰词和内部类详解

    被声明为 private 的成员只能被所在类访问,不能被其他类访问。 #### 2. 其他类修饰词 - **abstract**:表示抽象类。一个类如果被声明为 abstract,则它不能被实例化,主要用于提供一个基类供其他类继承。抽象类...

    innerClassDemo.zip

    局部内部类可以访问它所在的作用域内的所有变量,包括final修饰的局部变量。 3. **匿名内部类**:匿名内部类没有名字,通常用于简化代码和快速创建一次性使用的类。它们常用于实现接口或继承类,并且可以作为参数、...

    java内部类、异常、编程规范[文].pdf

    如果要访问方法的局部变量或参数,这些变量必须声明为final。 3. 静态内部类: 静态内部类不需要外部类的实例就可以创建对象,但不能直接访问外部类的非静态成员。它主要用于创建与外部类弱耦合的辅助类。 4. ...

    匿名内部类核心重点知识讲解和经典例子讲解

    - **局部内部类**:在方法或作用域内定义,只能访问该作用域内的变量(需final修饰)和外部类的静态成员。 3. **匿名内部类与接口** - **实现接口**:匿名内部类可以直接实现一个接口,并提供接口中所有方法的...

    2024内部类,泛型枚举包装类

    - 局部内部类可以访问其所在方法或代码块内的局部变量,但这些变量必须是final或实际上不可变的。 ##### 4. 匿名内部类 匿名内部类是一种没有名字的内部类,常用于简化代码编写,特别是创建接口的实例时。 - **...

    java面试问题整理

    - 局部内部类只能访问所在方法的final参数和变量,这是因为局部内部类的生命周期超出了它所在方法的生命周期,为了保持访问的稳定性,编译器会为final变量在局部内部类中创建副本。 - 当方法执行完毕,局部变量的...

    【05-面向对象(下)】

    •局部内部类不能在外部类以外的地方使用,那么局部内部类也不能使用访问控制符和static修饰 匿名内部类 •匿名内部类适合创建那种只需要一次使用的类,定义匿名内部类的语法格式如下: •new 父类构造器...

    java面试题 常用

    5. **局部内部类的访问权限**:局部内部类可以访问其所在方法或块的final变量以及类的成员变量,但不能直接访问非final的局部变量。这是因为局部变量在其所在的作用域内,而局部内部类的生命周期超出了这个范围,...

    软件编程词汇表(一些编程词汇的解释)

    匿名内部类可以访问外部类的所有成员变量和方法,包括私有的成员。 #### 6. 数组 (Array) 数组是一种数据结构,用于存储相同类型的一系列元素。数组中的每个元素都有一个索引,索引从0开始。在Java中,数组可以通过...

    Java基础精品课08-方法.zip

    学习和理解Java中的方法是编程的基础,通过熟练掌握方法的声明、调用、参数传递和返回,能有效提升编程效率和代码质量。此外,掌握方法的高级特性,如重载、递归等,对于编写复杂程序至关重要。 在"Java基础精品课...

    SCJP学习笔记

    - 如果内部类定义在方法中,它可以访问方法中的final局部变量以及该方法所在类的所有实例变量。 - 静态内部类则不能直接访问外部类的非静态成员,因为它不持有对外部类实例的引用。 5. **数据类型转换**: - ...

    软件工程师应聘面试试题集合

    5. **局部内部类与变量访问**:局部内部类可以访问其所在方法内的 `final` 变量和所有成员变量,但不能直接访问非 `final` 局部变量。这是因为局部内部类的生命周期超出了局部变量的作用域,为了保持一致性,Java ...

    JAVA面试题目汇总

    5. **局部内部类访问变量**:局部内部类可以访问其所在作用域内的final变量以及所有的成员变量(包括静态和非静态),但不能直接访问非final的局部变量。 6. **case语句中的值类型**:在switch语句的case子句中,...

    java 面试题(最新最全java面试题汇总)

    在给定的例子中,局部内部类`Animal`可以访问`work`(作为`final`变量)和`name`(成员变量),但不能访问非`final`的`age`变量。 6. **常量类与`instanceof`**: - `Math`和`String`都是`final`类,不能被继承。`...

Global site tag (gtag.js) - Google Analytics