天天看點

Java中final、finally、finalize的差別與用法

參考連結: Java之final、finally和finalize之間的差別

1.簡單差別: final用于聲明屬性,方法和類,分别表示屬性不可交變,方法不可覆寫,類不可繼承。 finally是異常處理語句結構的一部分,表示總是執行。 finalize是Object類的一個方法,在垃圾收集器執行的時候會調用被回收對象的此方法,供垃圾收集時的其他資源回收,例如關閉檔案等。 2.中等差別: 雖然這個單詞在Java中都存在,但是并沒太多關聯: final:java中的關鍵字,修飾符。 A).如果一個類被聲明為final,就意味着它不能再派生出新的子類,不能作為父類被繼承。是以,一個類不能同時被聲明為abstract抽象類的和final的類。 B).如果将變量或者方法聲明為final,可以保證它們在使用中不被改變.   1)被聲明為final的變量必須在聲明時給定初值,而在以後的引用中隻能讀取,不可修改。   2)被聲明final的方法隻能使用,不能重載。 finally:java的一種異常處理機制。   finally是對Java異常處理模型的最佳補充。finally結構使代碼總會執行,而不管無異常發生。使用finally可以維護對象的内部狀态,并可以清理非記憶體資源。特别是在關閉資料庫連接配接這方面,如果程式員把資料庫連接配接的close()方法放到finally中,就會大大降低程式出錯的幾率。 finalize:Java中的一個方法名。 Java技術使用finalize()方法在垃圾收集器将對象從記憶體中清除出去前,做必要的清理工作。這個方法是由垃圾收集器在确定這個對象沒被引用時對這個對象調用的。它是在Object類中定義的,是以所的類都繼承了它。子類覆寫finalize()方法以整理系統資源或者執行其他清理工作。finalize()方法是在垃圾收集器删除對象之前對這個對象調用的。 3.詳細差別: 這是一道再經典不過的面試題了,我們在各個公司的面試題中幾乎都能看到它的身影。final、finally和finalize雖然長得像孿生兄弟一樣,但是它們的含義和用法卻是大相徑庭。 final關鍵字我們首先來說說final。它可以用于以下四個地方: 1).定義變量,包括靜态的和非靜态的。 2).定義方法的參數。 3).定義方法。 4).定義類。 定義變量,包括靜态的和非靜态的。定義方法的參數 第一種情況: 如果final修飾的是一個基本類型,就表示這個變量被賦予的值是不可變的,即它是個常量; 如果final修飾的是一個對象,就表示這個變量被賦予的引用是不可變的 這裡需要提醒大家注意的是,不可改變的隻是這個變量所儲存的引用,并不是這個引用所指向的對象。 第二種情況:final的含義與第一種情況相同。 實際上對于前兩種情況,一種更貼切的表述final的含義的描述,那就是,如果一個變量或方法參數被final修飾,就表示它隻能被指派一次,但是JAVA虛拟機為變量設定的預設值不記作一次指派。被final修飾的變量必須被初始化。初始化的方式以下幾種: 1.在定義的時候初始化。 2.final變量可以在初始化塊中初始化,不可以在靜态初始化塊中初始化。 3.靜态final變量可以在定義時初始化,也可以在靜态初始化塊中初始化,不可以在初始化塊中初始化。 4.final變量還可以在類的構造器中初始化,但是靜态final變量不可以。 通過下面的代碼可以驗證以上的觀點: 

複制代碼 public class FinalTest{   public final int A=10; //在定義時初始化   public final int B;{B=20;} //在初始化塊中初始化 

//非靜态final變量不能在靜态初始化塊中初始化   //public final int C;static{//C=30; } 

//靜态常量,在定義時初始化   public static final int STATIC_D=40; 

//靜态常量,在靜态初始化塊中初始化   public static final int STATIC_E;static{STATIC_E = 50;} 

//靜态變量不能在初始化塊中初始化   //public static final int STATIC_F;{STATIC_F=60;} 

public final int G; 

//靜态final變量不可以在構造器中初始化   //public static final int STATIC_H; 

//在構造器中初始化   public finalTest(){     G=70;     //靜态final變量不可以在構造器中初始化     //STATIC_H=80; 

//給final的變量第二次指派時,編譯會報錯     //A=99;     //STATIC_D=99;   } 

//final變量未被初始化,編譯時就會報錯   //public final int L; 

//靜态final變量未被初始化,編譯時就會報錯   //public static final int STATIC_J; } 複制代碼 我們運作上面的代碼之後出了可以發現final變量(常量和靜态final變量(靜态常量被初始化時,編譯會報錯。 用final修飾的變量(常量比非final的變量(普通變量擁更高的效率,是以我們在際程式設計中應該盡可能多的用常量來代替普通變量。 定義方法 當final用來定義一個方法時,它表示這個方法不可以被子類重寫,但是并不影響它被子類繼承。我們寫段代碼來驗證一下: 

複制代碼 public class ParentClass{ public final void TestFinal(){ System.out.println(“父類–這是一個final方法”); } } public class SubClass extends ParentClass{ //子類無法重寫(override父類的final方法,否則編譯時會報錯 /* public void TestFinal(){ System.out.println(“子類–重寫final方法”); } */ public static void main(String[]args){ SubClass sc = new SubClass(); sc.TestFinal(); } } 複制代碼 這裡需要特殊說明的是,具有private通路權限的方法也可以增加final修飾,但是由于子類無法繼承private方法,是以也無法重寫它。編譯器在處理private方法時,是照final方來對待的,這樣可以提高該方法被調用時的效率。不過子類仍然可以定義同父類中private方法具同樣結構的方法,但是這并不會産生重寫的效果,而且它們之間也不存在必然聯系。 定義類 最後我們再來回顧一下final用于類的情況。這個大家應該也很熟悉了,因為我們最常用的String類就是final的。由于final類不允許被繼承,編譯器在處理時把它的所方法都當作final的,是以final類比普通類擁更高的效率。而由關鍵字abstract定義的抽象類含必須由繼承自它的子類重載實作的抽象方法,是以無法同時用final和abstract來修飾同一個類。同樣的道理, final也不能用來修飾接口。 final的類的所方法都不能被重寫,但這并不表示final的類的屬性(變量值也是不可改變的,要想做到final類的屬性值不可改變,必須給它增加final修飾,請看下面的例子: 

複制代碼 public final class FinalTest{ int i =20; final int j=50; public static void main(String[] args){ FinalTest ft = new FinalTest(); ft.i = 99;/final類FinalTest的屬性值 i是可以改變的,因為屬性值i前面沒final修飾/ //ft.j=49;//報錯…因為j屬性是final的不可以改變。 System.out.println(ft.i); } } 複制代碼 運作上面的代碼試試看,結果是99,而不是初始化時的10。 finally語句 接下來我們一起回顧一下finally的用法。finally隻能用在try/catch語句中并且附帶着一個語句塊,表示這段語句最終總是被執行。請看下面的代碼: 

複制代碼 public final class FinallyTest{ public static void main(String[] args){ try{ throw new NullPointerException(); }catch(NullPointerException e){ System.out.println(“程式抛出了異常”); }finally{ //這裡總會被執行,不受break,return影響另如資料庫連接配接的close()一般寫在這裡,可以降低程式的出錯幾率 System.out.println(“執行了finally語句塊”); } } } 複制代碼 運作結果說明了finally的作用: 

1.程式抛出了異常 

2.執行了finally語句塊請大家注意,捕獲程式抛出的異常之後,既不加處理,也不繼續向上抛出異常,并不是良好的程式設計習慣,它掩蓋了程式執行中發生的錯誤,這裡隻是友善示範,請不要學習。 那麼,沒一種情況使finally語句塊得不到執行呢? return、continue、break這個可以打亂代碼順序執行語句的規律。那我們就來試試看,這個語句是否能影響finally語句塊的執行: 

複制代碼 public final class FinallyTest { //測試return語句 //結果顯示:編譯器在編譯return new ReturnClass();時, //将它分成了兩個步驟,new ReturnClass()和return,前一個建立對象的語句是在finally語句塊之前被執行的, //而後一個return語句是在finally語句塊之後執行的,也就是說finally語句塊是在程式退出方法之前被執行的 public ReturnClass testReturn() { try { return new ReturnClass(); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(“執行了finally語句”); } return null; } 

//測試continue語句

public void testContinue(){

    for(int i=0; i<3; i++){

        try {

            System.out.println(i);

            if(i == 1){

                System.out.println("con");

            }

        } catch(Exception e) {

            e.printStackTrace();

        } finally {

            System.out.println("執行了finally語句");

        }

    }

}

//測試break語句

public void testBreak() {

    for (int i=0; i<3; i++) {

        try {

            System.out.println(i);

            if (i == 1) {

                break;

            }

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            System.out.println("執行了finally語句");

        }

    }

}

public static void main(String[] args) {

    FinallyTest ft = new FinallyTest();

    // 測試return語句

    ft.testReturn();

    System.out.println();

    // 測試continue語句

    ft.testContinue();

    System.out.println();

    // 測試break語句

    ft.testBreak();

}

class ReturnClass { public ReturnClass() { System.out.println(“執行了return語句”); } } 複制代碼 上面這段代碼的運作結果如下: 複制代碼 執行了return語句 執行了finally語句 

0 執行了finally語句 1 con 執行了finally語句 2 執行了finally語句 

0 執行了finally語句 1 執行了finally語句 複制代碼 很明顯,return、continue和break都沒能阻止finally語句塊的執行。從輸出的結果來看,return語句似乎在finally語句塊之前執行了,事實真的如此嗎?我們來想想看,return語句的作用是什麼呢?是退出目前的方法,并将值或對象傳回。如果 finally語句塊是在return語句之後執行的,那麼return語句被執行後就已經退出目前方法了,finally語句塊又如何能被執行呢?是以,正确的執行順序應該是這樣的:編譯器在編譯return new ReturnClass();時,将它分成了兩個步驟,new ReturnClass()和return,前一個建立對象的語句是在finally語句塊之前被執行的,而後一個return語句是在finally語句塊之後執行的,也就是說finally語句塊是在程式退出方法之前被執行的。同樣,finally語句塊是在循環被跳過(continue和中斷(break之前被執行的 finalize方法 最後,我們再來看看finalize,它是一個方法,屬于java.lang.Object類,它的定義如下:protected void finalize()throws Throwable{}衆所周知,finalize()方法是GC(garbagecollector運作機制的一部分,在此我們隻說說finalize()方法的作用是什麼呢?finalize()方法是在GC清理它所從屬的對象時被調用的,如果執行它的過程中抛出了無法捕獲的異常(uncaughtexception,GC将終止對改對象的清理,并且該異常會被忽略;直到下一次GC開始清理這個對象時,它的finalize()會被再次調用。請看下面的示例: 

複制代碼 public final class FinallyTest{ //重寫finalize()方法 protected void finalize() throws Throwable{ System.out.println(“執行了finalize()方法”); } public static void main(String[] args){ FinallyTest ft = new FinallyTest(); ft = null; System.gc(); } } 複制代碼 運作結果如下:• 執行了finalize()方法 程式調用了java.lang.System類的gc()方法,引起GC的執行,GC在清理ft對象時調用了它的finalize()方法,是以才了上面的輸出結果。調用System.gc()等同于調用下面這行代碼:Runtime.getRuntime().gc();調用它們的作用隻是建議垃圾收集器(GC啟動,清理無用的對象釋放記憶體空間,但是GC的啟動并不是一定的,這由JAVA虛拟機來決定。直到 JAVA虛拟機停止運作,些對象的finalize()可能都沒被運作過,那麼怎樣保證所對象的這個方法在JAVA虛拟機停止運作之前一定被調用呢?答案是我們可以調用System類的另一個方法: 

public static void FunFinalizersOnExit(boolean value){ //othercode } 給這個方法傳入true就可以保證對象的finalize()方法在JAVA虛拟機停止運作前一定被運作了,不過遺憾的是這個方法是不安全的,它會導緻有用的對象finalize()被誤調用,是以已不被贊成使用了。由于finalize()屬于Object類,是以所類都這個方法,Object的任意子類都可以重寫(override該方法,在其中釋放系統資源或者做其它的清理工作,如關閉輸入輸出流。通過以上知識的回顧,我想大家對于final、finally、finalize的用法差別已經很清楚了。 

每日積累一點,都是進步的見證。