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

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


咨詢(xún)
建站咨詢(xún)
