背景
最近在項目里經呈桥浚看到有的小伙伴喜歡在參數里面加上final關鍵字,平常也沒怎么注意澄惊,偶然幾天有空仔細看了一下唆途,覺得十分有意思。所以記錄下來掸驱,一方面是給自己多一些加深回憶的素材肛搬,另一方面也是希望能給與我有相同疑惑的同學有個參考。當然這個本身可能是比較基礎的問題毕贼,但是多換個角度看也蠻有意思的
final關鍵字在Java中的應用場景其實也還是蠻多的温赔,其中最出名的地方,應該就是繼承的時候不允許繼承吧鬼癣。然后還有很多的場景就是final static來定義一些常量吧陶贼。當然,還有一個比較有意思的場景待秃,就是今天我們的主角拜秧,在傳參時使用final關鍵字。
比如這樣:
public void doThings(final Object inputParam)
這里不由得聯想到了當年剛學C++中章郁,一個很有意思又很容易混淆的概念枉氮,參數時按址傳遞還是按值傳遞的呢?當然在Java中因為取消了 * 和 & 這些容易混淆的關鍵字暖庄,但是如果在傳遞參數時不注意的話聊替,還是很容易造成一些不容易發(fā)現的錯誤的。那么就想從使用final關鍵詞上來在看看Java有趣的傳參機制雄驹。
為什么在傳參中加上final
首先佃牛,我覺得還是給出一個大概的結論吧:
int、char這些基礎類型來說医舆,可以認為就是按值傳遞的(在函數中對其的修改不會影響到參數本身來源的值得改變)俘侠。當然String這個類型會比較特殊,在于雖然本身是一個基礎類型蔬将,但是它同時又是一個對象爷速,有一些列Object中繼承的方法。(這里和String在內存中具體處理有關系霞怀,因為我們實際操作的是String其實是一個對象惫东,而真實的字串值是存在于靜態(tài)區(qū),并非是String本身毙石,這一點和裝箱類型的Integer之類的有點相似廉沮。具體有興趣的同學可以參考一下Java中內存結構,如內存模型的一個圖
謎之音:湊不要臉的徐矩! 我:是啊滞时,我就是,怎么了(捂臉))對于對象來說滤灯,按照按值傳遞來處理坪稽,基本上也是沒有問題的,而且能夠簡化問題本身鳞骤。
但是(終于等到這個詞了窒百,據說是中國人最討厭的兩個字之一(捂臉)),在Java中其實也是可以直接改變參數對象的值的豫尽,只要你加上final關鍵字之后編譯器不報錯的話篙梢,而且這很好的彌補了有時我們返回值只有一個時的尷尬。
那么美旧,這是為啥呢庭猩,我們就來看看final關鍵字吧~其實就final本身來說,還是比較好理解的陈症,簡單的可以認為final就代表著不能改變的意思蔼水。可以發(fā)現录肯,不管是final在修飾變量趴腋,或者修飾方法,或者修飾類的時候论咏,其實都最最終意味著不可改變的意思优炬。
乍一聽,感覺有點玄乎厅贪,不可改變?yōu)樯缎揎椓藚抵蠓炊沟脜抵当旧砜梢愿淖兞四卮阑ぃ科鋵嵾@里和Java本身的機制有關,因為傳遞的是參數并非參數本身养涮,而是參數本身的一個引用的復制葵硕。這也就是很多文章將這里的傳遞和賦值號聯系起來的原因眉抬。具體可以參考這些文章,寫的挺好的:
所以參考下圖蜀变,我們可以發(fā)現,其實參數值的變化介评,是因為我們在內部可以通過引用來修改到參數值本身库北,但是其本質仍然是一個引用,而final定義的不可改變们陆,是引用的不可改變寒瓦,也就是引用本身是不能改變的(即不能指向其它對象!)
所以引用的不可改變就表示了我們始終只能修改最初開始所指向的值本身坪仇,而不會因為修改成中間可能指向了其它值得引用本身杂腰。這里可能有點難以理解,我們可以通過以下代碼來加強理解一下:
public class AnPoJo {
public int a = 1;
@Override
public String toString() {
return String.valueOf(a);
}
}
public class Application {
public static void main(String[] args) {
AnPoJo pojo = new AnPoJo();
modify1(pojo);
System.out.println("modify1: "+ pojo);
modify2(pojo);
System.out.println("modify2: " + pojo);
}
private static AnPoJo modify1(AnPoJo pojo){
pojo = new AnPoJo();
pojo.a = 2;
return pojo;
}
private static AnPoJo modify2(AnPoJo pojo){
pojo.a = 2;
return pojo;
}
}
打印結果如下:
modify1: 1
modify2: 2
由此可見烟很,其實只要引用本身不變化颈墅,是可以成功達到想要修改參數值的效果的。因此這里如果使用final關鍵字來標記想要直接改變參數值的方法是一個非常好的編程習慣雾袱,杜絕了疏忽可能導致的錯誤恤筛。例如可以這樣來修改方法modify1,通過編譯器就能很容易的檢測到錯誤點:
private static AnPoJo modify1(final AnPoJo pojo){
pojo = new AnPoJo(); // compile error
pojo.a = 2;
return pojo;
}
結語
其實有很多非常好的編程技巧芹橡,看似平淡無奇毒坛,但是仔細思考起來還是蠻有意思的一件事,能從中體會到優(yōu)秀代碼中對語言本身的理解和把握林说。除了本例之外煎殷,還有諸如其它一些例子,如: if(true==isValid())
當然由于知識結構所限腿箩,也有一個學習的過程豪直,如果有所錯誤或者遺漏,歡迎大家指正補充~
并在此給出一個簡單的結論:
- 如果不清楚到底是傳值還是傳址珠移,Java中就全部當做傳值處理就ok(除了部分特殊場景外弓乙,也不會有什么不足)
- 如果一定要改變參數值本身,一定要加final關鍵字钧惧!
enjoy~