Java提高篇 —— Java关键字之final的几种用法
一、前言
?
? ? ? ?在java的關(guān)鍵字中,static和final是兩個(gè)我們必須掌握的關(guān)鍵字。不同于其他關(guān)鍵字,他們都有多種用法,而且在一定環(huán)境下使用,可以提高程序的運(yùn)行性能,優(yōu)化程序的結(jié)構(gòu)。下面我們來了解一下final關(guān)鍵字及其用法。
?
二、final關(guān)鍵字
?
? ? ? ?在java中,final的含義在不同的場(chǎng)景下有細(xì)微的差別,但總體上來說,它指的是“這是不可變的”。不想被改變的原因有兩個(gè):效率、設(shè)計(jì)。使用到final的有三種情況:數(shù)據(jù)、方法、類。下面,我們來講final的四種主要用法。
?
1、修飾變量
? ? ? ?有時(shí)候數(shù)據(jù)的恒定不變是很有用的,它能夠減輕系統(tǒng)運(yùn)行時(shí)的負(fù)擔(dān)。對(duì)于這些恒定不變的數(shù)據(jù)我可以叫做“常量”。在java中,用final關(guān)鍵字修飾的變量,只能進(jìn)行一次賦值操作,并且在生存期內(nèi)不可以改變它的值?!俺A俊敝饕獞?yīng)用與以下兩個(gè)地方:
? ? ? ?1、編譯期常量,永遠(yuǎn)不可改變。
? ? ? ?2、運(yùn)行期初始化時(shí),我們希望它不會(huì)被改變。
? ? ? ?對(duì)于編譯期常量,它在類加載的過程就已經(jīng)完成了初始化,所以當(dāng)類加載完成后是不可更改的,編譯期可以將它代入到任何用到它的計(jì)算式中,也就是說可以在編譯期執(zhí)行計(jì)算式。當(dāng)然對(duì)于編譯期常量,只能使用基本類型,而且必須要在定義時(shí)進(jìn)行初始化。
? ? ? ?有些變量,我們希望它可以根據(jù)對(duì)象的不同而表現(xiàn)不同,但同時(shí)又不希望它被改變,這個(gè)時(shí)候我們就可以使用運(yùn)行期常量。對(duì)于運(yùn)行期常量,它既可是基本數(shù)據(jù)類型,也可是引用數(shù)據(jù)類型。基本數(shù)據(jù)類型不可變的是其內(nèi)容,而引用數(shù)據(jù)類型不可變的是其引用,引用所指定的對(duì)象內(nèi)容是可變的。不過在針對(duì)基本類型和引用類型時(shí),final關(guān)鍵字的效果存在細(xì)微差別。我們來看下面的例子:
class Value {int v;public Value(int v) {this.v = v;} }public class FinalTest {final int f1 = 1;final int f2;public FinalTest() {f2 = 2;}public static void main(String[] args) {final int value1 = 1;// value1 = 4;final double value2;value2 = 2.0;final Value value3 = new Value(1);value3.v = 4;} }? ? ? ?上面的例子中,我們先來看一下main方法中的幾個(gè)final修飾的數(shù)據(jù),在給value1賦初始值之后,我們無法再對(duì)value1的值進(jìn)行修改,final關(guān)鍵字起到了常量的作用。從value2我們可以看到,final修飾的變量可以不在聲明時(shí)賦值,即可以先聲明,后賦值。value3時(shí)一個(gè)引用變量,這里我們可以看到final修飾引用變量時(shí),只是限定了引用變量的引用不可改變,即不能將value3再次引用另一個(gè)Value對(duì)象,但是引用的對(duì)象的值是可以改變的,從內(nèi)存模型中我們看的更加清晰:
上圖中,final修飾的值用粗線條的邊框表示它的值是不可改變的,我們知道引用變量的值實(shí)際上是它所引用的對(duì)象的地址,也就是說該地址的值是不可改變的,從而說明了為什么引用變量不可以改變引用對(duì)象。而實(shí)際引用的對(duì)象實(shí)際上是不受final關(guān)鍵字的影響的,所以它的值是可以改變的。
另一方面,我們看到了用final修飾成員變量時(shí)的細(xì)微差別,因?yàn)閒inal修飾的數(shù)據(jù)的值是不可改變的,所以我們必須確保在使用前就已經(jīng)對(duì)成員變量賦值了。因此對(duì)于final修飾的成員變量,我們有且只有兩個(gè)地方可以給它賦值,一個(gè)是聲明該成員時(shí)賦值,另一個(gè)是在構(gòu)造方法中賦值,在這兩個(gè)地方我們必須給它們賦初始值。
最后我們需要注意的一點(diǎn)是,同時(shí)使用static和final修飾的成員在內(nèi)存中只占據(jù)一段不能改變的存儲(chǔ)空間。
?
2、修飾方法參數(shù)
? ? ? ?前面我們可以看到,如果變量是我們自己創(chuàng)建的,那么使用final修飾表示我們只會(huì)給它賦值一次且不會(huì)改變變量的值。那么如果變量是作為參數(shù)傳入的,我們?cè)趺幢WC它的值不會(huì)改變呢?這就用到了final的第二種用法,即在我們編寫方法時(shí),可以在參數(shù)前面添加final關(guān)鍵字,它表示在整個(gè)方法中,我們不會(huì)(實(shí)際上是不能)改變參數(shù)的值:
public class FinalTest {/* ... */public void finalFunc(final int i, final Value value) {// i = 5; 不能改變i的值// v = new Value(); 不能改變v的值value.v = 5; // 可以改變引用對(duì)象的值} }?
3、修飾方法
? ? ? ?第三種方式,即用final關(guān)鍵字修飾方法,它表示該方法不能被覆蓋。這種使用方式主要是從設(shè)計(jì)的角度考慮,即明確告訴其他可能會(huì)繼承該類的程序員,不希望他們?nèi)ジ采w這個(gè)方法。這種方式我們很容易理解。
下面這段話摘自《Java編程思想》第四版第143頁(yè):
? ? ? ?“使用final方法的原因有兩個(gè)。第一個(gè)原因是把方法鎖定,以防任何繼承類修改它的含義;第二個(gè)原因是效率。在java的早期實(shí)現(xiàn)中,如果將一個(gè)方法指明為final,就是同意編譯器將針對(duì)該方法的所有調(diào)用都轉(zhuǎn)為內(nèi)嵌調(diào)用。當(dāng)編譯器發(fā)現(xiàn)一個(gè)final方法調(diào)用命令時(shí),它會(huì)根據(jù)自己的謹(jǐn)慎判斷,跳過插入程序代碼這種正常的調(diào)用方式而執(zhí)行方法調(diào)用機(jī)制(將參數(shù)壓入棧,跳至方法代碼處執(zhí)行,然后跳回并清理?xiàng)V械膮?shù),處理返回值),并且以方法體中的實(shí)際代碼的副本來代替方法調(diào)用。這將消除方法調(diào)用的開銷。當(dāng)然,如果一個(gè)方法很大,你的程序代碼會(huì)膨脹,因而可能看不到內(nèi)嵌所帶來的性能上的提高,因?yàn)樗鶐淼男阅軙?huì)花費(fèi)于方法內(nèi)的時(shí)間量而被縮減。在最近的Java版本中,不需要使用final方法進(jìn)行這些優(yōu)化了?!?/p>
因此,如果只有在想明確禁止該方法在子類中被覆蓋的情況下才將方法設(shè)置為final的。
? ? ? ?注:類的private方法會(huì)隱式地被指定為final方法。
?
4、修飾類
? ? ? ?當(dāng)用final修飾一個(gè)類時(shí),表明這個(gè)類不能被繼承。也就是說,如果一個(gè)類你永遠(yuǎn)不會(huì)讓他被繼承,就可以用final進(jìn)行修飾。final類中的成員變量可以根據(jù)需要設(shè)為final,但是要注意final類中的所有成員方法都會(huì)被隱式地指定為final方法。
? ? ? ?注:在使用final修飾類的時(shí)候,要注意謹(jǐn)慎選擇,除非這個(gè)類真的在以后不會(huì)用來繼承或者出于安全的考慮,盡量不要將類設(shè)計(jì)為final類。
?
? ? ? ?上面我們講解了final的四種用法,然而,對(duì)于第三種和第四種用法,我們卻甚少使用。這不是沒有道理的,從final的設(shè)計(jì)來講,這兩種用法甚至可以說是雞肋,因?yàn)閷?duì)于開發(fā)人員來講,如果我們寫的類被繼承的越多,就說明我們寫的類越有價(jià)值,越成功。即使是從設(shè)計(jì)的角度來講,也沒有必要將一個(gè)類設(shè)計(jì)為不可繼承的。Java標(biāo)準(zhǔn)庫(kù)就是一個(gè)很好的反例,特別是Java 1.0/1.1中Vector類被如此廣泛的運(yùn)用,如果所有的方法均未被指定為final的話,它可能會(huì)更加有用。如此有用的類,我們很容易想到去繼承和重寫他們,然而,由于final的作用,導(dǎo)致我們對(duì)Vector類的擴(kuò)展受到了一些阻礙,導(dǎo)致了Vector并沒有完全發(fā)揮它應(yīng)有的全部?jī)r(jià)值。
?
三、深入理解final關(guān)鍵字
?
? ? ? ?在了解了final關(guān)鍵字的基本用法之后,這一節(jié)我們來看一下final關(guān)鍵字容易混淆的地方。
?
1、類的final變量和普通變量有什么區(qū)別?
? ? ? ?當(dāng)用final作用于類的成員變量時(shí),成員變量(注意是類的成員變量,局部變量只需要保證在使用之前被初始化賦值即可)必須在定義時(shí)或者構(gòu)造器中進(jìn)行初始化賦值,而且final變量一旦被初始化賦值之后,就不能再被賦值了。
那么final變量和普通變量到底有何區(qū)別呢?下面請(qǐng)看一個(gè)例子:
public class Test {public static void main(String[] args) {String a = "hello2"; final String b = "hello";String d = "hello";String c = b + 2; String e = d + 2;System.out.println((a == c));System.out.println((a == e));}/*** Output:* true* false*/ }? ? ? ?大家可以先想一下這道題的輸出結(jié)果。為什么第一個(gè)比較結(jié)果為true,而第二個(gè)比較結(jié)果為fasle。這里面就是final變量和普通變量的區(qū)別了,當(dāng)final變量是基本數(shù)據(jù)類型以及String類型時(shí),如果在編譯期間能知道它的確切值,則編譯器會(huì)把它當(dāng)做編譯期常量使用。也就是說在用到該final變量的地方,相當(dāng)于直接訪問的這個(gè)常量,不需要在運(yùn)行時(shí)確定。這種和C語言中的宏替換有點(diǎn)像。因此在上面的一段代碼中,由于變量b被final修飾,因此會(huì)被當(dāng)做編譯器常量,所以在使用到b的地方會(huì)直接將變量b 替換為它的? 值。而對(duì)于變量d的訪問卻需要在運(yùn)行時(shí)通過鏈接來進(jìn)行。想必其中的區(qū)別大家應(yīng)該明白了,不過要注意,只有在編譯期間能確切知道final變量值的情況下,編譯器才會(huì)進(jìn)行這樣的優(yōu)化,比如下面的這段代碼就不會(huì)進(jìn)行優(yōu)化:
public class Test {public static void main(String[] args) {String a = "hello2"; final String b = getHello();String c = b + 2; System.out.println((a == c));}public static String getHello() {return "hello";}/*** Output:* false*/ }? ? ? ?這段代碼的輸出結(jié)果為false。
?
2、被final修飾的引用變量指向的對(duì)象內(nèi)容可變嗎?
? ? ? ?在上面提到被final修飾的引用變量一旦初始化賦值之后就不能再指向其他的對(duì)象,那么該引用變量指向的對(duì)象的內(nèi)容可變嗎?看下面這個(gè)例子:
public class Test {public static void main(String[] args) {final MyClass myClass = new MyClass();System.out.println(++myClass.i);} }class MyClass {public int i = 0; }? ? ? ?這段代碼可以順利編譯通過并且有輸出結(jié)果,輸出結(jié)果為1。這說明引用變量被final修飾之后,雖然不能再指向其他對(duì)象,但是它指向的對(duì)象的內(nèi)容是可變的。
?
3、final和static
? ? ? ?很多時(shí)候會(huì)容易把static和final關(guān)鍵字混淆,static作用于成員變量用來表示只保存一份副本,而final的作用是用來保證變量不可變。看下面這個(gè)例子:
public class Test {public static void main(String[] args) {MyClass myClass1 = new MyClass();MyClass myClass2 = new MyClass();System.out.println(myClass1.i);System.out.println(myClass1.j);System.out.println(myClass2.i);System.out.println(myClass2.j);} }class MyClass {public final double i = Math.random();public static double j = Math.random(); }? ? ? ?運(yùn)行這段代碼就會(huì)發(fā)現(xiàn),每次打印的兩個(gè)i值都是一樣的,而j的值卻是不同的。從這里就可以知道final和static變量的區(qū)別了。
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的Java提高篇 —— Java关键字之final的几种用法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 支付宝星愿攒钱怎么关闭
- 下一篇: 农行etc信用卡是金卡吗?年费都可以减免