中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

Java中的==和equals的區別有哪些

發布時間:2021-11-24 16:04:22 來源:億速云 閱讀:151 作者:iii 欄目:大數據

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

java內存知識點:

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

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

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

  • equals 左側必須是對象,調用該對象的equals方法,返回true則相等,對象的equals方法可被重寫

等號(==)比較的內容與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的原因

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

a == d 為true的原因

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

b == c 為true的原因

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

b == d 為true的原因

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

JVM匯編指令分析

  public static void test1();
    Code:
       0: new           #3                  // class java/lang/Integer  ## 在堆內存開辟空間,其引用入棧, stack[0]=ref (ref表示為引用對象)
       3: dup                               ## 復制棧頂元素一份, 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  ## 調用JVM內部生成的<init>方法,該方法返回this=stack[0],且出棧,此時stack[0]=ref_3
       8: astore_0                          ## 出棧->入槽,將棧頂元素放入槽0 即stack[0]->salt[0]=ref_3,之后棧空(這里指的是操作數棧)
       9: iconst_3                          ## 入棧,從常量池中獲取int=3放入棧頂,即 stack[0]=3
      10: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  ## 調用靜態方法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;  ## 調用靜態方法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; ## 調用打印方法
      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元素引用類型的值(這里的值是內存地址,例如0xa5,0xa6),當結果不等時跳轉到33 			
      29: iconst_1                          ## 分支1:上邊指令不跳轉,則繼續,這里為入棧int1  
      30: goto          34                  ## 跳轉34直接打印結果,即兩引用類型不等則直接打印,否則跳轉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);結束
      40: aload_0                           // 出槽0->入棧 salt[0]=stack[0]=ref_3
      41: invokevirtual #8                  // Method java/lang/Integer.intValue:()I   ## 調用靜態方法Integer#intValue
      44: iload_3                           ## 出棧->入槽3,stack[0]=salt[3]=3	 
      45: if_icmpne     52                  ## 比較int類型數值大小,當結果不等時跳轉52,否則繼續
      48: iconst_1                          ## 分支1:數值相等時執行,入棧常量1 stack[0]=1,這里同上,boolean值=true
      49: goto          53                     
      52: iconst_0                          ## 分支2:數值不相等時執行,入棧常量0,stack[0]=0,這里同上,boolean值=false
      53: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V  ## 第二個 System.out.println(a == d); 結束
      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);結束
      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 調用靜態方法Integer#intValue
      79: iload_3                           ## 出槽3->入棧 salt[3]=stack[0]=3,stack[0]=Integer.valueOf(3).intValue
      80: if_icmpne     87                  ## 比較int類型數值大小,當結果不等時跳轉87,否則繼續
      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);結束
      91: return                            ## 方法結束
匯編指令結論

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

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

基本數據類型裝箱流程(這也是包裝類型的構建過程):加載基本數據常量,調用其對應的包裝類型的valueOf方法將基本數據類型轉為包裝類型。

包裝類型拆箱流程:調用其對應的xxValue方法獲取其基本數據類型常量,例如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的原因

== 兩側都是對象類型。根據包裝類型的構建(裝箱過程)會先調用ValueOf方法來實例化改包裝對象

針對Integer

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

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

針對Long: 享元范圍不可調[-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比較的內容

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

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

可以看出來,默認的equals還是調用的==,而且是兩側都是對象的==,所以對比的內存地址。equals的使用產生混淆的主要原因是equals能被重寫。

針對Integer: 重寫為基本數據類型值比大小

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

針對Long: 重寫為基本數據類型值比大小

    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: 浮點類型轉int,然后比大小,轉換方式為將浮點存儲的二進制數據當做int類型二進制直接讀取

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

針對Double:浮點類型轉long,然后比大小,轉換方式為將浮點存儲的二進制數據單子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
    }

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

編譯期優化: 常量相加可被直接優化,存儲的是優化后的結果。

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

到此,相信大家對“Java中的==和equals的區別有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

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

AI

蒙城县| 彰武县| 磐石市| 三穗县| 加查县| 长汀县| 通山县| 稷山县| 防城港市| 民县| 江安县| 宁武县| 垦利县| 乌兰县| 沁水县| 田东县| 临沧市| 柏乡县| 格尔木市| 泽库县| 时尚| 福海县| 克什克腾旗| 崇州市| 冷水江市| 荔浦县| 万宁市| 泗洪县| 博野县| 湾仔区| 安福县| 手游| 江门市| 忻州市| 丹江口市| 大安市| 青岛市| 井研县| 沙河市| 阿拉善右旗| 延津县|