天天看點

為何在匿名内部類中隻能問被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修飾,是以不用擔心指針發生更改