一功蜓、為什么要使用final?
final指的是"這是無(wú)法改變的"。不想改變可能出于兩種理由:設(shè)計(jì)或效率童社。
要具體得講著隆,那就必須看它的使用了,使用它能得到了什么樣的功能效果灭抑,就是它的作用了抵代。
可能是用到final的情況有三種:數(shù)據(jù)、方法和類(lèi)案腺。
1. 修飾數(shù)據(jù)
許多編程語(yǔ)言都有某種方法康吵,來(lái)向編譯器告知一塊數(shù)據(jù)是恒定不變的。
Java用final修飾這些數(shù)據(jù)同辣,就可以達(dá)到這種效果惭载。
我們知道,Java有兩種數(shù)據(jù)類(lèi)型:原始類(lèi)型(Primitive Types, 也經(jīng)常翻譯為原生類(lèi)型或者基本類(lèi)型)和引用類(lèi)型(Reference Types)棒妨。
final關(guān)鍵字修飾基本類(lèi)型和引用類(lèi)型會(huì)有些區(qū)別含长,這里分別進(jìn)行說(shuō)明。
修飾基本類(lèi)型
Java基本類(lèi)型包含數(shù)字類(lèi)型(整數(shù)類(lèi)型和浮點(diǎn)類(lèi)型)和布爾類(lèi)型纷纫。
數(shù)字類(lèi)型:byte, short, int, long, float, double
布爾類(lèi)型:boolean
設(shè)計(jì)
假設(shè)我們現(xiàn)在有這樣的一個(gè)需求:我們?cè)陬?lèi)中定義一個(gè)整型的屬性來(lái)代表某個(gè)邏輯執(zhí)行的次數(shù)田弥,這個(gè)次數(shù)是固定不變的,不希望它被改變商叹。
來(lái)看一下:
int count = 10;
我們?nèi)绻沁@樣簡(jiǎn)單的定義一個(gè)變量剖笙,可以達(dá)到這樣的效果嗎弥咪?答案是不行。我們可以對(duì)這個(gè)count進(jìn)行重新復(fù)制酷勺。實(shí)現(xiàn)上面所說(shuō)的需求扳躬,我們就可以用到final關(guān)鍵字來(lái)修飾這個(gè)表示次數(shù)的變量來(lái)告訴編譯器我們定義的是一個(gè)編譯時(shí)常量,在編譯時(shí)就已經(jīng)確定了击胜,運(yùn)行時(shí)是不能被改變的役纹。
效率
我們來(lái)看一下下面的代碼:
public class Final2 {
private final int a = 2;
private int b = 3;
public int calc1() {
return a * 2;
}
public int cacl2() {
return b * 2;
}
}
這里我們定義了兩個(gè)整型的變量a和b促脉,分別是final和非final的。
下面我們通過(guò)字節(jié)碼來(lái)進(jìn)行分析:
Classfile /Users/rocky/Desktop/Final2.class
Last modified Jan 26, 2019; size 388 bytes
MD5 checksum b032ca9258400a7c793c542102d63391
Compiled from "Final2.java"
public class Final2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#21 // Final2.a:I
#3 = Fieldref #4.#22 // Final2.b:I
#4 = Class #23 // Final2
#5 = Class #24 // java/lang/Object
#6 = Utf8 a
#7 = Utf8 I
#8 = Utf8 ConstantValue
#9 = Integer 2
#10 = Utf8 b
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 calc1
#16 = Utf8 ()I
#17 = Utf8 cacl2
#18 = Utf8 SourceFile
#19 = Utf8 Final2.java
#20 = NameAndType #11:#12 // "<init>":()V
#21 = NameAndType #6:#7 // a:I
#22 = NameAndType #10:#7 // b:I
#23 = Utf8 Final2
#24 = Utf8 java/lang/Object
{
public Final2();
descriptor: ()V
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: iconst_2
6: putfield #2 // Field a:I
9: aload_0
10: iconst_3
11: putfield #3 // Field b:I
14: return
LineNumberTable:
line 1: 0
line 3: 4
line 4: 9
public int calc1();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: iconst_4
1: ireturn
LineNumberTable:
line 7: 0
public int cacl2();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #3 // Field b:I
4: iconst_2
5: imul
6: ireturn
LineNumberTable:
line 11: 0
}
SourceFile: "Final2.java"
對(duì)于calc2方法,關(guān)鍵字節(jié)碼指令如下:
0: aload_0
1: getfield #3 // Field b:I
4: iconst_2
5: imul
6: ireturn
解讀:
第一步:通過(guò)getfield訪問(wèn)類(lèi)字段b;
第二步:將常量2加載到操作數(shù)棧;
第三步:執(zhí)行乘法運(yùn)算指令imul;
第四步:返回結(jié)果下硕。
再來(lái)看看calc1方法,關(guān)鍵字節(jié)碼指令如下:
0: iconst_4
1: ireturn
解讀:
第一步:將常量4加載到操作數(shù)棧中霜幼;
第二步:返回結(jié)果誉尖。
這里4表示的就是a*2的結(jié)果,沒(méi)有看到乘法運(yùn)算指令琢感。也就是說(shuō)對(duì)于包含有final常量的運(yùn)算表達(dá)式,在編譯器的時(shí)候編譯就已經(jīng)幫我們進(jìn)行了運(yùn)算烘挫。
總結(jié):對(duì)于編譯期常量這種情況柬甥,編譯器可以將常量值代入任何可能用到它的計(jì)算式中,也就是說(shuō)卤橄,可以在編譯時(shí)執(zhí)行計(jì)算式臂外,這減輕了一些運(yùn)行時(shí)的負(fù)擔(dān)。
修飾引用類(lèi)型
上面修飾基本類(lèi)型辜膝,表示變量的數(shù)值恒定不變漾肮,但是如果修飾的是引用類(lèi)型,表示的是引用恒定不變忱辅,一旦引用被初始化指向一個(gè)對(duì)象谭溉,就無(wú)法再把它改為執(zhí)行另一個(gè)對(duì)象。然后损搬,對(duì)象其自身卻是可以被修改的柜与。
比如說(shuō)a變量通過(guò)final修飾指向了一個(gè)對(duì)象b,用指針的概念來(lái)看就是這個(gè)a指向了b這個(gè)對(duì)象所在的內(nèi)存地址颅悉,a這個(gè)指向是不能被修改了迁匠,但是b這個(gè)對(duì)象內(nèi)存空間里面的數(shù)據(jù)是可以被修改的驹溃。
實(shí)例圖說(shuō)明:
2.修飾方法
用final修飾方法豌鹤,主要是為了把方法鎖定搂鲫,以防任何繼承類(lèi)修改它的含義。這是出于設(shè)計(jì)的考慮拐辽。
《Java編程思想》中提到使用final修改方法提高效率的問(wèn)題擦酌。同時(shí)也提到最近虛擬機(jī)不需要使用final修飾方法這樣的方式來(lái)進(jìn)行優(yōu)化。
3.修飾類(lèi)
當(dāng)用final修飾類(lèi)睁搭,從設(shè)計(jì)的角度上來(lái)看笼平,出于某種考慮,你對(duì)該類(lèi)的設(shè)計(jì)永不需要做任何變動(dòng)锌唾,或者處于安全的考慮 夺英,你不希望它有子類(lèi)。
二、空白final
Java允許生成"空白final"载萌,所謂空白final是指被聲明為final但又未給定初值的域。
但是要注意:無(wú)論什么情況可缚,編譯器都確闭啵空白final在使用前必須被初始化知给。
我們可以將final域的初始化放置到構(gòu)造函數(shù)中描姚,這樣的話可以做到根據(jù)對(duì)象而有所不同轩勘,卻又保持其恒定不變的特性怯邪。