亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解final、finally和finalize

發布時間:2021-10-25 16:13:48 來源:億速云 閱讀:147 作者:iii 欄目:編程語言

本篇內容主要講解“如何理解final、finally和finalize”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“如何理解final、finally和finalize”吧!

如何理解final、finally和finalize

1. final、finally 和 finalize

我相信在座的各位都是資深程序員,final 這種基礎關鍵字就不用多說了。不過,還是要照顧一下小白讀者,畢竟我們都是從小白走過來的嘛。

(1) final 修飾類、屬性和方法

final 可以用來修飾類,final 修飾的類不允許其他類繼承,也就是說,final 修飾的類是獨一無二的。如下所示

如何理解final、finally和finalize

我們首先定義了一個 FinalUsage 類,它使用 final 修飾,同時我們又定義了一個 FinalUsageExtend  類,它想要繼承(extend) FinalUsage,我們如上繼承后,編譯器不讓我們這么玩兒,它提示我們 不能從 FinalUsage  類繼承,為什么呢?不用管,這是 Java 的約定,有一些為什么沒有必要,遵守就行。

final 可以用來修飾方法,final 修飾的方法不允許被重寫,我們先演示一下不用 final 關鍵字修飾的情況

如何理解final、finally和finalize

如上圖所示,我們使用 FinalUsageExtend 類繼承了 FinalUsage 類,并提供了 writeArticle  方法的重寫。這樣編譯是沒有問題的,重寫的關鍵點是 @Override 注解和方法修飾符、名稱、返回值的一致性。

注意:很多程序員在重寫方法的時候都會忽略 @Override,這樣其實無疑增加了代碼閱讀的難度,不建議這樣。

當我們使用 final 修飾方法后,這個方法則不能被重寫,如下所示

如何理解final、finally和finalize

當我們把 writeArticle 方法聲明為 void 后,重寫的方法會報錯,無法重寫 writeArticle 方法。

final 可以修飾變量,final 修飾的變量一經定義后就不能被修改,如下所示

如何理解final、finally和finalize

編譯器提示的錯誤正是不能繼承一個被 final 修飾的類。

我們上面使用的是字符串 String ,String 默認就是 final 的,其實用不用 final  修飾意義不大,因為字符串本來就不能被改寫,這并不能說明問題。

我們改寫一下,使用基本數據類型來演示

如何理解final、finally和finalize

同樣的可以看到,編譯器仍然給出了 age 不能被改寫的提示,由此可以證明,final 修飾的變量不能被重寫。

在 Java 中不僅僅只有基本數據類型,還有引用數據類型,那么引用類型被 final 修飾后會如何呢?我們看一下下面的代碼

首先構造一個 Person 類:

public class Person {     int id;     String name;     get() and set() ...     toString()... }

然后我們定義一個 final 的 Person 變量。

static final Person person = new Person(25,"cxuan");  public static void main(String[] args) {    System.out.println(person);   person.setId(26);   person.setName("cxuan001");   System.out.println(person); }

輸出一下,你會發現一個奇怪的現象,為什么我們明明改了 person 中的 id 和 name ,編譯器卻沒有報錯呢?

這是因為,final 修飾的引用類型,只是保證對象的引用不會改變。對象內部的數據可以改變。這就涉及到對象在內存中的分配問題,我們后面再說。

(2) finally 保證程序一定被執行

finally 是保證程序一定執行的機制,同樣的它也是 Java 中的一個關鍵字,一般來講,finally 一般不會單獨使用,它一般和 try  塊一起使用,例如下面是一段 try...finally 代碼塊:

try{   lock.lock(); }finally {   lock.unlock(); }

這是一段加鎖/解鎖的代碼示例,在 lock 加鎖后,在 finally 中執行解鎖操作,因為 finally  能夠保證代碼一定被執行,所以一般都會把一些比較重要的代碼放在 finally 中,例如解鎖操作、流關閉操作、連接釋放操作等。

當 lock.lock() 產生異常時還可以和 try...catch...finally 一起使用:

try{   lock.lock(); }catch(Exception e){   e.printStackTrace(); }finally {   lock.unlock(); }

try...finally 這種寫法適用于 JDK1.7 之前,在 JDK1.7 中引入了一種新的關閉流的操作,那就是  try...with...resources,Java 引入了 try-with-resources 聲明,將 try-catch-finally 簡化為  try-catch,這其實是一種語法糖,并不是多了一種語法。try...with...resources 在編譯時還是會進行轉化為  try-catch-finally 語句。

語法糖(Syntactic  sugar),也譯為糖衣語法,是指計算機語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用。通常來說使用語法糖能夠增加程序的可讀性,從而減少程序代碼出錯的機會。

在 Java 中,有一些為了簡化程序員使用的語法糖,后面有機會我們再談。

(3) finalize 的作用

finalize 是祖宗類 Object類的一個方法,它的設計目的是保證對象在垃圾收集前完成特定資源的回收。finalize 現在已經不再推薦使用,在  JDK 1.9 中已經明確的被標記為 deprecated。

2. 深入理解 final 、finally 和 finalize

(1) final 設計

許多編程語言都會有某種方法來告知編譯器,某一塊數據是恒定不變的。有時候恒定不變的數據很有用,比如

  • 一個永不改變的編譯期常量 。例如 static final int num = 1024

  • 一個運行時被初始化的值,而且你不希望改變它

final 的設計會和 abstract 的設計產生沖突,因為 abstract 關鍵字主要修飾抽象類,而抽象類需要被具體的類所實現。final  表示禁止繼承,也就不會存在被實現的問題。因為只有繼承后,子類才可以實現父類的方法。

類中的所有 private 都隱式的指定為 final 的,在 private 修飾的代碼中使用 final 并沒有額外的意義。

(2) 空白 final

Java 是允許空白 final 的,空白 final 指的是聲明為 final ,但是卻沒有對其賦值使其初始化。但是無論如何,編譯器都需要初始化  final,所以這個初始化的任務就交給了構造器來完成,空白 final 給 final 提供了更大的靈活性。如下代碼:

public class FinalTest {     final Integer finalNum;        public FinalTest(){        finalNum = 11;    }        public FinalTest(int num){        finalNum = num;    }      public static void main(String[] args) {         new FinalTest();         new FinalTest(25);     } }

在不同的構造器中對不同的 final 進行初始化,使 finalNum 的使用更加靈活。

使用 final 的方法主要有兩個:不可變 和 效率

  • 不可變:不可變說的是把方法鎖定(注意不是加鎖),重在防止其他方法重寫。

  • 效率:這個主要是針對 Java 早期版本來說的,在 Java 早期實現中,如果將方法聲明為 final  的,就是同意編譯器將對此方法的調用改為內嵌調用,但是卻沒有帶來顯著的性能優化。這種調用就比較雞肋,在 Java5/6 中,hotspot  虛擬機會自動探測到內嵌調用,并把它們優化掉,所以使用 final 修飾的方法就主要有一個:不可變。

注意:final 不是 Immutable 的,Immutable 才是真正的不可變。

final 不是真正的 Immutable,因為 final  關鍵字引用的對象是可以改變的。如果我們真的希望對象不可變,通常需要相應的類支持不可變行為,比如下面這段代碼:

final List<String> fList = new ArrayList(); fList.add("Hello"); fList.add("World"); List unmodfiableList = List.of("hello","world"); unmodfiableList.add("again");

List.of 方法創建的就是不可變的 List。不可變 Immutable 在很多情況下是很好的選擇,一般來說,實現 Immutable  需要注意如下幾點

  • 將類聲明為 final,防止其他類進行擴展。

  • 將類內部的成員變量(包括實例變量和類變量)聲明為 private 或 final 的,不要提供可以修改成員變量的方法,也就是 setter 方法。

  • 在構造對象時,通常使用 deep-clone ,這樣有助于防止在直接對對象賦值時,其他人對輸入對象的修改。

  • 堅持 copy-on-write 原則,創建私有的拷貝。

(3) final 能提高性能嗎?

final 能否提高性能一直是業界爭論的點,很多書籍中都介紹了可以在特定場景提高性能,例如 final 可能用于幫助 JVM  將方法進行內聯,可以改造編譯器進行編譯的能力等等,但這些結論很多都是基于假設作出的。

大致說的就是無論局部變量聲明時帶不帶 final 關鍵字修飾,對其訪問的效率都一樣。

比如下面這段代碼(不帶 final 的版本):

static int foo() {   int a = someValueA();   int b = someValueB();   return a + b; // 這里訪問局部變量 }

帶 final 的版本:

static int foo() {   final int a = someValueA();   final int b = someValueB();   return a + b; // 這里訪問局部變量 }

使用 javac 編譯后得出來的結果一摸一樣。

invokestatic someValueA:()I istore_0 // 設置a的值 invokestatic someValueB:()I istore_1 // 設置b的值 iload_0  // 讀取a的值 iload_1  // 讀取b的值 iadd ireturn

因為上面是使用引用類型,所以字節碼相同。

如果是常量類型,我們看一下:

// 帶 final static int foo(){    final int a = 11;   final int b = 12;    return a + b;  }  // 不帶 final static int foo(){    int a = 11;   int b = 12;    return a + b;  }

我們分別編譯一下兩個 foo 方法,會發現如下字節碼:

如何理解final、finally和finalize

左邊是非 final 關鍵字修飾的代碼,右邊是有 final 關鍵字修飾的代碼,對比這兩個字節碼,可以得出如下結論。

  • 不管有沒有 final 修飾 ,int a = 11 或者 int a = 12 都當作常量看待。

  • 在 return 返回處,不加 final 的 a + b 會當作變量來處理;加 final 修飾的 a + b 會直接當作常量處理。

其實這種層面上的差異只對比較簡易的 JVM 影響較大,因為這樣的 VM 對解釋器的依賴較大,原本 Class  文件里的字節碼是怎樣的它就怎么執行;對高性能的 JVM(例如 HotSpot、J9 等)則沒啥影響。

所以,大部分 final 對性能優化的影響,可以直接忽略,我們使用 final 更多的考量在于其不可變性。

(4) 深入理解 finally

我們上面大致聊到了 finally 的使用,其作用就是保證在 try 塊中的代碼執行完成之后,必然會執行 finally 中的語句。不管 try  塊中是否拋出異常。

那么下面我們就來深入認識一下 finally ,以及 finally 的字節碼是什么,以及 finally 究竟何時執行的本質。

首先我們知道 finally 塊只會在 try 塊執行的情況下才執行,finally 不會單獨存在。

這個不用再過多解釋,這是大家都知道的一條規則。finally 必須和 try 塊或者 try catch 塊一起使用。

其次,finally 塊在離開 try 塊執行完成后或者 try  塊未執行完成但是接下來是控制轉移語句時(return/continue/break)在控制轉移語句之前執行

這一條其實是說明 finally 的執行時機的,我們以 return 為例來看一下是不是這么回事。

如下這段代碼:

static int mayThrowException(){   try{     return 1;   }finally {     System.out.println("finally");   } }  public static void main(String[] args) {   System.out.println(FinallyTest.mayThrowException()); }

從執行結果可以證明是 finally 要先于 return 執行的。

當 finally 有返回值時,會直接返回。不會再去返回 try 或者 catch 中的返回值。

static int mayThrowException(){   try{     return 1;   }finally {     return 2;   } }  public static void main(String[] args) {   System.out.println(FinallyTest.mayThrowException()); }

在執行 finally 語句之前,控制轉移語句會將返回值存在本地變量中

看下面這段代碼:

static int mayThrowException(){   int i = 100;   try {     return i;   }finally {     ++i;   } }  public static void main(String[] args) {   System.out.println(FinallyTest.mayThrowException()); }

上面這段代碼能夠說明 return i 是先于 ++i 執行的,而且 return i 會把 i 的值暫存,和 finally 一起返回。

(5) finally 的本質

下面我們來看一段代碼:

public static void main(String[] args) {    int a1 = 0;   try {     a1 = 1;   }catch (Exception e){     a1 = 2;   }finally {     a1 = 3;   }    System.out.println(a1); }

這段代碼輸出的結果是什么呢?答案是 3,為啥呢?

抱著疑問,我們先來看一下這段代碼的字節碼

如何理解final、finally和finalize

字節碼的中文注釋我已經給你標出來了,這里需要注意一下下面的 Exception table,Exception table  是異常表,異常表中每一個條目代表一個異常發生器,異常發生器由 From 指針,To 指針,Target 指針和應該捕獲的異常類型構成。

所以上面這段代碼的執行路徑有三種

  • 如果 try 語句塊中出現了屬于 exception 及其子類的異常,則跳轉到 catch 處理

  • 如果 try 語句塊中出現了不屬于 exception 及其子類的異常,則跳轉到 finally 處理

  • 如果 catch 語句塊中新出現了異常,則跳轉到 finally 處理

聊到這里,我們還沒說 finally 的本質到底是什么,仔細觀察一下上面的字節碼,你會發現其實 finally 會把 a1 = 3 的字節碼  iconst_3 和 istore_1 放在 try 塊和 catch 塊的后面,所以上面這段代碼就形同于:

public static void main(String[] args) {    int a1 = 0;   try {     a1 = 1;   // finally a1 = 3   }catch (Exception e){     a1 = 2;     // finally a1 = 3   }finally {     a1 = 3;   }   System.out.println(a1); }

上面中的 Exception table 是只有 Throwable 的子類 exception 和 error 才會執行異常走查的異常表,正常情況下沒有  try 塊是沒有異常表的,下面來驗證一下:

public static void main(String[] args) {   int a1 = 1;   System.out.println(a1); }

比如上面我們使用了一段非常簡單的程序來驗證,編譯后我們來看一下它的字節碼

如何理解final、finally和finalize

可以看到,果然沒有異常表的存在。

(6) finally 一定會執行嗎

上面我們討論的都是 finally 一定會執行的情況,那么 finally 一定會被執行嗎?恐怕不是。

除了機房斷電、機房爆炸、機房進水、機房被雷劈、強制關機、拔電源之外,還有幾種情況能夠使 finally 不會執行。

  • 調用 System.exit 方法

  • 調用 Runtime.getRuntime().halt(exitStatus) 方法

  • JVM 宕機(搞笑臉)

  • 如果 JVM 在 try 或 catch 塊中達到了無限循環(或其他不間斷,不終止的語句)

  • 操作系統是否強行終止了 JVM 進程;例如,在 UNIX 上執行 kill -9 pid

  • 如果主機系統死機;例如電源故障,硬件錯誤,操作系統死機等不會執行

  • 如果 finally 塊由守護程序線程執行,那么所有非守護線程在 finally 調用之前退出。

(7) finalize 真的沒用嗎

我們上面簡單介紹了一下 finalize 方法,并說明了它是一種不好的實踐。那么 finalize 調用的時機是什么?為什么說 finalize  沒用呢?

我們知道,Java 與 C++ 一個顯著的區別在于 Java 能夠自動管理內存,在 Java 中,由于 GC 的自動回收機制,因而并不能保證  finalize 方法會被及時地執行(垃圾對象的回收時機具有不確定性),也不能保證它們會被執行。

也就是說,finalize 的執行時期不確定,我們并不能依賴于 finalize 方法幫我們進行垃圾回收,可能出現的情況是在我們耗盡資源之前,gc  卻仍未觸發,所以推薦使用資源用完即顯示釋放的方式,比如 close 方法。除此之外,finalize 方法也會生吞異常。

finalize 的工作方式是這樣的:一旦垃圾回收器準備好釋放對象占用的存儲空間,將會首先調用 finalize  方法,并且在下一次垃圾回收動作發生時,才會真正回收對象占用的內存。垃圾回收只與內存有關。

我們在日常開發中并不提倡使用 finalize 方法,能用 finalize 方法的地方,使用 try...finally 會處理的更好。

到此,相信大家對“如何理解final、finally和finalize”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

普安县| 广东省| 工布江达县| 正镶白旗| 浦东新区| 法库县| 大化| 瑞丽市| 方城县| 曲沃县| 环江| 怀远县| 巴里| 赫章县| 穆棱市| 巫溪县| 陵水| 顺义区| 周宁县| 墨玉县| 鄂伦春自治旗| 罗江县| 巫山县| 布尔津县| 高清| 行唐县| 梁河县| 邛崃市| 林州市| 杭州市| 阿巴嘎旗| 尼勒克县| 英超| 富民县| 井研县| 曲水县| 孝感市| 辽阳县| 福贡县| 辽宁省| 黄石市|