两个Long类型真的不能直接用或比较么?其实可以
2019獨角獸企業重金招聘Python工程師標準>>>
當我在Google輸入“Long類型的比較”時,會出現多如牛毛的與這個問題相關的博文,并且這些博文對此問題的看法如出一轍,都“不約而同”地持有如下觀點:
對于Long類型的數據,它是一個對象,所以對象不可以直接通過“>”,“==”,“<”的比較。若要比較是否相等,可以用Long對象的equals方法;若要進行“>”,“<”的比較,需通過Long對象的longValue方法。
那么問題來了,這個觀點真的全對嗎?或者準確地說,后半段關于“>”,“<”的說法真的對嗎?起初我也差點信了,按理說Java中并沒有像C++中的操作符重載之類的東東,對象直接拿來用“>”或“<”比較確實很少這么干的,而且有童鞋可能會說,既然大家都這么說,當然是對的無疑咯。那么今天筆者想告訴你的是,它是錯的!Long類型可以直接用“>”和“<”比較,并且其他包裝類型也同理。不信?先別急著反駁,且聽筆者娓娓道來。
問題起源
關于Long類型的大小比較這個問題,其實是源于我的上一篇博文談談ali與Google的Java開發規范,在其中關于“相同類型的包裝類對象之間值的比較”這一規范,我補充了如下一點:
然后oschina上的一個熱心網友關于此提出了一個很好的問題:
即有沒有可能比較的是內存地址并且剛好其大小滿足上述條件?想想也不無道理,畢竟對于Java中的對象引用a、b、c的值實際就是對象在堆中的地址。關于這個問題,其實我最初也質疑過,為此我編寫了多種類似上面的testCase,比如:
Long a = new Long(1000L); Long b = new Long(2000L); Long c = new Long(222L); Assert.isTrue(a<b && a>c); //斷言成功最終的結論跟預期一致的:兩者的比較結果跟Long對象中的數值大小的比較結果是一致的,至少從目前所嘗試過的所有testCase來看是這樣的。
從現象到本質
但是,光靠那幾個有限的單元測試,貌似并不具有較強的說服力,心中難免總有疑惑:會不會有特殊的case沒覆蓋到?會不會還是地址比較的巧合?怎么才能有效地驗證我的結論呢?
于是我開始琢磨:畢竟對于new Long()這種操作,是在堆中動態分配內存的,我們不太好控制a、b等的地址大小,那又該怎么驗證上述的比較不是地址比較的結果呢?除了地址之外,還有別的我們能控制的嗎?有的,那就是對象中的內容!我們可以在不改變對象引用值的情況下,改變對象的內容,然后看其比較結果是否發生變化,這對于我們來說輕而易舉。有時候換個角度思考問題,就能有新的收獲!
一、debug驗證
那么接下來,我們就可以用反證法來證明上述問題,還是以本文開頭的testCase為例:假設上述testCase中比較的是地址值,只要我們不對a、b進行賦值操作,即不改變它們的地址值,其比較結果就應該也是始終不變,此時我們僅修改對象中的數值,這里對應Long對象中的value字段,使數值的大小比較與當前Long對象的比較結果相反,如果此時Long對象的比較結果也跟著變為相反,也就推翻了地址比較這一假設,否則就是地址比較,證畢。
接下來以實例來演示我們的推斷過程。首先上代碼:
/*** @author sherlockyb* @2018年1月14日*/ public class JdkTest {@Testpublic void longCompare() {Long a = new Long(1000L);Long b = new Long(222L);boolean flagBeforeAlter = a > b;boolean flagAfterAlter = a > b; // 斷點1System.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>":()V4: return public void longCompare();Code:0: new #17 // class java/lang/Long3: dup 4: ldc2_w #19 // long 1000l7: invokespecial #21 // Method java/lang/Long."<init>":(J)V10: astore_1 11: new #17 // class java/lang/Long14: dup 15: ldc2_w #24 // long 222l18: invokespecial #21 // Method java/lang/Long."<init>":(J)V21: astore_2 22: aload_1 23: invokevirtual #26 // Method java/lang/Long.longValue:()J26: aload_2 27: invokevirtual #26 // Method java/lang/Long.longValue:()J30: lcmp 31: ifle 3834: iconst_1 35: goto 3938: iconst_0 39: istore_3 40: aload_1 41: invokevirtual #26 // Method java/lang/Long.longValue:()J44: aload_2 45: invokevirtual #26 // Method java/lang/Long.longValue:()J48: lcmp 49: ifle 5652: iconst_1 53: goto 5756: iconst_0 57: istore 459: getstatic #30 // Field java/lang/System.out:Ljava/io/PrintStream;62: new #36 // class java/lang/StringBuilder65: dup 66: ldc #38 // String flagBeforeAlter: 68: invokespecial #40 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V71: 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 482: 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;)V91: 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來比較,并不會阻礙你編碼。但是,筆者想說但是,作為一個程序猿,打破砂鍋問到底的精神是不可少的,我們應該拒絕黑盒,追求細節,這樣才可能更好地成長,在代碼的世界里游刃有余。
同步更新到原文。
轉載于:https://my.oschina.net/u/3386233/blog/1612659
總結
以上是生活随笔為你收集整理的两个Long类型真的不能直接用或比较么?其实可以的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jq学习总结之方法
- 下一篇: bug之bootstrap switch