當前位置: 首頁> 學習園地

對話式情景剖析,String被final修飾的真正原因!一篇足矣

2019-05-16 09:13:19更新

面試官:你好,能看得清下面這張圖嗎?

我:可以的。

面試官:恩,好的。呃,你能不能說一說為什么String要用final修飾?

我:final意味著不能被繼承或者被重寫,String類用final修飾是Java的設計人員不希望客戶端程序員繼承String類,并有可能改寫String類中的方法。使用String對象的最佳實踐,應該是關聯或者依賴,而不是繼承。

面試官:恩,你還沒有說到點兒上,能再展開談談嗎?

我:恩,好的。具體來說,String類被定義為final的主要是從兩個方面來考慮:安全和性能,也就是說,String被設計成final的,即考慮到了安全性,也兼顧了性能問題。

我們可以看到上面這張圖中,出現了兩個final。一個final是修飾了String類,而另一個final修飾了char數組。我們知道,String的本質實際上就是這個char數組,先來說一說 final char[] 的這個 final。

final修飾char數組的原因,還需要從我們日常的實際開發中說起。

在日常的實際開發中,開發者會用到大量的字符串對象,可以說我們無時無刻不在和字符串打交道。大量的字符串被輕易的創建出來,這就涉及到一個非常嚴重的問題,即性能的開銷,我們知道分配給Java虛擬機的內存是有限的,如果不加節制的創建字符串對象,那么弊端顯而易見:內存迅速被占滿,程序執行緩慢?。?!于是Java的設計者采用了一種非常有效的解決辦法,即:共享字符串。共享字符串對象的方法是將字符串對象存放到虛擬機中的方法區里面的常量池里,不同的類,不同的方法,甚至是不同的線程,可以使用同一個字符串對象,而不需要再在內存中開辟新的內存空間,從而極大的降低了內存的消耗,也提升了程序運行效率。

因此,字符串共享是解決內存消耗以及龐大的性能開銷的必然選擇。但是到這里為止,還不能解釋為什么這個char數組要用final修飾。用final修飾的原因來自于另一個必須要考慮的問題:安全性。什么是安全性?這里的安全性,指的是線程安全性,這個很好理解,首先,我們已經確定了一個大的前提:字符串要共享,否則內存將瞬間擠爆、性能將嚴重下降。

但是共享的問題在于:不同的線程有可能會修改這個共享對象。

比如,thread_1正在循環一個List,每個元素和 “abc” 進行比較,同時thread_2也在使用這個 “abc” 對象,如果thread_2改變了這個共享字符串,結果會怎樣?很明顯,thread_1 的結果將不可預測??!因此,解決共享變量安全性的最好的手段,就是禁止修改共享對象,于是字符串對象的這個char數組就必然要被 final 修飾了,因為 final 意味著禁止改變。

面試官:恩恩,沒想到你的想法這么透徹!那么,這是char數組final的作用,那為什么還要給String類本身加一個final呢?

我:恩,這也是另一個Java設計者需要考慮的問題。既然共享字符數組已經確定是final的、不能改變的了,那為什么要給String也加一個final呢?原因依然是性能安全性兩個方面。

但是,此時需要考慮的性能和安全性卻和 final char[] 的final 不太一樣了。

首先,如果假設String可以被繼承,那么方法也可以被重寫,這里面涉及到一個C++中的概念,叫做:虛函數表。

面試官:哦?你還懂C++?

我: 是的。同樣是面向對象的語言,Java和C++有著共通的地方。首先,虛函數是指:可以定義一個父類的指針, 其指向一個子類對象, 當通過父類的指針去調用函數時, 可以在運行時決定應該調用父類的函數還是子類的函數。虛函數是實現多態的基礎。前面說了,如果String可以被繼承,那么勢必就會有人通過創建String引用并指向String子類對象的方式,來使用子類的方法,比如像這樣寫:

這看似沒有什么問題,但是問題在于性能,前面提到了,在程序開發過程中,字符串對象是非常常用的,上述代碼在調用對象aa.length() 時,虛擬機就會去虛函數表中查找并判定究竟是應該調用哪個子類的length()方法。在大量使用字符串對象的場景下,勢必會降低程序運行效率。

其次又是安全性,這個安全性的解釋為語義的安全性,面向對象的語言本身就要求要有清晰的語義和明確的表達。String的各個方法都圍繞著一個char數組進行,所有方法的語義都是最直接、最有效的。重寫String的方法意味著:不一樣的語義或者錯誤的語義。這將直接導致String行為的不確定性,使用String對象的代碼將會是不安全的代碼。因此,Java設計者才會禁止任何人繼承String類,主要是為了String對象的操作語義不被改變,確保使用String對象的代碼是絕對安全的。

面試官:原來如此,沒想到你的理解這么到位!年薪50萬,明天就來上班吧!

上海时时彩: 總結

上海时时彩 www.rcmdl.com 當面試官問道為什么 String 是final的時候,要答出兩方面:第一就是final char value[] 的final ;第二就是 final class 的final。

這兩個final都要緊扣安全性能兩個方面闡述。

1、final char value[] 的final 要抓住幾個關鍵點是:value[]數組的final用于限制字符數組的修改。字符串將會被大量使用,從性能上考慮迫使Java語言的設計者將 char[] 設計為共享的,又因為字符串是共享的再次迫使設計者考慮到線程安全性,這才需要用final來修飾,避免并發場景下行為不可預測。

2、final class 的final 要抓住幾個關鍵點是:類上的final用于限制產生子類(或限制多態/或限制行為的變化)。字符串的使用是頻繁的,如果通過多態的方式使用String子類對象及其方法將會一定程度上導致性能下降(多態的實現原理:底層的虛函數表),同時String中的方法也可能面臨被Override重寫的危險導致程序語義不安全、甚至是邏輯錯誤,與Java自始至終強調的安全性理念相違背。