1. 程式人生 > >java final 關鍵字 -- 常量部分

java final 關鍵字 -- 常量部分

field 常量池 let line static value 標示 pub text

java中final 定義常量有兩種方式,一種是靜態常量,一種是實例常量,下面分別介紹

靜態常量的定義又可以分兩種情況:一種是定義時賦值,一種是靜態方法塊中賦值

定義時賦值,如下代碼:

/**

* Created by Jokul on 2018/1/17.

*/

public class FinalTest {

private static final String A = "av";

public static void main(String[] args) {

System.out.println(A);

}

}

此種情況,由編譯器在編譯時就將 “av” 常量值放入了常量區,不存在A這個變量。

靜態方法塊中賦值,如下代碼:

/**

* Created by Jokul on 2018/1/17.

*/

public class FinalTest {

private static final String A;

static {

A = "av";

}

public static void main(String[] args) {

System.out.println(A);

}

}

看一下class文件的指令,如下所示:

Classfile /D:/ideaProjects/final-test/target/classes/com/test/java/FinalTest.class

Last modified 2018-1-17; size 652 bytes

MD5 checksum b2a05ed23493ecfc5d34dd7a7a64f41a

Compiled from "FinalTest.java"

public class com.test.java.FinalTest

SourceFile: "FinalTest.java"

minor version: 0

major version: 49

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #7.#24 // java/lang/Object."<init>":()V

#2 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;

#3 = Fieldref #6.#27 // com/test/java/FinalTest.A:Ljava/lang/String;

#4 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V

#5 = String #30 // av

#6 = Class #31 // com/test/java/FinalTest

#7 = Class #32 // java/lang/Object

#8 = Utf8 A

#9 = Utf8 Ljava/lang/String;

#10 = Utf8 <init>

#11 = Utf8 ()V

#12 = Utf8 Code

#13 = Utf8 LineNumberTable

#14 = Utf8 LocalVariableTable

#15 = Utf8 this

#16 = Utf8 Lcom/test/java/FinalTest;

#17 = Utf8 main

#18 = Utf8 ([Ljava/lang/String;)V

#19 = Utf8 args

#20 = Utf8 [Ljava/lang/String;

#21 = Utf8 <clinit>

#22 = Utf8 SourceFile

#23 = Utf8 FinalTest.java

#24 = NameAndType #10:#11 // "<init>":()V

#25 = Class #33 // java/lang/System

#26 = NameAndType #34:#35 // out:Ljava/io/PrintStream;

#27 = NameAndType #8:#9 // A:Ljava/lang/String;

#28 = Class #36 // java/io/PrintStream

#29 = NameAndType #37:#38 // println:(Ljava/lang/String;)V

#30 = Utf8 av

#31 = Utf8 com/test/java/FinalTest

#32 = Utf8 java/lang/Object

#33 = Utf8 java/lang/System

#34 = Utf8 out

#35 = Utf8 Ljava/io/PrintStream;

#36 = Utf8 java/io/PrintStream

#37 = Utf8 println

#38 = Utf8 (Ljava/lang/String;)V

{

public com.test.java.FinalTest();

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."<init>":()V

4: return

LineNumberTable:

line 6: 0

LocalVariableTable:

Start Length Slot Name Signature

0 5 0 this Lcom/test/java/FinalTest;

public static void main(java.lang.String[]);

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=1, args_size=1

0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;

3: getstatic #3 // Field A:Ljava/lang/String;

6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

9: return

LineNumberTable:

line 23: 0

line 24: 9

LocalVariableTable:

Start Length Slot Name Signature

0 10 0 args [Ljava/lang/String;

static {};

flags: ACC_STATIC

Code:

stack=1, locals=0, args_size=0

0: ldc #5 // String av

2: putstatic #3 // Field A:Ljava/lang/String;

5: return

LineNumberTable:

line 12: 0

line 13: 5

}

從class文件的指令中可以看出(看黃底標示的指令),在類被加載時 執行static{} 代碼塊時會將 "av" 字面值賦值給 靜態常量A,然後在main() 方法中獲取了常量A的值,所以靜態方法塊對靜態常量賦值是在類加載階段完成的

靜態常量相關內容介紹完了,下面介紹實例常量,實例常量跟靜態常量一樣也有兩種賦值方式,一種是定義時賦值,一種是構造函數中賦值,但最終編譯後都是構造函數中賦值,我們下面一起看一下

先看定義時賦值,代碼如下:

/**

* Created by Jokul on 2018/1/17.

*/

public class FinalTest {

private final String b = "bv";

public static void main(String[] args) {

FinalTest test = new FinalTest();

System.out.println(test.b);

}

}

class指令如下:

Classfile /D:/ideaProjects/final-test/target/classes/com/test/java/FinalTest.class

Last modified 2018-1-17; size 708 bytes

MD5 checksum a28964e208e3fd7b3e8368a8688be37d

Compiled from "FinalTest.java"

public class com.test.java.FinalTest

SourceFile: "FinalTest.java"

minor version: 0

major version: 49

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #9.#27 // java/lang/Object."<init>":()V

#2 = String #28 // bv

#3 = Fieldref #4.#29 // com/test/java/FinalTest.b:Ljava/lang/String;

#4 = Class #30 // com/test/java/FinalTest

#5 = Methodref #4.#27 // com/test/java/FinalTest."<init>":()V

#6 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream;

#7 = Methodref #9.#33 // java/lang/Object.getClass:()Ljava/lang/Class;

#8 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/String;)V

#9 = Class #36 // java/lang/Object

#10 = Utf8 b

#11 = Utf8 Ljava/lang/String;

#12 = Utf8 ConstantValue

#13 = Utf8 <init>

#14 = Utf8 ()V

#15 = Utf8 Code

#16 = Utf8 LineNumberTable

#17 = Utf8 LocalVariableTable

#18 = Utf8 this

#19 = Utf8 Lcom/test/java/FinalTest;

#20 = Utf8 main

#21 = Utf8 ([Ljava/lang/String;)V

#22 = Utf8 args

#23 = Utf8 [Ljava/lang/String;

#24 = Utf8 test

#25 = Utf8 SourceFile

#26 = Utf8 FinalTest.java

#27 = NameAndType #13:#14 // "<init>":()V

#28 = Utf8 bv

#29 = NameAndType #10:#11 // b:Ljava/lang/String;

#30 = Utf8 com/test/java/FinalTest

#31 = Class #37 // java/lang/System

#32 = NameAndType #38:#39 // out:Ljava/io/PrintStream;

#33 = NameAndType #40:#41 // getClass:()Ljava/lang/Class;

#34 = Class #42 // java/io/PrintStream

#35 = NameAndType #43:#44 // println:(Ljava/lang/String;)V

#36 = Utf8 java/lang/Object

#37 = Utf8 java/lang/System

#38 = Utf8 out

#39 = Utf8 Ljava/io/PrintStream;

#40 = Utf8 getClass

#41 = Utf8 ()Ljava/lang/Class;

#42 = Utf8 java/io/PrintStream

#43 = Utf8 println

#44 = Utf8 (Ljava/lang/String;)V

{

public com.test.java.FinalTest();

flags: ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."<init>":()V

4: aload_0

5: ldc #2 // String bv

7: putfield #3 // Field b:Ljava/lang/String;

10: return

LineNumberTable:

line 6: 0

line 9: 4

LocalVariableTable:

Start Length Slot Name Signature

0 11 0 this Lcom/test/java/FinalTest;

public static void main(java.lang.String[]);

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=2, args_size=1

0: new #4 // class com/test/java/FinalTest

3: dup

4: invokespecial #5 // Method "<init>":()V

7: astore_1

8: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;

11: aload_1

12: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class;

15: pop

16: ldc #2 // String bv

18: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

21: return

LineNumberTable:

line 21: 0

line 23: 8

line 24: 21

LocalVariableTable:

Start Length Slot Name Signature

0 22 0 args [Ljava/lang/String;

8 14 1 test Lcom/test/java/FinalTest;

}

請看標黃的指令行,我們在代碼中並未編寫構造函數,更沒有在構造函數中給實例常量b 賦值,編譯器自動都給加上了,同時編譯器在使用常量時做優化,直接使用了常量區的常量值,請看main方法中標黃的指令,接下來再來看我們主動在構造函數中給實例常量b賦值,代碼如下:

/**

* Created by Jokul on 2018/1/17.

*/

public class FinalTest {

private final String b;

public FinalTest(){

b = "bv";

}

public static void main(String[] args) {

FinalTest test = new FinalTest();

System.out.println(test.b);

}

}

class指令如下:

Classfile /D:/ideaProjects/final-test/target/classes/com/test/java/FinalTest.class

Last modified 2018-1-17; size 642 bytes

MD5 checksum 3254d93a7430c59105e2a9532bd3d95b

Compiled from "FinalTest.java"

public class com.test.java.FinalTest

SourceFile: "FinalTest.java"

minor version: 0

major version: 49

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #8.#25 // java/lang/Object."<init>":()V

#2 = String #26 // bv

#3 = Fieldref #4.#27 // com/test/java/FinalTest.b:Ljava/lang/String;

#4 = Class #28 // com/test/java/FinalTest

#5 = Methodref #4.#25 // com/test/java/FinalTest."<init>":()V

#6 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream;

#7 = Methodref #31.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V

#8 = Class #33 // java/lang/Object

#9 = Utf8 b

#10 = Utf8 Ljava/lang/String;

#11 = Utf8 <init>

#12 = Utf8 ()V

#13 = Utf8 Code

#14 = Utf8 LineNumberTable

#15 = Utf8 LocalVariableTable

#16 = Utf8 this

#17 = Utf8 Lcom/test/java/FinalTest;

#18 = Utf8 main

#19 = Utf8 ([Ljava/lang/String;)V

#20 = Utf8 args

#21 = Utf8 [Ljava/lang/String;

#22 = Utf8 test

#23 = Utf8 SourceFile

#24 = Utf8 FinalTest.java

#25 = NameAndType #11:#12 // "<init>":()V

#26 = Utf8 bv

#27 = NameAndType #9:#10 // b:Ljava/lang/String;

#28 = Utf8 com/test/java/FinalTest

#29 = Class #34 // java/lang/System

#30 = NameAndType #35:#36 // out:Ljava/io/PrintStream;

#31 = Class #37 // java/io/PrintStream

#32 = NameAndType #38:#39 // println:(Ljava/lang/String;)V

#33 = Utf8 java/lang/Object

#34 = Utf8 java/lang/System

#35 = Utf8 out

#36 = Utf8 Ljava/io/PrintStream;

#37 = Utf8 java/io/PrintStream

#38 = Utf8 println

#39 = Utf8 (Ljava/lang/String;)V

{

public com.test.java.FinalTest();

flags: ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."<init>":()V

4: aload_0

5: ldc #2 // String bv

7: putfield #3 // Field b:Ljava/lang/String;

10: return

LineNumberTable:

line 15: 0

line 16: 4

line 17: 10

LocalVariableTable:

Start Length Slot Name Signature

0 11 0 this Lcom/test/java/FinalTest;

public static void main(java.lang.String[]);

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=2, args_size=1

0: new #4 // class com/test/java/FinalTest

3: dup

4: invokespecial #5 // Method "<init>":()V

7: astore_1

8: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;

11: aload_1

12: getfield #3 // Field b:Ljava/lang/String;

15: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

18: return

LineNumberTable:

line 21: 0

line 23: 8

line 24: 18

LocalVariableTable:

Start Length Slot Name Signature

0 19 0 args [Ljava/lang/String;

8 11 1 test Lcom/test/java/FinalTest;

}

從以上標黃指令可以看出,給實例常量b賦值仍然是在構造函數中進行的,這次編譯器並未對使用實例常量的地方進行編譯優化,而是采用 getfield 來獲取實例常量的值。

綜上所述,可以得出如下表所示的結論:

常量類型

定義賦值

構造函數賦值

靜態常量

編譯時存儲到常量池

直接引用常量池中的值

構造函數中賦值給常量字段

從常量字段中獲取

實例常量

編譯時存儲到常量池,同時構造函數進行初始化(如果沒有構造函數會放在默認構造函數中)

直接引用常量池中的值

構造函數中賦值給常量字段

從常量字段中獲取

發生的階段如下表所示:

階段

靜態常量

實例常量

編譯階段

字面值

字面值

類加載階段

靜態構造函數(靜態塊)賦值

類初始化階段

定義賦值和構造函數賦值

自己原創,希望跟大家交流討論,有不當的地方請指正

註:本文中使用到的查看class文件的工具,使用的是 javap –verbose 命令


java final 關鍵字 -- 常量部分