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

我:可以的。
面試官:恩,好的。呃,你能不能說一說為什么String要用final修飾?
我:final意味著不能被繼承或者被重寫,String類用final修飾是Java的設(shè)計人員不希望客戶端程序員繼承String類,并有可能改寫String類中的方法。使用String對象的***實(shí)踐,應(yīng)該是關(guān)聯(lián)或者依賴,而不是繼承。
面試官:恩,你還沒有說到點(diǎn)兒上,能再展開談?wù)剢?
我:恩,好的。具體來說,String類被定義為final的主要是從兩個方面來考慮:安全和性能,也就是說,String被設(shè)計成final的,即考慮到了安全性,也兼顧了性能問題。
我們可以看到上面這張圖中,出現(xiàn)了兩個final。一個final是修飾了String類,而另一個final修飾了char數(shù)組。我們知道,String的本質(zhì)實(shí)際上就是這個char數(shù)組,先來說一說 final char[] 的這個 final。
用final修飾char數(shù)組的原因,還需要從我們?nèi)粘5膶?shí)際開發(fā)中說起。
在日常的實(shí)際開發(fā)中,開發(fā)者會用到大量的字符串對象,可以說我們無時無刻不在和字符串打交道。大量的字符串被輕易的創(chuàng)建出來,這就涉及到一個非常嚴(yán)重的問題,即性能的開銷,我們知道分配給Java虛擬機(jī)的內(nèi)存是有限的,如果不加節(jié)制的創(chuàng)建字符串對象,那么弊端顯而易見:內(nèi)存迅速被占滿,程序執(zhí)行緩慢!!!于是Java的設(shè)計者采用了一種非常有效的解決辦法,即:共享字符串。共享字符串對象的方法是將字符串對象存放到虛擬機(jī)中的方法區(qū)里面的常量池里,不同的類,不同的方法,甚至是不同的線程,可以使用同一個字符串對象,而不需要再在內(nèi)存中開辟新的內(nèi)存空間,從而極大的降低了內(nèi)存的消耗,也提升了程序運(yùn)行效率。
因此,字符串共享是解決內(nèi)存消耗以及龐大的性能開銷的必然選擇。但是到這里為止,還不能解釋為什么這個char數(shù)組要用final修飾。用final修飾的原因來自于另一個必須要考慮的問題:安全性。什么是安全性?這里的安全性,指的是線程安全性,這個很好理解,首先,我們已經(jīng)確定了一個大的前提:字符串要共享,否則內(nèi)存將瞬間擠爆、性能將嚴(yán)重下降。
但是共享的問題在于:不同的線程有可能會修改這個共享對象。
比如,thread_1正在循環(huán)一個List,每個元素和 “abc” 進(jìn)行比較,同時thread_2也在使用這個 “abc” 對象,如果thread_2改變了這個共享字符串,結(jié)果會怎樣?很明顯,thread_1 的結(jié)果將不可預(yù)測!!因此,解決共享變量安全性的***的手段,就是禁止修改共享對象,于是字符串對象的這個char數(shù)組就必然要被 final 修飾了,因?yàn)?final 意味著禁止改變。
面試官:恩恩,沒想到你的想法這么透徹!那么,這是char數(shù)組final的作用,那為什么還要給String類本身加一個final呢?
我:恩,這也是另一個Java設(shè)計者需要考慮的問題。既然共享字符數(shù)組已經(jīng)確定是final的、不能改變的了,那為什么要給String也加一個final呢?原因依然是性能和安全性兩個方面。
但是,此時需要考慮的性能和安全性卻和 final char[] 的final 不太一樣了。
首先,如果假設(shè)String可以被繼承,那么方法也可以被重寫,這里面涉及到一個C++中的概念,叫做:虛函數(shù)表。
面試官:哦?你還懂C++?
我: 是的。同樣是面向?qū)ο蟮恼Z言,Java和C++有著共通的地方。首先,虛函數(shù)是指:可以定義一個父類的指針, 其指向一個子類對象, 當(dāng)通過父類的指針去調(diào)用函數(shù)時, 可以在運(yùn)行時決定應(yīng)該調(diào)用父類的函數(shù)還是子類的函數(shù)。虛函數(shù)是實(shí)現(xiàn)多態(tài)的基礎(chǔ)。前面說了,如果String可以被繼承,那么勢必就會有人通過創(chuàng)建String引用并指向String子類對象的方式,來使用子類的方法,比如像這樣寫:
- String aa = new SubString("abcd");
- aa.length();
這看似沒有什么問題,但是問題在于性能,前面提到了,在程序開發(fā)過程中,字符串對象是非常常用的,上述代碼在調(diào)用對象aa.length() 時,虛擬機(jī)就會去虛函數(shù)表中查找并判定究竟是應(yīng)該調(diào)用哪個子類的length()方法。在大量使用字符串對象的場景下,勢必會降低程序運(yùn)行效率。
其次又是安全性,這個安全性的解釋為語義的安全性,面向?qū)ο蟮恼Z言本身就要求要有清晰的語義和明確的表達(dá)。String的各個方法都圍繞著一個char數(shù)組進(jìn)行,所有方法的語義都是最直接、最有效的。重寫String的方法意味著:不一樣的語義或者錯誤的語義。這將直接導(dǎo)致String行為的不確定性,使用String對象的代碼將會是不安全的代碼。因此,Java設(shè)計者才會禁止任何人繼承String類,主要是為了String對象的操作語義不被改變,確保使用String對象的代碼是絕對安全的。
面試官:原來如此,沒想到你的理解這么到位!年薪50萬,明天就來上班吧!
總結(jié)
當(dāng)面試官問道為什么 String 是final的時候,要答出兩方面:***就是final char value[] 的final ;第二就是 final class 的final。
這兩個final都要緊扣安全與性能兩個方面闡述。
1、final char value[] 的final 要抓住幾個關(guān)鍵點(diǎn)是:value[]數(shù)組的final用于限制字符數(shù)組的修改。字符串將會被大量使用,從性能上考慮迫使Java語言的設(shè)計者將 char[] 設(shè)計為共享的,又因?yàn)樽址枪蚕淼脑俅纹仁乖O(shè)計者考慮到線程安全性,這才需要用final來修飾,避免并發(fā)場景下的行為不可預(yù)測。
2、final class 的final 要抓住幾個關(guān)鍵點(diǎn)是:類上的final用于限制產(chǎn)生子類(或限制多態(tài)/或限制行為的變化)。字符串的使用是頻繁的,如果通過多態(tài)的方式使用String子類對象及其方法將會一定程度上導(dǎo)致性能下降(多態(tài)的實(shí)現(xiàn)原理:底層的虛函數(shù)表),同時String中的方法也可能面臨被Override重寫的危險導(dǎo)致程序語義不安全、甚至是邏輯錯誤,與Java自始至終強(qiáng)調(diào)的安全性理念相違背。
標(biāo)題名稱:對話式情景剖析,String被Final修飾的真正原因!一篇足矣
本文鏈接:http://www.5511xx.com/article/cdohheg.html


咨詢
建站咨詢
