为何在匿名内部类中只能问被final修饰的本地变量?

探讨Java中匿名内部类访问方法本地变量时为何需声明为final,分析生命周期不匹配带来的问题及解决方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们在一个方法中定义匿名内部类访问方法的本地变量时常常会发现编译时出错,被告知“需要被声明为最终类型",甚是疑惑,于是在网上搜索其原因, 在此作一总结。

 

局部内部类(在方法内部定义的类)中无法直接访问方法中的局部变量,须修饰其为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修饰,所以不用担心指针发生更改

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值