java中final 與效率


 

關於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了,既保證了代碼安全,又能讓數組中的元素被訪問了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM