談到final關鍵字,想必很多人都不陌生,在使用匿名内部類的時候可能會經常用到final關鍵字。另外,Java中的String類就是一個final類,那麼今天我們就來了解final這個關鍵字的用法。下面是本文的目錄大綱:
一.final關鍵字的基本用法
二.深入了解final關鍵字
若有不正之處,請多多諒解并歡迎指正。
請尊重作者勞動成果,轉載請标明原文連結:
http://www.cnblogs.com/dolphin0520/p/3736238.html
在Java中,final關鍵字可以用來修飾類、方法和變量(包括成員變量和局部變量)。下面就從這三個方面來了解一下final關鍵字的基本用法。
1.修飾類
當用final修飾一個類時,表明這個類不能被繼承。也就是說,如果一個類你永遠不會讓他被繼承,就可以用final進行修飾。final類中的成員變量可以根據需要設為final,但是要注意final類中的所有成員方法都會被隐式地指定為final方法。

在使用final修飾類的時候,要注意謹慎選擇,除非這個類真的在以後不會用來繼承或者出于安全的考慮,盡量不要将類設計為final類。
2.修飾方法
下面這段話摘自《Java程式設計思想》第四版第143頁:
“使用final方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含 義;第二個原因是效率。在早期的Java實作版本中,會将final方法轉為内嵌調用。但是如果方法過于龐大,可能看不到内嵌調用帶來的任何性能提升。在 最近的Java版本中,不需要使用final方法進行這些優化了。“
是以,如果隻有在想明确禁止 該方法在子類中被覆寫的情況下才将方法設定為final的。
注:類的private方法會隐式地被指定為final方法。
3.修飾變量
修飾變量是final用得最多的地方,也是本文接下來要重點闡述的内容。首先了解一下final變量的基本文法:
對于一個final變量,如果是基本資料類型的變量,則其數值一旦在初始化之後便不能更改;如果是引用類型的變量,則在對其初始化之後便不能再讓其指向另一個對象。
舉個例子:
上面的一段代碼中,對變量i和obj的重新指派都報錯了。
在了解了final關鍵字的基本用法之後,這一節我們來看一下final關鍵字容易混淆的地方。
1.類的final變量和普通變量有什麼差別?
當用final作用于類的成員變量時,成員變量(注意是類的成員變量,局部變量隻需要保證在使用之前被初始化指派即可)必須在定義時或者構造器中進行初始化指派,而且final變量一旦被初始化指派之後,就不能再被指派了。
那麼final變量和普通變量到底有何差別呢?下面請看一個例子:
1
2
3
4
5
6
7
8
9
10
11
<code>public</code> <code>class</code> <code>Test {</code>
<code> </code><code>public</code> <code>static</code> <code>void</code> <code>main(String[] args) {</code>
<code> </code><code>String a =</code><code>"hello2"</code><code>; </code>
<code> </code><code>final</code> <code>String b =</code><code>"hello"</code><code>;</code>
<code> </code><code>String d =</code><code>"hello"</code><code>;</code>
<code> </code><code>String c = b +</code><code>2</code><code>; </code>
<code> </code><code>String e = d +</code><code>2</code><code>;</code>
<code> </code><code>System.out.println((a == c));</code>
<code> </code><code>System.out.println((a == e));</code>
<code> </code><code>}</code>
<code>}</code>
大家可以先想一下這道題的輸出結果。為什麼第一個比較結果為true,而第二個比較結果為fasle。這裡面就是final變量和普通變量的區 别了,當final變量是基本資料類型以及String類型時,如果在編譯期間能知道它的确切值,則編譯器會把它當做編譯期常量使用。也就是說在用到該 final變量的地方,相當于直接通路的這個常量,不需要在運作時确定。這種和C語言中的宏替換有點像。是以在上面的一段代碼中,由于變量b被final 修飾,是以會被當做編譯器常量,是以在使用到b的地方會直接将變量b 替換為它的 值。而對于變量d的通路卻需要在運作時通過連結來進行。想必其中的差別大家應該明白了,不過要注意,隻有在編譯期間能确切知道final變量值的情況下, 編譯器才會進行這樣的優化,比如下面的這段代碼就不會進行優化:
12
13
<code> </code><code>final</code> <code>String b = getHello();</code>
<code> </code>
<code> </code><code>public</code> <code>static</code> <code>String getHello() {</code>
<code> </code><code>return</code> <code>"hello"</code><code>;</code>
這段代碼的輸出結果為false。
2.被final修飾的引用變量指向的對象内容可變嗎?
在上面提到被final修飾的引用變量一旦初始化指派之後就不能再指向其他的對象,那麼該引用變量指向的對象的内容可變嗎?看下面這個例子:
<code> </code><code>final</code> <code>MyClass myClass =</code><code>new</code> <code>MyClass();</code>
<code> </code><code>System.out.println(++myClass.i);</code>
<code>class</code> <code>MyClass {</code>
<code> </code><code>public</code> <code>int</code> <code>i =</code><code>0</code><code>;</code>
這段代碼可以順利編譯通過并且有輸出結果,輸出結果為1。這說明引用變量被final修飾之後,雖然不能再指向其他對象,但是它指向的對象的内容是可變的。
3.final和static
很多時候會容易把static和final關鍵字混淆,static作用于成員變量用來表示隻儲存一份副本,而final的作用是用來保證變量不可變。看下面這個例子:
14
15
16
<code> </code><code>MyClass myClass1 =</code><code>new</code> <code>MyClass();</code>
<code> </code><code>MyClass myClass2 =</code><code>new</code> <code>MyClass();</code>
<code> </code><code>System.out.println(myClass1.i);</code>
<code> </code><code>System.out.println(myClass1.j);</code>
<code> </code><code>System.out.println(myClass2.i);</code>
<code> </code><code>System.out.println(myClass2.j);</code>
<code> </code><code>public</code> <code>final</code> <code>double</code> <code>i = Math.random();</code>
<code> </code><code>public</code> <code>static</code> <code>double</code> <code>j = Math.random();</code>
運作這段代碼就會發現,每次列印的兩個j值都是一樣的,而i的值卻是不同的。從這裡就可以知道final和static變量的差別了。
4.匿名内部類中使用的外部局部變量為什麼隻能是final變量?
5.關于final參數的問題
關于網上流傳的”當你在方法中不需要改變作為參數的對象變量時,明确使用final進行聲明,會防止你無意的修改而影響到調用方法外的變量“這句話,我個人了解這樣說是不恰當的。
因為無論參數是基本資料類型的變量還是引用類型的變量,使用final聲明都不會達到上面所說的效果。
看這個例子就清楚了:
上面這段代碼好像讓人覺得用final修飾之後,就不能在方法中更改變量i的值了。殊不 知,方法changeValue和main方法中的變量i根本就不是一個變量,因為java參數傳遞采用的是值傳遞,對于基本類型的變量,相當于直接将變 量進行了拷貝。是以即使沒有final修飾的情況下,在方法内部改變了變量i的值也不會影響方法外的i。
再看下面這段代碼:
<code> </code><code>MyClass myClass =</code><code>new</code> <code>MyClass();</code>
<code> </code><code>StringBuffer buffer =</code><code>new</code> <code>StringBuffer(</code><code>"hello"</code><code>);</code>
<code> </code><code>myClass.changeValue(buffer);</code>
<code> </code><code>System.out.println(buffer.toString());</code>
<code> </code><code>void</code> <code>changeValue(</code><code>final</code> <code>StringBuffer buffer) {</code>
<code> </code><code>buffer.append(</code><code>"world"</code><code>);</code>
運作這段代碼就會發現輸出結果為 helloworld。很顯然,用final進行修飾并沒有阻止在changeValue中改變buffer指向的對象的内容。有人說假如把final去 掉了,萬一在changeValue中讓buffer指向了其他對象怎麼辦。有這種想法的朋友可以自己動手寫代碼試一下這樣的結果是什麼,如果把 final去掉了,然後在changeValue中讓buffer指向了其他對象,也不會影響到main方法中的buffer,原因在于java采用的是 值傳遞,對于引用變量,傳遞的是引用的值,也就是說讓實參和形參同時指向了同一個對象,是以讓形參重新指向另一個對象對實參并沒有任何影響。
是以關于網上流傳的final參數的說法,我個人不是很贊同。