天天看點

深刻了解Java中final的作用(一):從final的作用剖析String被設計成不可變類的深層原因

  聲明:本部落格為原創部落格,未經允許,不得轉載!小夥伴們如果是在别的地方看到的話,建議還是來csdn上看吧(原文連結為),看代碼和提問、讨論都更友善。

       Java中final的作用主要表現在三方面:修飾變量、修飾方法和修飾類。下面就從這兩個方面來講解final的作用。在文末從final及類的設計安全性出發,論述了Java中String為何要被設計成不可變類。

1.final修飾變量

      final修飾變量時與C++中的const作用類似,即如果是基本類型變量,則表示其值不能改變;如果是引用類型變量,則表示其一旦初始化,就不能再指向别的對象,但是注意它指向的對象本身的值可以改變(其實這一點也跟C++中的常指針很像)。看下面一個例子即知。

輸出結果如下圖所示:

深刻了解Java中final的作用(一):從final的作用剖析String被設計成不可變類的深層原因

顯然,final修飾的基本類型變量a是不能被改變;由輸出的哈希碼不變可知apple始終指向同一個對象,但是它指向的這個對象的成員weight卻發生了改變。

       也正是由于final對于引用類型變量“隻能保證指向不變,卻不能保證指向的對象本身不變”,是以在構造類時要特别小心,否則很容易被黑客利用。看下面一個例子即知。

深刻了解Java中final的作用(一):從final的作用剖析String被設計成不可變類的深層原因

類的設計者本意是想使appleTag一旦被初始化即不被修改,并且刻意不提供set函數以防止其被篡改。但實際上類的使用者卻可以從兩個地方修改appleTag進而達到改變apple的目的,其中一個地方是利用構造器中的實參進行修改,另一個就是利用getAppleTag()函數的傳回值對其進行修改。

      顯然,本例是由于類的設計不當而釋放出過大的權限,使類的使用者将apple的重量和大小修改成了極不合理的值,進而得到錯誤的結果。那要如何避免這種錯誤呢?

     很簡單,就是讓final變量與外界充分隔離開,如本例中使類的使用者能擷取相應的值,但是無法擷取appleTag,代碼如下所示:

程式輸出結果如下:

深刻了解Java中final的作用(一):從final的作用剖析String被設計成不可變類的深層原因

顯然,盡管此處類的使用者仍然嘗試越權去修改appleTag,卻沒有獲得成功,原因就在于:在接收時,在類的内部建立了一個對象并讓appleTag指向它;在輸出時,使用者擷取的是建立的對象,保證了使用者即可以獲得相應的值,又無法利用擷取的對象來修改appleTag。是以apple始終未被改變,三次輸出結果相同。

       在我的部落格《》一文中已經說過String的對象是不可變的,因而在使用String時即使是直接傳回引用也不用擔心使用者篡改對象的問題,如下例:

輸出結果如下所示:

深刻了解Java中final的作用(一):從final的作用剖析String被設計成不可變類的深層原因

顯然,與前兩個例子類似,類的使用者也嘗試用相似的方法去修改type進而達到修改apple的目的,但是輸出結果表明這種嘗試沒有成功。原因就在于String的對象不可變,在外面的修改實際上是讓appleType及type分别指向了不同的對象,而用于初始化的String對象始終沒有改變,當然apple中的type也就不改變了。

再回過頭來,我們發現曾經讓我們很不爽的String(在一文中講到過,由于String對象不可變進而導緻即使傳引用也無法使對象的值改變),其實是經過Java設計者精心設計的,試想一下,如果String對象可變的話,在我們平常的程式編寫中将會帶來極大的安全隐患,而如果想杜絕這種隐患,則每次在使用String時都要經過精密考慮,程式也會變得更複雜,而偏偏String是被大量使用的(實際上不管哪種程式設計語言,字元串及其處理都占有相當大的比重),顯然會給我們日常的程式編寫帶來極大的不便。

     另外一個值得注意的地方就是數組變量名其實也是一個引用,是以final修飾數組時,數組成員依然可以被改變。