重學Java 面向對象 之 final

final 的套路:java

當初在背面試題的時候final出現的機率能夠說是至關高了,在各類面試題庫中都少不了它的身影,一提及final ,那打開方式差很少就是這樣的:面試

1.  對於基本類型變量:final 修飾的變量不可修改json

2.  對於引用型變量: final 修飾的對象,引用自己不可修改,可是被引用的內容能夠修改。數組

3. 對於 方法 : 方法不能重寫app

4. 對於類:類不能被繼承測試

 

由於當時看了太多遍同時內容簡單又好背,如今不看書也能寫出來了,至於具體的代碼示例這裏就不放了,網上也比較多。ui

可是本身歷來沒有想過爲何。this

爲何不讓類被繼承?爲何不讓人家重寫方法?spa

 

反向思考設計

關於這個問題我以爲反向來思考是最有效的,那就是若是我繼承了又怎麼樣,我重寫了又會如何。

最後發現若是繼承或重寫了咱們可能就有很大的麻煩,仍是不要繼承,也不要重寫了吧,而後咱們就在類和方法上加上了final。

 

而關於final與不可變性,能夠從變量,方法,類三個角度來思考。

 

變量與final

 你們初學時都會碰到的Math類,而Math類中有着許多的static變量,是能夠共享的,如耳熟能詳的PI,同時它也被設計成了final 的。

 

 

 假設咱們如今正在開發一個計算各類圖形參數的程序,你負責開發Circle(sphere)的部分,另一我的(就叫背鍋俠吧,名字揭示了命運。。。)負責開發sphere(球體),很明顯,大家的各類計算過程當中都會涉及到PI這個變量。

假如PI沒有加final

有一天你以爲這個PI怎麼看起來這麼長啊,真是不爽,你的運算結果只須要保留兩位小數就好了,但我卻須要拿一個這麼多小數位的變量,因而你決定在本身的類初始化的時候修改這個變量的值,讓它只有兩位小數。而你不知道的是,負責開發球體的那個夥計他的計算結果要保留到小數點後4位。

悲劇到這裏已經很明顯了:大家本身測試的時候都沒什麼問題,而用戶在使用程序計算完一個圓的各類參數後,再計算其餘涉及到PI變量的程序都沒辦法獲得想要的結果,好比計算球體的體積。

而用戶是不會知道這一切的,他們只會反饋給開發團隊一個錯誤信息:我使用計算球體的程序很不穩定,常常得不到我想要的結果。

因而技術經理把背鍋俠喊了過來大罵了一頓,並讓他馬上修復好bug。

背鍋俠同志很鬱悶,本身測試的時候什麼問題都沒有,爲何一上線就有bug

但是他怎麼調試結果都出不來,一切都很正常,背鍋俠很生氣也很煩惱,由於他實在不知道問題出在哪兒。

已經晚上9點了,背鍋俠尚未找到bug所在,背鍋俠氣的有點想砸鍵盤,由於他尚未吃晚飯,有點餓,但又沒有心情吃飯。

背鍋俠以爲本身使用的外部變量只有一個PI,問題確定出在這兒,但是調試的時候一切都正常啊。

因而背鍋俠啓用了他的終極方案,他按下了Ctrl + Shift + F 對PI進行了全局搜索。。。。。

我相信當他最後知道是誰改了這個變量的值,他砸的必定不是鍵盤。

 

小結:通常來講,聲明爲static的變量或者class都會加上final,由於這是你們都要用的公共變量,也是你們達成了一致共識的這個變量就應該是這個值,或者說這個方法就應該這麼寫,要是被隨意的更改,後果不堪設想,並且還可能找錯罵人的對象。。。

 

 

方法與final

 改變方法的主要方式就是重寫,那想想我們平時爲何會想要重寫一個方法呢?固然是以爲父類寫的很差啦(或者說不太合適)

好比說Object的 toString 方法每每不符合咱們對於打印的要求,那咱們有須要的時候就會去重寫這個 toString方法。

重寫的意義在於我返回的是跟你同樣的類型,只是我實現的方式不一樣,而你如今連實現的方式都給我定死了,這也太霸道了。

那麼什麼狀況下咱們不該該重寫某個方法呢?

我認爲可能有兩種狀況(固然不會只有這兩種狀況,我的技術比較菜,暫時只能想到這兩種):

  1. 個人方法裏面涉及到了對靜態變量的修改,那固然就由不得你胡亂修改了,不然就會出現上面的悲劇

  2. 個人方法不只返回類型是固定的,連返回值也是固定的,這個時候我就不但願你來修改它了,好比我有一個修改人物信息的方法,其中有一個年齡字段我但願返回值永遠都是18,一旦我能讓你修改了,你把我改爲80怎麼辦。

這個例子可能不太貼切,咱們再想一個例子:

好比說我們在打遊戲的時候,人物掛掉了,會到出生點,這個時候咱們須要設置人物處於滿狀態(滿血,滿藍啥的),那麼這個時候咱們會有一個set 人物屬性的方法,若是這個方法讓你重寫了,這遊戲還能玩嗎?

而後我在JDK中也找到了一個例子,它就是Calendar的clear方法(Calendar類自己不是final修飾的,即它是能夠繼承的):

 

 這裏的變量是啥意思不用關心,你須要知道的是爲了實現clear這個方法的功能,我這裏的stamp 和 fields 都須要賦一個具體的值0,若是這個方法敞開了讓你改,那麼這個方法的意義就沒有了,你可能把它改爲任何東西。

 

對於上面2點,我以爲是有一個共性的,那就是個人方法必須得給用戶使用(通常是public的),可是我又不想你來修改我方法的返回結果(是的,不是返回類型,連結果都握在手中不放),這個時候咱們就能夠考慮用final來修飾一個方法。

 

類 與 final

在類上使用final,那就更絕了,我壓根就不讓你繼承,你全部的方法都得按照個人來,想改變量,想重寫方法?門兒都沒有

在我所知道的類裏面,這麼變態的類很少,目前看到了兩個,一個Math,一個String

那麼一般來講一個類爲何要設計成final的呢?

這裏我只能用我淺薄的基礎嘗試理解一下:

Math

1. 從語義角度上講:它其中的方法都是涉及到一些 數學的定值、 數學公式的計算、精度的處理等等,這些東西它的返回值應該是按照一種特定的計算過程返回的,因此它並不想讓你修改它的方法過程。

2. 從語法角度上講:Math類並不容許實例化,因此它的變量和方法都是實例化的,由於上面講到它涉及到的都是一些數學的定值和 數學公式,這些東西在很長時間內都是不變的,徹底能夠共享出去讓你們使用,而沒必要實例化,由於我不須要每個對象都有一個PI變量,PI對於因此人來講都是同樣的,它也不該該被更改。

 

String

關於String的不變性網上的討論十分多,徹底夠再寫一兩篇文章了,因此這裏我只是簡單的小結一下:

首先要了解的一點是String的內部其實是一個字符數組,它的成員變量以下:

 

 

 而後做者在註釋的第一段就爲String的不可變性舉出一顆糖炒栗子:

/**
 * String str = "abc";
 *
 * is equivalent to:
 *
 * char data[] = {'a', 'b', 'c'};
 * String str = new String(data);
 */

這段代碼啥意思呢?

咱們稍微看一下String的源碼能夠發現:

public String(char value[]) {
    this.value = Arrays.copyOf(value, value.length);
}

咱們看到它其實是使用了參數的一個copy,這樣就算你去改變data數組,你也沒法改變str,當你再給str賦值時,實際上就又執行了上面這個過程,str就指向另外一塊內存,而不是修改原來的內存了,

String中還有許多這樣的操做來保證String的不可變性

在理解了這個知識點的前提下咱們再來看下面的內容

1. 字符串常量池: Java中有一個字符串常量池的概念:

String a = "1";
String b = "1";
boolean flag = (a == b);    // true

就是雖然他們是兩個對象,可是指向了同一塊內存,它就是Java中的一個常量池

當咱們須要定義一個新的字符串的時候,咱們就會去這個常量池中找有沒有已經存在的,若是有直接拿過來用,沒有就新建一個

但若是String是可變的, 那你每次都會去直接修改內存的值,那麼常量池的意義就沒有了。

2.  另一個例子就是HashMap了

咱們在HashMap中經常會用String當作key值,這樣比較好理解,同時也很符合經常使用的json的格式。

若是我們的String是可變的會發生什麼,我從知乎上找到一個例子,講的比較清晰:

在java中String類爲何要設計成final?  ,代碼在第一個答案中

按照這個思路我本身也寫了一個:

這是一個name - age 的map

 

    HashMap<StringBuilder,Integer> map = new HashMap<>();
    StringBuilder sb1 = new StringBuilder("李雲龍");
    StringBuilder sb2 = new StringBuilder("趙剛");
    map.put(sb1,35);
    map.put(sb2,30);
    System.out.println(map);
StringBuilder sb3
= sb1; sb3.append("的兒子"); map.put(sb3,6); System.out.println(map);

 

結果以下:

 

 我們李大團長哪兒去了?

 

 這裏咱們分析一下,sb1 和 sb3 指向了同一塊內存,而後sb3修改了內存的值,致使sb1的值也變了,因此sb1中存儲的李雲龍也變成了「李雲龍的兒子」,那我們的李大團長就消失了。

咱們明明是想加一我的進來,卻把李團長搞丟了,這明顯和咱們的指望不符合,然而若是咱們使用具備不可變性的String則能夠達到目的:

 

HashMap<String,Integer> map1 = new HashMap<>();
String laoli = "李雲龍";
String xiaozhao = "趙剛";
map1.put(laoli,35);
map1.put(xiaozhao,30);
 System.out.println(map1);

String son = laoli;
son += "的兒子";
map1.put(son,6);
System.out.println(map1);

結果:

 

 

固然了,若是咱們只是單純的對字符串進行處理,而不是要做爲key值,Stringbuilder或者StringBuffer則是更好的選擇,可是這不在本篇文章的討論範圍以內,不作贅述。

 

至此,關於final的幾個特性我就小結完畢了 ,我的技術有限,若是你們發現什麼錯漏之處,歡迎發在評論區,你們一塊兒討論

相關文章
相關標籤/搜索