gdufo

           

          JAVA面試題解惑系列(四)——final、finally和finalize的區(qū)別

          轉(zhuǎn)自:
          http://www.javaeye.com/topic/213051
          作者:臧圩人(zangweiren)
          網(wǎng)址:http://zangweiren.javaeye.com

          >>>轉(zhuǎn)載請注明出處!<<<

          final、finally和finalize的區(qū)別是什么?

          這是一道再經(jīng)典不過的面試題了,我們在各個公司的面試題中幾乎都能看到它的身影。final、finally和finalize雖然長得像孿生三兄弟一樣,但是它們的含義和用法卻是大相徑庭。這一次我們就一起來回顧一下這方面的知識。

          我們首先來說說final。它可以用于以下四個地方:
          1. 定義變量,包括靜態(tài)的和非靜態(tài)的。
          2. 定義方法的參數(shù)。
          3. 定義方法。
          4. 定義類。

          我們依次來回顧一下每種情況下final的作用。首先來看第一種情況,如果final修飾的是一個基本類型,就表示這個變量被賦予的值是不可變的,即它是個常量;如果final修飾的是一個對象,就表示這個變量被賦予的引用是不可變的,這里需要提醒大家注意的是,不可改變的只是這個變量所保存的引用,并不是這個引用所指向的對象。在第二種情況下,final的含義與第一種情況相同。實際上對于前兩種情況,有一種更貼切的表述final的含義的描述,那就是,如果一個變量或方法參數(shù)被final修飾,就表示它只能被賦值一次,但是JAVA虛擬機為變量設(shè)定的默認(rèn)值不記作一次賦值。

          被final修飾的變量必須被初始化。初始化的方式有以下幾種:
          1. 在定義的時候初始化。
          2. 在初始化塊中初始化。
          3. 在類的構(gòu)造器中初始化。
          4. 靜態(tài)變量也可以在靜態(tài)初始化塊中初始化。

          通過下面的代碼可以驗證以上的觀點:
           1public class FinalTest {   
           2    // 在定義時初始化   
           3    public final int A = 10;   
           4  
           5    public final int B;   
           6    // 在初始化塊中初始化   
           7    {   
           8        B = 20;   
           9    }
             
          10  
          11    // 靜態(tài)常量,在定義時初始化   
          12    public static final int STATIC_C = 30;   
          13  
          14    public static final int STATIC_D;   
          15    // 靜態(tài)常量,在靜態(tài)初始化塊中初始化   
          16    static {   
          17        STATIC_D = 40;   
          18    }
             
          19  
          20    public final int E;   
          21  
          22    public static int STATIC_F;   
          23  
          24    // 在構(gòu)造器中初始化   
          25    public FinalTest() {   
          26        E = 50;   
          27        // 靜態(tài)變量也可以在構(gòu)造器中初始化   
          28        STATIC_F = 60;   
          29  
          30        // 給final的變量第二次賦值時,編譯會報錯   
          31        // A = 99;   
          32        // STATIC_C = 99;   
          33    }
             
          34  
          35    // 靜態(tài)變量不能在初始化塊中初始化   
          36    // public static final int STATIC_G;   
          37    // {   
          38    // STATIC_G = 70;   
          39    // }   
          40  
          41    // final變量未被初始化,編譯時就會報錯   
          42    // public final int H;   
          43  
          44    // 靜態(tài)final變量未被初始化,編譯時就會報錯   
          45    // public static final int STATIC_I;   
          46}
            
          47

          我們運行上面的代碼之后出了可以發(fā)現(xiàn)final變量(常量)和靜態(tài)final變量(靜態(tài)常量)未被初始化時,編譯會報錯;另外還可以發(fā)現(xiàn),靜態(tài)final變量可以在構(gòu)造器中初始化,卻不可以在初始化塊中初始化。

          用final修飾的變量(常量)比非final的變量(普通變量)擁有更高的效率,因此我們在實際編程中應(yīng)該盡可能多的用常量來代替普通變量,這也是一個很好的編程習(xí)慣。

          當(dāng)final用來定義一個方法時,會有什么效果呢?正如大家所知,它表示這個方法不可以被子類重寫,但是它這不影響它被子類繼承。我們寫段代碼來驗證一下:

           1class ParentClass {   
           2    public final void TestFinal() {   
           3        System.out.println("父類--這是一個final方法");   
           4    }
             
           5}
             
           6  
           7public class SubClass extends ParentClass {   
           8    /**  
           9     * 子類無法重寫(override)父類的final方法,否則編譯時會報錯  
          10     */
            
          11    // public void TestFinal() {   
          12    // System.out.println("子類--重寫final方法");   
          13    // }   
          14       
          15    public static void main(String[] args) {   
          16        SubClass sc = new SubClass();   
          17        sc.TestFinal();   
          18    }
             
          19}
            
          20

           

          這里需要特殊說明的是,具有private訪問權(quán)限的方法也可以增加final修飾,但是由于子類無法繼承private方法,因此也無法重寫它。編譯器在處理private方法時,是按照final方法來對待的,這樣可以提高該方法被調(diào)用時的效率。不過子類仍然可以定義同父類中的private方法具有同樣結(jié)構(gòu)的方法,但是這并不會產(chǎn)生重寫的效果,而且它們之間也不存在必然聯(lián)系。

          最后我們再來回顧一下final用于類的情況。這個大家應(yīng)該也很熟悉了,因為我們最常用的String類就是final的。由于final類不允許被繼承,編譯器在處理時把它的所有方法都當(dāng)作final的,因此final類比普通類擁有更高的效率。final的類的所有方法都不能被重寫,但這并不表示final的類的屬性(變量)值也是不可改變的,要想做到final類的屬性值不可改變,必須給它增加final修飾,請看下面的例子:

           1public final class FinalTest {   
           2  
           3    int i = 10;   
           4  
           5    public static void main(String[] args) {   
           6        FinalTest ft = new FinalTest();   
           7        ft.i = 99;   
           8        System.out.println(ft.i);   
           9    }
             
          10}
            
          11

          運行上面的代碼試試看,結(jié)果是99,而不是初始化時的10。

          接下來我們一起回顧一下finally的用法。這個就比較簡單了,它只能用在try/catch語句中,并且附帶著一個語句塊,表示這段語句最終總是被執(zhí)行。請看下面的代碼:

           1public final class FinallyTest {   
           2    public static void main(String[] args) {   
           3        try {   
           4            throw new NullPointerException();   
           5        }
           catch (NullPointerException e) {   
           6            System.out.println("程序拋出了異常");   
           7        }
           finally {   
           8            System.out.println("執(zhí)行了finally語句塊");   
           9        }
             
          10    }
             
          11}
            
          12
          運行結(jié)果說明了finally的作用:
          1. 程序拋出了異常
          2. 執(zhí)行了finally語句塊

          請大家注意,捕獲程序拋出的異常之后,既不加處理,也不繼續(xù)向上拋出異常,并不是良好的編程習(xí)慣,它掩蓋了程序執(zhí)行中發(fā)生的錯誤,這里只是方便演示,請不要學(xué)習(xí)。

          那么,有沒有一種情況使finally語句塊得不到執(zhí)行呢?大家可能想到了return、continue、break這三個可以打亂代碼順序執(zhí)行語句的規(guī)律。那我們就來試試看,這三個語句是否能影響finally語句塊的執(zhí)行:

           1public final class FinallyTest {   
           2  
           3    // 測試return語句   
           4    public ReturnClass testReturn() {   
           5        try {   
           6            return new ReturnClass();   
           7        }
           catch (Exception e) {   
           8            e.printStackTrace();   
           9        }
           finally {   
          10            System.out.println("執(zhí)行了finally語句");   
          11        }
             
          12        return null;   
          13    }
             
          14  
          15    // 測試continue語句   
          16    public void testContinue() {   
          17        for (int i = 0; i < 3; i++{   
          18            try {   
          19                System.out.println(i);   
          20                if (i == 1{   
          21                    continue;   
          22                }
             
          23            }
           catch (Exception e) {   
          24                e.printStackTrace();   
          25            }
           finally {   
          26                System.out.println("執(zhí)行了finally語句");   
          27            }
             
          28        }
             
          29    }
             
          30  
          31    // 測試break語句   
          32    public void testBreak() {   
          33        for (int i = 0; i < 3; i++{   
          34            try {   
          35                System.out.println(i);   
          36                if (i == 1{   
          37                    break;   
          38                }
             
          39            }
           catch (Exception e) {   
          40                e.printStackTrace();   
          41            }
           finally {   
          42                System.out.println("執(zhí)行了finally語句");   
          43            }
             
          44        }
             
          45    }
             
          46  
          47    public static void main(String[] args) {   
          48        FinallyTest ft = new FinallyTest();   
          49        // 測試return語句   
          50        ft.testReturn();   
          51        System.out.println();   
          52        // 測試continue語句   
          53        ft.testContinue();   
          54        System.out.println();   
          55        // 測試break語句   
          56        ft.testBreak();   
          57    }
             
          58}
             
          59  
          60class ReturnClass {   
          61    public ReturnClass() {   
          62        System.out.println("執(zhí)行了return語句");   
          63    }
             
          64}
            
          65


          上面這段代碼的運行結(jié)果如下:

          1. 執(zhí)行了return語句
          2. 執(zhí)行了finally語句
          3. 0
          4. 執(zhí)行了finally語句
          5. 1
          6. 執(zhí)行了finally語句
          7. 2
          8. 執(zhí)行了finally語句
          9. 0
          10. 執(zhí)行了finally語句
          11. 1
          12. 執(zhí)行了finally語句


          很明顯,return、continue和break都沒能阻止finally語句塊的執(zhí)行。從輸出的結(jié)果來看,return語句似乎在finally語句塊之前執(zhí)行了,事實真的如此嗎?我們來想想看,return語句的作用是什么呢?是退出當(dāng)前的方法,并將值或?qū)ο蠓祷亍H绻鹒inally語句塊是在return語句之后執(zhí)行的,那么return語句被執(zhí)行后就已經(jīng)退出當(dāng)前方法了,finally語句塊又如何能被執(zhí)行呢?因此,正確的執(zhí)行順序應(yīng)該是這樣的:編譯器在編譯return new ReturnClass();時,將它分成了兩個步驟,new ReturnClass()和return,前一個創(chuàng)建對象的語句是在finally語句塊之前被執(zhí)行的,而后一個return語句是在finally語句塊之后執(zhí)行的,也就是說finally語句塊是在程序退出方法之前被執(zhí)行的。同樣,finally語句塊是在循環(huán)被跳過(continue)和中斷(break)之前被執(zhí)行的。

          最后,我們再來看看finalize,它是一個方法,屬于java.lang.Object類,它的定義如下

           

          protected void finalize() throws Throwable { }  

           

          眾所周知,finalize()方法是GC(garbage collector)運行機制的一部分,關(guān)于GC的知識我們將在后續(xù)的章節(jié)中來回顧。

          在此我們只說說finalize()方法的作用是什么呢?

          finalize()方法是在GC清理它所從屬的對象時被調(diào)用的,如果執(zhí)行它的過程中拋出了無法捕獲的異常(uncaught exception),GC將終止對改對象的清理,并且該異常會被忽略;直到下一次GC開始清理這個對象時,它的finalize()會被再次調(diào)用。

          請看下面的示例:

           

          public final class FinallyTest {   
              
          // 重寫finalize()方法   
              protected void finalize() throws Throwable {   
                  System.out.println(
          "執(zhí)行了finalize()方法");   
              }
             
            
              
          public static void main(String[] args) {   
                  FinallyTest ft 
          = new FinallyTest();   
                  ft 
          = null;   
                  System.gc();   
              }
             
          }
            

          運行結(jié)果如下:
          • 執(zhí)行了finalize()方法

          程序調(diào)用了java.lang.System類的gc()方法,引起GC的執(zhí)行,GC在清理ft對象時調(diào)用了它的finalize()方法,因此才有了上面的輸出結(jié)果。調(diào)用System.gc()等同于調(diào)用下面這行代碼:
          Java代碼 復(fù)制代碼
          1. Runtime.getRuntime().gc();  

          調(diào)用它們的作用只是建議垃圾收集器(GC)啟動,清理無用的對象釋放內(nèi)存空間,但是GC的啟動并不是一定的,這由JAVA虛擬機來決定。直到JAVA虛擬機停止運行,有些對象的finalize()可能都沒有被運行過,那么怎樣保證所有對象的這個方法在JAVA虛擬機停止運行之前一定被調(diào)用呢?答案是我們可以調(diào)用System類的另一個方法:
          Java代碼 復(fù)制代碼
          1. public static void runFinalizersOnExit(boolean value) {   
          2.     //other code   
          3. }  

          給這個方法傳入true就可以保證對象的finalize()方法在JAVA虛擬機停止運行前一定被運行了,不過遺憾的是這個方法是不安全的,它會導(dǎo)致有用的對象finalize()被誤調(diào)用,因此已經(jīng)不被贊成使用了。

          由于finalize()屬于Object類,因此所有類都有這個方法,Object的任意子類都可以重寫(override)該方法,在其中釋放系統(tǒng)資源或者做其它的清理工作,如關(guān)閉輸入輸出流。

          通過以上知識的回顧,我想大家對于final、finally、finalize的用法區(qū)別已經(jīng)很清楚了。

          posted on 2008-08-22 11:37 gdufo 閱讀(211) 評論(0)  編輯  收藏 所屬分類: JAVA 基礎(chǔ)

          導(dǎo)航

          統(tǒng)計

          常用鏈接

          留言簿(6)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          收藏夾

          Hibernate

          友情鏈接

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 遂溪县| 沧州市| 德安县| 旬阳县| 平远县| 晋州市| 平湖市| 获嘉县| 六枝特区| 德令哈市| 兰坪| 安龙县| 荣成市| 上栗县| 迁安市| 茂名市| 新竹市| 清新县| 义马市| 双鸭山市| 黄大仙区| 晋宁县| 金湖县| 云浮市| 沙田区| 漳州市| 大英县| 鄂托克旗| 扶余县| 水富县| 日土县| 乌鲁木齐市| 辽阳县| 平阳县| 桂东县| 勃利县| 博客| 嘉黎县| 仁怀市| 大田县| 集安市|