为何在匿名内部类中只能问被final修饰的本地变量?
2013-06-26 16:56
459 查看
我们在一个方法中定义匿名内部类访问方法的本地变量时常常会发现编译时出错,被告知“需要被声明为最终类型",甚是疑惑,于是在网上搜索其原因, 在此作一总结。
局部内部类(在方法内部定义的类)中无法直接访问方法中的局部变量,须修饰其为final
1:在方法内声明的本地变量的生命周期与局部内部类对象的生命周期不一致从而导致了这个问题。前者在一个方法运行结束后就随之被销毁。而后者生命周期的终点却并不在此处,只有当该对象不再被引用时,它才会被GC回收。倘若匿名内部类可以直接访问不被final修饰的本地变量,那么就有可能出现一个奇怪的现象:对象在访问一个已经不存在的变量。
2:我们假设局部内部类中可以直接访问方法中的局部变量,且不需要其为final型。我们知道,在内部类中访问变量实际上是在访问该变量的复制品,如果上述条件成立,无论是基本类型还是引用类型,那么一旦局部变量实体或者复制品任何一方发生改变,都不能相互同步,从而造成变量的实体与复制品不一致,想象一下你看着是在访问一个变量,然而你得到的值却与实际的值不同,因此这样就毫无意义可言。
为了更加清晰深刻的了解Java在处理匿名内部类访问final修饰的本地变量时的处理过程,先看看下面这一段代码
代码清单FinalKeywordTest.java
首先通过JDK的工具javap查看一下这个Java文件被编译成什么样子的
这里得到的结果分别打印到1.txt和2.txt中了,由于我们研究的是匿名内部类如何访问final修饰的本地变量,所以我们主要看2.txt即可
通过上面的字节码分析我们可以发现匿名内部类访问本地变量的时候是直接将本地对象的引用当作了该类中的一个实例变量来处理的,因为是由final修饰,所以不用担心指针发生更改
局部内部类(在方法内部定义的类)中无法直接访问方法中的局部变量,须修饰其为final
1:在方法内声明的本地变量的生命周期与局部内部类对象的生命周期不一致从而导致了这个问题。前者在一个方法运行结束后就随之被销毁。而后者生命周期的终点却并不在此处,只有当该对象不再被引用时,它才会被GC回收。倘若匿名内部类可以直接访问不被final修饰的本地变量,那么就有可能出现一个奇怪的现象:对象在访问一个已经不存在的变量。
2:我们假设局部内部类中可以直接访问方法中的局部变量,且不需要其为final型。我们知道,在内部类中访问变量实际上是在访问该变量的复制品,如果上述条件成立,无论是基本类型还是引用类型,那么一旦局部变量实体或者复制品任何一方发生改变,都不能相互同步,从而造成变量的实体与复制品不一致,想象一下你看着是在访问一个变量,然而你得到的值却与实际的值不同,因此这样就毫无意义可言。
为了更加清晰深刻的了解Java在处理匿名内部类访问final修饰的本地变量时的处理过程,先看看下面这一段代码
代码清单FinalKeywordTest.java
public class FinalKeywordTest { public static void main(String[] args) { final int i = 10; final Person person = new Person("penny", 26); new Thread() { @Override public void run() { System.out.println(i + 1); System.out.println(person); } }; } } class Person{ String name; int age; public Person(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
首先通过JDK的工具javap查看一下这个Java文件被编译成什么样子的
javap -v FinalKeywordTest.class > 1.txt javap -v FinalKeywordTest$1.class > 2.txt
这里得到的结果分别打印到1.txt和2.txt中了,由于我们研究的是匿名内部类如何访问final修饰的本地变量,所以我们主要看2.txt即可
Classfile /D:/Workspace/eclipse-for-temp/test/bin/com/kmter/test/FinalKeywordTest$1.class Last modified 2014-12-21; size 772 bytes MD5 checksum b2d663f92b7895759e9a26f6124ef489 Compiled from "FinalKeywordTest.java" class com.kmter.test.FinalKeywordTest$1 extends java.lang.Thread SourceFile: "FinalKeywordTest.java" EnclosingMethod: #38.#40 // com.kmter.test.FinalKeywordTest.main InnerClasses: #1; //class com/kmter/test/FinalKeywordTest$1 minor version: 0 major version: 51 flags: ACC_SUPER Constant pool: #1 = Class #2 // com/kmter/test/FinalKeywordTest$1 #2 = Utf8 com/kmter/test/FinalKeywordTest$1 #3 = Class #4 // java/lang/Thread #4 = Utf8 java/lang/Thread #5 = Utf8 val$person #6 = Utf8 Lcom/kmter/test/Person; #7 = Utf8 <init> #8 = Utf8 (Lcom/kmter/test/Person;)V #9 = Utf8 Code #10 = Fieldref #1.#11 // com/kmter/test/FinalKeywordTest$1.val$person:Lcom/kmter/test/Person; //这里可以看到被final修饰的Person对象在匿名内部类中直接体现为常量池的一个对象引用,这个对象的引用是从FinalKeywordTest类的main方法中复制过来的 #11 = NameAndType #5:#6 // val$person:Lcom/kmter/test/Person; #12 = Methodref #3.#13 // java/lang/Thread."<init>":()V #13 = NameAndType #7:#14 // "<init>":()V #14 = Utf8 ()V #15 = Utf8 LineNumberTable #16 = Utf8 LocalVariableTable #17 = Utf8 this #18 = Utf8 Lcom/kmter/test/FinalKeywordTest$1; #19 = Utf8 run #20 = Fieldref #21.#23 // java/lang/System.out:Ljava/io/PrintStream; #21 = Class #22 // java/lang/System #22 = Utf8 java/lang/System #23 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Methodref #27.#29 // java/io/PrintStream.println:(I)V #27 = Class #28 // java/io/PrintStream #28 = Utf8 java/io/PrintStream #29 = NameAndType #30:#31 // println:(I)V #30 = Utf8 println #31 = Utf8 (I)V #32 = Methodref #27.#33 // java/io/PrintStream.println:(Ljava/lang/Object;)V #33 = NameAndType #30:#34 // println:(Ljava/lang/Object;)V #34 = Utf8 (Ljava/lang/Object;)V #35 = Utf8 SourceFile #36 = Utf8 FinalKeywordTest.java #37 = Utf8 EnclosingMethod #38 = Class #39 // com/kmter/test/FinalKeywordTest #39 = Utf8 com/kmter/test/FinalKeywordTest #40 = NameAndType #41:#42 // main:([Ljava/lang/String;)V #41 = Utf8 main #42 = Utf8 ([Ljava/lang/String;)V #43 = Utf8 InnerClasses { com.kmter.test.FinalKeywordTest$1(com.kmter.test.Person); flags: //默认构造函数字节码 Code: stack=2, locals=2, args_size=2 0: aload_0 1: aload_1 2: putfield #10 // Field val$person:Lcom/kmter/test/Person; //将实例域person赋值为常量池中的#10(这里是关键,将main方法中的person对象在本类中认为是一个实例变量) 5: aload_0 6: invokespecial #12 // Method java/lang/Thread."<init>":()V 9: return LineNumberTable: line 1: 0 line 7: 5 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this Lcom/kmter/test/FinalKeywordTest$1; //run方法的字节码 public void run(); flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 3: bipush 11 //将单字节常量11压入栈顶 5: invokevirtual #26 // Method java/io/PrintStream.println:(I)V //执行println(int)方法 8: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream; 11: aload_0 //将this指针压入栈顶 12: getfield #10 // Field val$person:Lcom/kmter/test/Person; //拿到person对象并将其压入栈顶 15: invokevirtual #32 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V //执行println(Object)方法 18: return LineNumberTable: line 11: 0 line 12: 8 line 13: 18 LocalVariableTable: Start Length Slot Name Signature 0 19 0 this Lcom/kmter/test/FinalKeywordTest$1; }
通过上面的字节码分析我们可以发现匿名内部类访问本地变量的时候是直接将本地对象的引用当作了该类中的一个实例变量来处理的,因为是由final修饰,所以不用担心指针发生更改
相关文章推荐
- 为什么匿名内部类只能访问final变量【转】
- 为什么匿名内部类和局部内部类只能访问final变量
- Java基础-内部类-为什么局部和匿名内部类只能访问局部final变量
- 为什么匿名内部类和局部内部类只能访问final变量
- 为什么匿名内部类和局部内部类只能访问final变量
- Java匿名内部类访问外部变量,为何需被标志为final?
- 关于java为什么在Method中局部内部类或者匿名内部类调用Method中定义的变量要用final修饰?
- 为什么匿名内部类只能访问其所在方法中的final变量
- 匿名内部类和局部内部类中只能访问final变量的原因
- 为什么匿名内部类和局部内部类只能访问被final修饰的局部变量?
- 为什么匿名内部类和局部内部类只能访问final变量
- 为什么匿名内部类和局部内部类只能访问final变量
- Java中为什么匿名内部类和局部内部类只能访问final变量?
- java学习记录(四):关于匿名内部类和局部内部类只能访问final变量的问题
- 局部内部类只能访问局部中被final修饰的变量
- Java匿名内部类访问外部变量,为何需被标志为final?
- 为什么匿名内部类只能访问其所在方法中的final变量
- 为什么匿名内部类只能访问其所在方法中的final变量
- 为什么匿名内部类只能访问其所在方法中的final类型的局部变量?
- 注意java8中已经没有匿名内部类和局部内部类只能访问final变量的限制了!