關於final關鍵字,總是那么些疑惑,今天就總結一下。
一、final的概念:在Java中, 可以使用final關鍵字修飾類、方法以及成員變量。
1、final標記的類不能被繼承;
在設計類時候,如果這個類不需要有子類,類的實現細節不允許改變,並且確信這個類不會載被擴展,那么就設計為final類。2、final標記的方法不能被子類復寫;
如果一個類不允許其子類覆蓋某個方法,則可以把這個方法聲明為final方法。
使用final方法的原因有二:
第一、把方法鎖定,防止任何繼承類修改它的意義和實現。
第二、高效。編譯器在遇到調用final方法時候會轉入內嵌機制,大大提高執行效率。
3、final標記的變量即成為常量,只能被賦值一次.
4. 一個既是static 又是final的域只占據一段不能改變的存儲空間
二、final的用法
1、final 對於常量來說,意味着值不能改變,例如 final int i=100。這個i的值永遠都是100。
但是對於變量來說又不一樣,只是標識這個引用不可被改變,例如 final File f=new File("c:\test.txt");那么這個f一定是不能被改變的,如果f本身有方法修改其中的成員變量,例如是否可讀,是允許修改的。有個形象的比喻:一個女子定義了一個final的老公,這個老公的職業和收入都是允許改變的,只是這個女人不會換老公而已。2、關於空白final
final修飾的變量有三種:靜態變量、實例變量和局部變量,分別表示三種類型的常量。
另外,final變量定義的時候,可以先聲明,而不給初值,這中變量也稱為final空白,無論什么情況,編譯器都確保空白final在使用之前必須被初始化。但是,final空白在final關鍵字final的使用上提供了更大的靈活性,為此,一個類中的final數據成員就可以實現依對象而有所不同,卻有保持其恆定不變的特征。
public class FinalTest {
final int p;
final int q=3;
FinalTest(){
p=1;
}
FinalTest(int i){
p=i;//可以賦值,相當於直接定義p
q=i;//不能為一個final變量賦值
}
}
3、final內存分配
剛提到了內嵌機制,現在詳細展開。
要知道調用一個函數除了函數本身的執行時間之外,還需要額外的時間去尋找這個函數(類內部有一個函數簽名和函數地址的映射表)。所以減少函數調用次數就等於降低了性能消耗。
final修飾的函數會被編譯器優化,優化的結果是減少了函數調用的次數。如何實現的,舉個例子給你看:
public class Test{
final void func(){
System.out.println("g");
}
public void main(String[] args){
for(int j=0; j<1000; j++)
func();
}
}
經過編譯器優化之后,這個類變成了相當於這樣寫:
public class Test{
final void func(){
System.out.println("g");
}
public void main(String[] args){
for(int j=0; j<1000; j++){
System.out.println("g");
}
}
}
看出來區別了吧?編譯器直接將func的函數體內嵌到了調用函數的地方,這樣的結果是節省了1000次函數調用,當然編譯器處理成字節碼,只是我們可以想象成這樣,看個明白。
不過,當函數體太長的話,用final可能適得其反,因為經過編譯器內嵌之后代碼長度大大增加,於是就增加了jvm解釋字節碼的時間。
在使用final修飾方法的時候,編譯器會將被final修飾過的方法插入到調用者代碼處,提高運行速度和效率,但被final修飾的方法體不能過大,編譯器可能會放棄內聯,但究竟多大的方法會放棄,我還沒有做測試來計算過。
本文是通過兩個疑問來繼續闡述的:
1、使用final修飾方法會提高速度和效率嗎;
見下面的測試代碼,我會執行五次:
public class Test {
public static void getJava() {
String str1 = "Java ";
String str2 = "final ";
for (int i = 0; i < 10000; i++) {
str1 += str2;
}
}
public static final void getJava_Final() {
String str1 = "Java ";
String str2 = "final ";
for (int i = 0; i < 10000; i++) {
str1 += str2;
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
getJava();
System.out.println("調用不帶final修飾的方法執行時間為:" + (System.currentTimeMillis() - start) + "毫秒時間");
start = System.currentTimeMillis();
String str1 = "Java ";
String str2 = "final ";
for (int i = 0; i < 10000; i++) {
str1 += str2;
}
System.out.println("正常的執行時間為:" + (System.currentTimeMillis() - start) + "毫秒時間");
start = System.currentTimeMillis();
getJava_Final();
System.out.println("調用final修飾的方法執行時間為:" + (System.currentTimeMillis() - start) + "毫秒時間");
}
}
結果為:
第一次:
調用不帶final修飾的方法執行時間為:1732毫秒時間
正常的執行時間為:1498毫秒時間
調用final修飾的方法執行時間為:1593毫秒時間
第二次:
調用不帶final修飾的方法執行時間為:1217毫秒時間
正常的執行時間為:1031毫秒時間
調用final修飾的方法執行時間為:1124毫秒時間
第三次:
調用不帶final修飾的方法執行時間為:1154毫秒時間
正常的執行時間為:1140毫秒時間
調用final修飾的方法執行時間為:1202毫秒時間
第四次:
調用不帶final修飾的方法執行時間為:1139毫秒時間
正常的執行時間為:999毫秒時間
調用final修飾的方法執行時間為:1092毫秒時間
第五次:
調用不帶final修飾的方法執行時間為:1186毫秒時間
正常的執行時間為:1030毫秒時間
調用final修飾的方法執行時間為:1109毫秒時間
由以上運行結果不難看出,執行最快的是“正常的執行”即代碼直接編寫,而使用final修飾的方法,不像有些書上或者文章上所說的那樣,速度與效率與“正常的執行”無異,而是位於第二位,最差的是調用不加final修飾的方法。
觀點:加了比不加好一點。
1、使用final修飾變量會讓變量的值不能被改變嗎;
見代碼:
public class Final {
public static void main(String[] args) {
Color.color[3] = "white";
for (String color : Color.color)
System.out.print(color+" ");
}
}
class Color {
public static final String[] color = { "red", "blue", "yellow", "black" };
}
執行結果:
red blue yellow white
看!,黑色變成了白色。
在使用findbugs插件時,就會提示public static String[] color = { “red”, “blue”, “yellow”, “black” };這行代碼不安全,但加上final修飾,這行代碼仍然是不安全的,因為final沒有做到保證變量的值不會被修改!原因是:final關鍵字只能保證變量本身不能被賦與新值,而不能保證變量的內部結構不被修改。例如在main方法有如下代碼Color.color = new String[]{“”};就會報錯了。那可能有的同學就會問了,加上final關鍵字不能保證數組不會被外部修改,那有什么方法能夠保證呢?答案就是降低訪問級別,把數組設為private。這樣的話,就解決了數組在外部被修改的不安全性,但也產生了另一個問題,那就是這個數組要被外部使用的。
解決這個問題見代碼:
import java.util.AbstractList;
import java.util.List;
public class Final {
public static void main(String[] args) {
for (String color : Color.color)
System.out.print(color + " ");
Color.color.set(3, "white");
}
}
class Color {
private static String[] _color = { "red", "blue", "yellow", "black" };
public static List<String> color = new AbstractList<String>() {
@Override
public String get(int index) {
return _color[index];
}
@Override
public String set(int index, String value) {
throw new RuntimeException("為了代碼安全,不能修改數組");
}
@Override
public int size() {
return _color.length;
}
};
這樣就OK了,既保證了代碼安全,又能讓數組中的元素被訪問了。