兩個Long類型真的不能直接用>或<比較麼?其實能夠

  當我在Google輸入「Long類型的比較」時,會出現多如牛毛的與這個問題相關的博文,而且這些博文對此問題的見解一模一樣,都「不約而同」地持有以下觀點:java

對於Long類型的數據,它是一個對象,因此對象不能夠直接經過「>」,「==」,「<」的比較。若要比較是否相等,能夠用Long對象的equals方法;若要進行「>」,「<」的比較,需經過Long對象的longValue方法。app

那麼問題來了,這個觀點真的全對嗎?或者準確地說,後半段關於「>」,「<」的說法真的對嗎?起初我也差點信了,按理說Java中並無像C++中的操做符重載之類的東東,對象直接拿來用「>」或「<」比較確實不多這麼幹的,並且有童鞋可能會說,既然你們都這麼說,固然是對的無疑咯。那麼今天筆者想告訴你的是,它是錯的Long類型能夠直接用「>」和「<」比較,而且其餘包裝類型也同理。不信?先別急着反駁,且聽筆者娓娓道來。ide

問題起源

  關於Long類型的大小比較這個問題,實際上是源於個人上一篇博文談談ali與Google的Java開發規範,在其中關於「相同類型的包裝類對象之間值的比較」這一規範,我補充了以下一點:Image1.png單元測試

  而後oschina上的一個熱心網友關於此提出了一個很好的問題:Image2.png測試

  即有沒有可能比較的是內存地址而且恰好其大小知足上述條件?想一想也不無道理,畢竟對於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

1、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的當前值爲trueImage3.png

  此時咱們經過Change Value修改a中的value值爲100L,如圖:Image4.png

  而後F8到斷點2,觀察此時flagAfterAlter的值爲falseImage5.png

  最後的輸出結果以下:

flagBeforeAlter: true, flagAfterAlter: false

  由此說明,兩個Long對象直接用「>」或「<」比較時,是數值比較而非地址比較。

  好了,上面的debug測試已經能解釋咱們的困惑,可是筆者認爲這還不夠!僅僅停留在表面不是咱們程序猿的做風,咱們要從本質——源碼出發。原理是什麼?爲何最終比較的是數值而不是引用?難道這也發生了自動拆箱嗎?(跟咱們之前所認知的自動拆箱有出入哦)

2、迴歸本質——字節碼

  真理來自源碼。咱們經過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(...)   從字節碼能夠清晰地看到第2327行以及第4145行,invokevirtual,顯式調用了java/lang/Long.longValue:()方法,確實自動拆箱了。也就是說對於基本包裝類型,除了咱們以前所認知的自動裝箱和拆箱場景(關於自動裝箱和拆箱,你們能夠參考這篇博文——Java中的自動裝箱與拆箱,寫的不錯,這裏我就不作過多敘述了)外,對於兩個包裝類型的>和<的操做,也會自動拆箱。無需任何testCase來佐證,結論一目瞭然。

  除了Long類型,感興趣的童鞋還能夠找Integer、Byte、Short等來驗證下,結果是同樣的,這裏我就不作過多敘述了。

總結

  古人說得好——盡信書,則不如無書。可能,大多數的咱們在面對這個問題時,都會下意識地去Google一把,而後多家博客對比查閱,最後發現幾乎全部的博文都是一致的觀點:Long對象不可直接用">"或"<"比較,須要調用Long.longValue()來比較。因而毫無疑問地就信了。當再次遇到這個問題時,就會「很自信」地告訴別人,要用Long.longValue()比較。而實際呢,殊不知道本身已經陷入誤區!

  雖然今天談論的只是Long對象的">"或"<"用法問題,看起來好像是個「小問題」,最壞狀況下,若是不肯定是否能夠直接比較,大不了直接用Long.longValue來比較,並不會阻礙你編碼。可是,筆者想說可是,做爲一個程序猿,打破砂鍋問到底的精神是不可少的,咱們應該拒絕黑盒,追求細節,這樣纔可能更好地成長,在代碼的世界裏遊刃有餘。

同步更新到原文

相關文章
相關標籤/搜索