當我在Google輸入「Long類型的比較」時,會出現多如牛毛的與這個問題相關的博文,而且這些博文對此問題的見解一模一樣,都「不約而同」地持有以下觀點:java
對於Long類型的數據,它是一個對象,因此對象不能夠直接經過「>」,「==」,「<」的比較。若要比較是否相等,能夠用Long對象的equals方法;若要進行「>」,「<」的比較,需經過Long對象的longValue方法。app
那麼問題來了,這個觀點真的全對嗎?或者準確地說,後半段關於「>」,「<」的說法真的對嗎?起初我也差點信了,按理說Java中並無像C++中的操做符重載之類的東東,對象直接拿來用「>」或「<」比較確實不多這麼幹的,並且有童鞋可能會說,既然你們都這麼說,固然是對的無疑咯。那麼今天筆者想告訴你的是,它是錯的!Long類型能夠直接用「>」和「<」比較,而且其餘包裝類型也同理。不信?先別急着反駁,且聽筆者娓娓道來。ide
關於Long類型的大小比較這個問題,實際上是源於個人上一篇博文談談ali與Google的Java開發規範,在其中關於「相同類型的包裝類對象之間值的比較」這一規範,我補充了以下一點:單元測試
而後oschina上的一個熱心網友關於此提出了一個很好的問題:測試
即有沒有可能比較的是內存地址而且恰好其大小知足上述條件?想一想也不無道理,畢竟對於Java中的對象引用a、b、c的值實際就是對象在堆中的地址。關於這個問題,其實我最初也質疑過,爲此我編寫了多種相似上面的testCase,好比:ui
Long a = new Long(1000L); Long b = new Long(2000L); Long c = new Long(222L); Assert.isTrue(a<b && a>c); //斷言成功
最終的結論跟預期一致的:二者的比較結果跟Long對象中的數值大小的比較結果是一致的,至少從目前所嘗試過的全部testCase來看是這樣的。google
可是,光靠那幾個有限的單元測試,貌似並不具備較強的說服力,心中不免總有疑惑:會不會有特殊的case沒覆蓋到?會不會仍是地址比較的巧合?怎麼纔能有效地驗證個人結論呢?編碼
因而我開始琢磨:畢竟對於new Long()
這種操做,是在堆中動態分配內存的,咱們不太好控制a、b等的地址大小,那又該怎麼驗證上述的比較不是地址比較的結果呢?除了地址以外,還有別的咱們能控制的嗎?有的,那就是對象中的內容!咱們能夠在不改變對象引用值的狀況下,改變對象的內容,而後看其比較結果是否發生變化,這對於咱們來講垂手可得。有時候換個角度思考問題,就能有新的收穫!debug
那麼接下來,咱們就能夠用反證法來證實上述問題,仍是以本文開頭的testCase爲例:假設上述testCase中比較的是地址值,只要咱們不對a、b進行賦值操做,即不改變它們的地址值,其比較結果就應該也是始終不變,此時咱們僅修改對象中的數值,這裏對應Long對象中的value字段,使數值的大小比較與當前Long對象的比較結果相反,若是此時Long對象的比較結果也跟着變爲相反,也就推翻了地址比較這一假設,不然就是地址比較,證畢。code
接下來以實例來演示咱們的推斷過程。首先上代碼:
/** * @author sherlockyb * @2018年1月14日 */ public class JdkTest { @Test public void longCompare() { Long a = new Long(1000L); Long b = new Long(222L); boolean flagBeforeAlter = a > b; boolean flagAfterAlter = a > b; // 斷點1 System.out.println("flagBeforeAlter: " + flagBeforeAlter + ", flagAfterAlter: " + flagAfterAlter); // 斷點2 } }
咱們以debug模式運行上述testCase,首先運行到斷點1處,此處可觀察到flagBeforeAlter
的當前值爲true:
此時咱們經過Change Value
修改a中的value值爲100L,如圖:
而後F8到斷點2,觀察此時flagAfterAlter
的值爲false:
最後的輸出結果以下:
flagBeforeAlter: true, flagAfterAlter: false
由此說明,兩個Long對象直接用「>」或「<」比較時,是數值比較而非地址比較。
好了,上面的debug測試已經能解釋咱們的困惑,可是筆者認爲這還不夠!僅僅停留在表面不是咱們程序猿的做風,咱們要從本質——源碼出發。原理是什麼?爲何最終比較的是數值而不是引用?難道這也發生了自動拆箱嗎?(跟咱們之前所認知的自動拆箱有出入哦)
真理來自源碼。咱們經過javap -c
來看下剛纔那個JdkTest類,反編譯字節碼是啥:
// Compiled from "JdkTest.java" public class org.sherlockyb.blogdemos.jdk.JdkTest { public org.sherlockyb.blogdemos.jdk.JdkTest(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public void longCompare(); Code: 0: new #17 // class java/lang/Long 3: dup 4: ldc2_w #19 // long 1000l 7: invokespecial #21 // Method java/lang/Long."<init>":(J)V 10: astore_1 11: new #17 // class java/lang/Long 14: dup 15: ldc2_w #24 // long 222l 18: invokespecial #21 // Method java/lang/Long."<init>":(J)V 21: astore_2 22: aload_1 23: invokevirtual #26 // Method java/lang/Long.longValue:()J 26: aload_2 27: invokevirtual #26 // Method java/lang/Long.longValue:()J 30: lcmp 31: ifle 38 34: iconst_1 35: goto 39 38: iconst_0 39: istore_3 40: aload_1 41: invokevirtual #26 // Method java/lang/Long.longValue:()J 44: aload_2 45: invokevirtual #26 // Method java/lang/Long.longValue:()J 48: lcmp 49: ifle 56 52: iconst_1 53: goto 57 56: iconst_0 57: istore 4 59: getstatic #30 // Field java/lang/System.out:Ljava/io/PrintStream; 62: new #36 // class java/lang/StringBuilder 65: dup 66: ldc #38 // String flagBeforeAlter: 68: invokespecial #40 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 71: iload_3 72: invokevirtual #43 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 75: ldc #47 // String , flagAfterAlter: 77: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 80: iload 4 82: invokevirtual #43 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 85: invokevirtual #52 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 88: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 91: return }
第59行(這裏的「行」是一種形象的描述,實指當前字節碼相對於方法體開始位置的偏移量)是咱們打印結果的地方:System.out.println(...)
從字節碼能夠清晰地看到第23、27行以及第41、45行,invokevirtual,顯式調用了java/lang/Long.longValue:()
方法,確實自動拆箱了。也就是說對於基本包裝類型,除了咱們以前所認知的自動裝箱和拆箱場景(關於自動裝箱和拆箱,你們能夠參考這篇博文——Java中的自動裝箱與拆箱,寫的不錯,這裏我就不作過多敘述了)外,對於兩個包裝類型的>和<的操做,也會自動拆箱。無需任何testCase來佐證,結論一目瞭然。
除了Long類型,感興趣的童鞋還能夠找Integer、Byte、Short等來驗證下,結果是同樣的,這裏我就不作過多敘述了。
古人說得好——盡信書,則不如無書。可能,大多數的咱們在面對這個問題時,都會下意識地去Google一把,而後多家博客對比查閱,最後發現幾乎全部的博文都是一致的觀點:Long對象不可直接用">"或"<"比較,須要調用Long.longValue()
來比較。因而毫無疑問地就信了。當再次遇到這個問題時,就會「很自信」地告訴別人,要用Long.longValue()
比較。而實際呢,殊不知道本身已經陷入誤區!
雖然今天談論的只是Long對象的">"或"<"用法問題,看起來好像是個「小問題」,最壞狀況下,若是不肯定是否能夠直接比較,大不了直接用Long.longValue來比較,並不會阻礙你編碼。可是,筆者想說可是,做爲一個程序猿,打破砂鍋問到底的精神是不可少的,咱們應該拒絕黑盒,追求細節,這樣纔可能更好地成長,在代碼的世界裏遊刃有餘。
同步更新到原文。