最近抽了點時間溫故,一些零零散散的問題仍是整理了起來。我決定把一些曾經坑過本身的問題寫成博客文章,給學弟學妹們一個警示吧。java
今天的故事從一個例子開始:shell
@Test public void testFinal() { String s1="happyBKsOffer"; String s2="happyBKs"; final String s3="happyBKs";//s3.replace("h", "H"); String s4=s2+"Offer"; String s5=s3+"Offer"; if (s1 == s4) { System.out.println("s1==s4"); } else { System.out.println("s1!=s4"); } if (s1 == s5) { System.out.println("s1==s5"); } else { System.out.println("s1!=s5"); } if (s1.equals(s4)) { System.out.println("s1 equals s4"); } else { System.out.println("s1 not equals s4"); } if (s1.equals(s5)) { System.out.println("s1 equals s5"); } else { System.out.println("s1 not equals s5"); } }
若是你看到這個代碼以爲莫名其妙而且伴隨着一點心虛,以爲不都是同樣的嘛,那麼恭喜你,看完了今天的例子,你就不用再往坑裏跳了。若是你一眼就看出了其中的坑,那麼也恭喜你,能夠在我博客下方評論處BB了,「這也算個問題,太基礎了」。(本文出自:http://my.oschina.net/happyBKs/blog/493904,請自行代表出處)數組
實際的運行結果以下:安全
s1!=s4 s1==s5 s1 equals s4 s1 equals s5
有沒有一點出乎您的意料呢?app
那麼這究竟是怎麼了,s4和s5都是"happBKs"與"Offer"的憑藉,爲何再==和equals下的結果不相同呢?性能
咱們先說說==和equals方法的區別。這兩種判斷兩個值是否相等的方式其實存在本質的不一樣。==在斷定基本數據類型時,沒有什麼不一樣,就是判斷連個基本數據類型的值是否相等;在斷定對象數據類型時,會根據對象的類中覆蓋的equals方法來斷定對象是否相等(若是沒有從新實現類的equals方法,運行時會斷定兩個對象的地址是否指向同一個對象)。優化
這時候,也許純潔無邪的孩子們會說:「我明白了,若是是int類型,那麼equals和==沒什麼區別,Integer類型則不是」。這時候,就會有人在旁邊陰笑,內心想:「拆箱和裝箱都不懂,呵呵。。。結果確定都同樣」spa
我給個例子:.net
@Test public void testInt() { int a=1,b=1; if (a == b) { System.out.println("a==b"); } else { System.out.println("a!=b"); } // if (a.equals(b)) { // System.out.println("a equals b"); // } else { // System.out.println("a not equals b"); // } Integer c=new Integer(1),d=new Integer(1); if (c == d) { System.out.println("c==d"); } else { System.out.println("c!=d"); } if (c.equals(d)) { System.out.println("c equals d"); } else { System.out.println("c not equals d"); } Integer e=1,f=1; if (e == f) { System.out.println("e==f"); } else { System.out.println("e!=f"); } if (e.equals(f)) { System.out.println("e equals f"); } else { System.out.println("e not equals f"); } }
那麼結果會怎麼樣呢?線程
a==b c!=d c equals d 1 e==f e equals f
是的,笑別人單純的人眼睛沒有瞎,真的結果就如同小朋友們料想的那麼單純。好了,別捶胸頓足故做恍然大悟了,單純的結果後面有着不單純的緣由。
首先int的==比較沒有什麼可多說的。爲何Integer類型的c和d,e和f在equals時會有兩種不一樣的記過呢?這是由於java編譯器的優化機制造就的:c和d各自初始化了一個對象類型,因此在==比較時,他們指的不是一個對象,因此==比較是不一樣的,而Integer的equals方法已經重寫,比較的是二者實際的整型數值,因此是相等的。e和f的確用到了裝箱的拆箱,可是java編譯器在編譯時作了優化,將兩個常量1裝箱後優化爲一個對象,這樣e和f指向同一個Integer對象了,因此在==時是相同的。
回到咱們剛纔的實例中。s2和s3雖然都是"happyBKs",可是s2是final類型,s3是通常的類型。在java編譯器編譯時,一樣會對常量數值作一些優化,編譯器會將s4=s2+"Offer"進行優化,由於s2是常量、"Offer"也是常量,因此,s4會在編譯後直接被轉換成s4="happyBKsOffer",而s1的初始化賦值也是常量"happyBKsOffer",因此編譯器會再次將它們優化爲一個String對象,s1和s4指向同一個String對象,因此二者在==是相同的。而s5=s3+"Offer",s3不是final的,因此必須在運行時計算,這樣s5只有在運行時候才能生成一個對象,確定與s1的那個不是同一個String對象,因此s1與s5在==比較時確定是不一樣的。
明白了吧。也許你仍是很揪心,咱們這裏只給出一個建議吧:那就是,儘可能養成用equals的好習慣!
好最後,咱們隊final 自己再作個總結:final能夠修飾類、方法和變量。
修飾類
當用final修飾一個類時,代表這個類不能被繼承。也就是說,若是一個類你永遠不會讓他被繼承,就能夠用final進行修飾。final類中的成員變量能夠根據須要設爲final,可是要注意final類中的全部成員方法都會被隱式地指定爲final方法。
在使用final修飾類的時候,要注意謹慎選擇,除非這個類真的在之後不會用來繼承或者出於安全的考慮,儘可能不要將類設計爲final類。
修飾方法
使用final方法的緣由有兩個。第一個緣由是把方法鎖定,以防任何繼承類修改它的含義;第二個緣由是效率。在早期的Java實現版本中,會將final方法轉爲內嵌調用。可是若是方法過於龐大,可能看不到內嵌調用帶來的任何性能提高。在最近的Java版本中,不須要使用final方法進行這些優化了。
所以,若是隻有在想明確禁止 該方法在子類中被覆蓋的狀況下才將方法設置爲final的。
注:類的private方法會隱式地被指定爲final方法。
修飾變量
修飾變量是final用得最多的地方,也是本文接下來要重點闡述的內容。首先了解一下final變量的基本語法:
對於一個final變量,若是是基本數據類型的變量,則其數值一旦在初始化以後便不能更改;若是是引用類型的變量,則在對其初始化以後便不能再讓其指向另外一個對象。
String類型咱們也作一個補充總結:
String不是基本類型,而是對象類型,而且,其內部喲一個final的char數組,所以String的方法中不提供修改String中內容字符的方法。即使是subString、replace等看似修改了String的方法,其實只是返回了一個新的String對象,而原數組沒有變化。這樣作的目的有性能的、線程安全諸多方面的考慮,之後我會專門開一個文章講這個。