溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中的==和equals的區(qū)別有哪些

發(fā)布時間:2021-11-24 16:04:22 來源:億速云 閱讀:149 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容主要講解“Java中的==和equals的區(qū)別有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Java中的==和equals的區(qū)別有哪些”吧!

java內(nèi)存知識點:

引用對象存儲的內(nèi)存是引用對象的內(nèi)存地址,類似0xa5、0xa6;基本數(shù)據(jù)類型存儲的常量值,例如1、2、5。==比較的就是存儲內(nèi)存內(nèi)容,因為有可能是內(nèi)存地址,有可能是常量值,所以結(jié)果會產(chǎn)生混淆。

在詳細了解==與equals底層原理之前,先知道怎么使用:

  • == 遇到兩側(cè)都是對象時,則比較對象的引用地址是否相同,否則全是比較其常量值是否相同

  • equals 左側(cè)必須是對象,調(diào)用該對象的equals方法,返回true則相等,對象的equals方法可被重寫

等號(==)比較的內(nèi)容與JVM底層原理

在說明之前先看下例子

public static void test1() {
     Integer a = new Integer(3);
     Integer b = 3;
     Integer c = 3;
     int d = 3;
     System.out.println(a == b);   // false
     System.out.println(a == d);   // true
     System.out.println(b == c);   // true
     System.out.println(b == d);   // true
}
a == b 為false的原因

== 兩側(cè)a和b都是對象類型,所以這里比較的是對象的引用地址是否相同。其中a是通過new關(guān)鍵字在堆內(nèi)存開辟的空間,其引用是指向該內(nèi)存空間的地址。而b則涉及到了Integer的享元模式,即JVM在啟動時會針對Integer實例化一批Integer數(shù)據(jù)放到緩存池中,這批數(shù)據(jù)的范圍默認是[-128,127],可以通過JVM參數(shù)調(diào)整,不在該范圍的Integer則通過new方式創(chuàng)建。通過享元模式在開發(fā)中使用該范圍內(nèi)的Integer數(shù)據(jù)時,會直接從緩存池中獲取。而這里Integer b=3則是從緩存池中取出的,其引用地址也是批量初始化時開辟的內(nèi)存空間。二者不同,故返回false

a == d 為true的原因

==兩側(cè)中d是基本數(shù)據(jù)類型,另一側(cè)為包裝類型,這時會導(dǎo)致另一側(cè)自動拆箱,Integer轉(zhuǎn)為int,轉(zhuǎn)換方法為Integer#intValue,然后才去比較。這樣==兩側(cè)就都是int類型。故二者相等。了解自動拆箱能更清楚這點。

b == c 為true的原因

== 兩側(cè)都是對象類型,因此會比較地址。而這兩個對象在創(chuàng)建時符合Integer享元數(shù)據(jù)故從緩存池[-128,127]中獲取,二者引用地址指向的都是緩存池中Integer=3的內(nèi)存地址,故二者相等

b == d 為true的原因

==兩側(cè)中c是基本數(shù)據(jù)類型,另一側(cè)為包裝類型,比較原理與a == d一致。

JVM匯編指令分析

  public static void test1();
    Code:
       0: new           #3                  // class java/lang/Integer  ## 在堆內(nèi)存開辟空間,其引用入棧, stack[0]=ref (ref表示為引用對象)
       3: dup                               ## 復(fù)制棧頂元素一份, stack[1]=ref stack[0]=ref
       4: iconst_3							## 入棧常量int=3,剛開辟空間的引用指向常量int=3,stack[1]=ref_3 stack[0]=ref_3		
       5: invokespecial #4                  // Method java/lang/Integer."<init>":(I)V  ## 調(diào)用JVM內(nèi)部生成的<init>方法,該方法返回this=stack[0],且出棧,此時stack[0]=ref_3
       8: astore_0                          ## 出棧->入槽,將棧頂元素放入槽0 即stack[0]->salt[0]=ref_3,之后棧空(這里指的是操作數(shù)棧)
       9: iconst_3                          ## 入棧,從常量池中獲取int=3放入棧頂,即 stack[0]=3
      10: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  ## 調(diào)用靜態(tài)方法Integer#valueOf
      13: astore_1							## 出棧->入槽1,stack[0]=salt[1]=Integer.valueOf(3).注意該方法
      14: iconst_3							## 入棧常量3,stack[0]=3
      15: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  ## 調(diào)用靜態(tài)方法Integer#valueOf
      18: astore_2							## 出棧->入槽2,stack[0]=salt[2]=Integer.valueOf(3).注意該方法	 
      19: iconst_3                          ## 入棧常量3,stack[0]=3
      20: istore_3							## 出棧->入槽3,stack[0]=salt[3]=3	 		
      21: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream; ## 調(diào)用打印方法
      24: aload_0                           ## 出槽0->入棧 salt[0]=stack[0]=ref_3
      25: aload_1                           ## 出槽1->入棧 salt[1]=stack[0]=Integer.valueOf(3),stack[1]=ref_3
      26: if_acmpne     33					## 比較棧頂2元素引用類型的值(這里的值是內(nèi)存地址,例如0xa5,0xa6),當結(jié)果不等時跳轉(zhuǎn)到33 			
      29: iconst_1                          ## 分支1:上邊指令不跳轉(zhuǎn),則繼續(xù),這里為入棧int1  
      30: goto          34                  ## 跳轉(zhuǎn)34直接打印結(jié)果,即兩引用類型不等則直接打印,否則跳轉(zhuǎn)33
      33: iconst_0                          ## 分支2: 對比兩引用類型相等,則入棧int=0,然后打印,這里的boolean類型,實際為int類型(0、1)
      34: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
      37: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream; ##第一個System.out.println(a == b);結(jié)束
      40: aload_0                           // 出槽0->入棧 salt[0]=stack[0]=ref_3
      41: invokevirtual #8                  // Method java/lang/Integer.intValue:()I   ## 調(diào)用靜態(tài)方法Integer#intValue
      44: iload_3                           ## 出棧->入槽3,stack[0]=salt[3]=3	 
      45: if_icmpne     52                  ## 比較int類型數(shù)值大小,當結(jié)果不等時跳轉(zhuǎn)52,否則繼續(xù)
      48: iconst_1                          ## 分支1:數(shù)值相等時執(zhí)行,入棧常量1 stack[0]=1,這里同上,boolean值=true
      49: goto          53                     
      52: iconst_0                          ## 分支2:數(shù)值不相等時執(zhí)行,入棧常量0,stack[0]=0,這里同上,boolean值=false
      53: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V  ## 第二個 System.out.println(a == d); 結(jié)束
      56: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream; 
      59: aload_1                           ## 出槽1->入棧 salt[1]=stack[0]=Integer.valueOf(3)
      60: aload_2                           ## 出槽2->入棧 salt[2]=stack[0]=Integer.valueOf(3),stack[0]=Integer.valueOf(3)
      61: if_acmpne     68                  ## 引用地址比較: 
      64: iconst_1                          ## 分支1: true
      65: goto          69
      68: iconst_0                          ## 分支2: false 
      69: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V    ## 第三個System.out.println(b == c);結(jié)束
      72: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      75: aload_1							## 出槽1->入棧 salt[1]=stack[0]=Integer.valueOf(3)
      76: invokevirtual #8                  // Method java/lang/Integer.intValue:()I 調(diào)用靜態(tài)方法Integer#intValue
      79: iload_3                           ## 出槽3->入棧 salt[3]=stack[0]=3,stack[0]=Integer.valueOf(3).intValue
      80: if_icmpne     87                  ## 比較int類型數(shù)值大小,當結(jié)果不等時跳轉(zhuǎn)87,否則繼續(xù)
      83: iconst_1                          ## 分支1: true
      84: goto          88
      87: iconst_0                          ## 分支2: false
      88: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V   ## 第四個System.out.println(b == d);結(jié)束
      91: return                            ## 方法結(jié)束
匯編指令結(jié)論

== 兩側(cè)都是引用類型時,會直接比較引用類型的存儲值(存儲的是指向的對象的地址)來判斷結(jié)果

== 兩側(cè)任何一側(cè)為引用類型時,會將引用類型轉(zhuǎn)為基本數(shù)據(jù)類型(存儲的是常量值),然后對比數(shù)值大小來判斷結(jié)果

基本數(shù)據(jù)類型裝箱流程(這也是包裝類型的構(gòu)建過程):加載基本數(shù)據(jù)常量,調(diào)用其對應(yīng)的包裝類型的valueOf方法將基本數(shù)據(jù)類型轉(zhuǎn)為包裝類型。

包裝類型拆箱流程:調(diào)用其對應(yīng)的xxValue方法獲取其基本數(shù)據(jù)類型常量,例如Integer的intValue

再看一個示例

    public static void test2() {
        Integer a = new Integer(150);
        Integer b = 150;
        Integer c = 150;
        int d = 150;
        System.out.println(a == b);   // false
        System.out.println(a == d);   // true
        System.out.println(b == c);   // false
        System.out.println(b == d);   // true
    }
b == c 為false的原因

== 兩側(cè)都是對象類型。根據(jù)包裝類型的構(gòu)建(裝箱過程)會先調(diào)用ValueOf方法來實例化改包裝對象

針對Integer

public static Integer valueOf(int i) {
    // 享元范圍數(shù)據(jù)直接返回緩存對象 
	if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)];
	// 否則構(gòu)建對象
	return new Integer(i);
}

這里150不在默認的享元范圍,則會重新構(gòu)建對象(開辟空間,加載常量,引用指向),此時==兩側(cè)比較的地址則不相等

針對Long: 享元范圍不可調(diào)[-128,127]

public static Long valueOf(long l) {
        final int offset = 128;
        if (l >= -128 && l <= 127) { // will cache
            return LongCache.cache[(int)l + offset];
        }
        return new Long(l);
    }

針對Boolean:是直接緩存的2個對象,從始至終只有2個實例:TRUE、FALSE

    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }

針對Byte

    public static Byte valueOf(byte b) {
        final int offset = 128;
        return ByteCache.cache[(int)b + offset];
    }

針對Short

    public static Short valueOf(short s) {
        final int offset = 128;
        int sAsInt = s;
        if (sAsInt >= -128 && sAsInt <= 127) { // must cache
            return ShortCache.cache[sAsInt + offset];
        }
        return new Short(s);
    }

針對Character

    public static Character valueOf(char c) {
        if (c <= 127) { // must cache
            return CharacterCache.cache[(int)c];
        }
        return new Character(c);
    }

針對Float: 無緩存,全部開辟新空間

    public static Float valueOf(float f) {
        return new Float(f);
    }

針對Double:無緩存,全部開辟新空間

    public static Double valueOf(double d) {
        return new Double(d);
    }

equals比較的內(nèi)容

equals比較時左側(cè)一定為對象,那么先看下Object的equals方法

    public boolean equals(Object obj) {
        return (this == obj);
    }

可以看出來,默認的equals還是調(diào)用的==,而且是兩側(cè)都是對象的==,所以對比的內(nèi)存地址。equals的使用產(chǎn)生混淆的主要原因是equals能被重寫。

針對Integer: 重寫為基本數(shù)據(jù)類型值比大小

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

針對Long: 重寫為基本數(shù)據(jù)類型值比大小

    public boolean equals(Object obj) {
        if (obj instanceof Long) {
            return value == ((Long)obj).longValue();
        }
        return false;
    }

針對Byte

    public boolean equals(Object obj) {
        if (obj instanceof Byte) {
            return value == ((Byte)obj).byteValue();
        }
        return false;
    }

針對Boolean

    public boolean equals(Object obj) {
        if (obj instanceof Boolean) {
            return value == ((Boolean)obj).booleanValue();
        }
        return false;
    }

針對Short

    public boolean equals(Object obj) {
        if (obj instanceof Short) {
            return value == ((Short)obj).shortValue();
        }
        return false;
    }

針對Float: 浮點類型轉(zhuǎn)int,然后比大小,轉(zhuǎn)換方式為將浮點存儲的二進制數(shù)據(jù)當做int類型二進制直接讀取

    public boolean equals(Object obj) {
        return (obj instanceof Float)
               && (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
    }

針對Double:浮點類型轉(zhuǎn)long,然后比大小,轉(zhuǎn)換方式為將浮點存儲的二進制數(shù)據(jù)單子long類型二進制直接讀取

    public boolean equals(Object obj) {
        return (obj instanceof Double)
               && (doubleToLongBits(((Double)obj).value) ==
                      doubleToLongBits(value));
    }

建議:Float與Double比較大小時,使用equals,如果使用==則比較的是引用地址。

主要的混淆對象是String

針對Character:比較的是引用地址

public final boolean equals(Object obj) {
  return (this == obj);
}

針對String: 先比較地址,地址相同則為true,否則比較每一個char的地址,一個不同則為false

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

示例

    public static void test4(){
        String str1 = "const";
        String str2 = "const";
        String str3 = "con" + "st";
        String str4 = "con" + new String("st");
        String str5 = new String("const");
        String str6 = str5.intern();
        String str7 = "con";
        String str8 = "st";
        String str9 = str7 + str8;
        System.out.println(str1 == str2); //true
        System.out.println(str1 == str3); //true
        System.out.println(str1 == str4); //false
        System.out.println(str4 == str5); //false
        System.out.println(str1 == str6); //true
        System.out.println(str1 == str9); //false
    }

這里比較,涉及到了常量與變量的區(qū)別:常量在編譯期會直接放入常量池中,變量只會在運行時進行賦值。

編譯期優(yōu)化: 常量相加可被直接優(yōu)化,存儲的是優(yōu)化后的結(jié)果。

String#intern,會從常量池中獲取首次創(chuàng)建該字符串的地址引用,如果不存在則創(chuàng)建后放入,再返回該引用。注意該引用是常量池引用,與new出來的堆引用不是一個。

到此,相信大家對“Java中的==和equals的區(qū)別有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI