您好,登錄后才能下訂單哦!
本篇內容主要講解“Java中的==和equals的區別有哪些”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Java中的==和equals的區別有哪些”吧!
java內存知識點:
引用對象存儲的內存是引用對象的內存地址,類似0xa5、0xa6;基本數據類型存儲的常量值,例如1、2、5。==比較的就是存儲內存內容,因為有可能是內存地址,有可能是常量值,所以結果會產生混淆。
在詳細了解==與equals底層原理之前,先知道怎么使用:
== 遇到兩側都是對象時,則比較對象的引用地址是否相同,否則全是比較其常量值是否相同
equals 左側必須是對象,調用該對象的equals方法,返回true則相等,對象的equals方法可被重寫
在說明之前先看下例子
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都是對象類型,所以這里比較的是對象的引用地址是否相同。其中a是通過new關鍵字在堆內存開辟的空間,其引用是指向該內存空間的地址。而b則涉及到了Integer的享元模式,即JVM在啟動時會針對Integer實例化一批Integer數據放到緩存池中,這批數據的范圍默認是[-128,127],可以通過JVM參數調整,不在該范圍的Integer則通過new方式創建。通過享元模式在開發中使用該范圍內的Integer數據時,會直接從緩存池中獲取。而這里Integer b=3則是從緩存池中取出的,其引用地址也是批量初始化時開辟的內存空間。二者不同,故返回false
==兩側中d是基本數據類型,另一側為包裝類型,這時會導致另一側自動拆箱,Integer轉為int,轉換方法為Integer#intValue,然后才去比較。這樣==兩側就都是int類型。故二者相等。了解自動拆箱能更清楚這點。
== 兩側都是對象類型,因此會比較地址。而這兩個對象在創建時符合Integer享元數據故從緩存池[-128,127]中獲取,二者引用地址指向的都是緩存池中Integer=3的內存地址,故二者相等
==兩側中c是基本數據類型,另一側為包裝類型,比較原理與a == d一致。
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 }
== 兩側都是對象類型。根據包裝類型的構建(裝箱過程)會先調用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比較時左側一定為對象,那么先看下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,如果使用==則比較的是引用地址。
針對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的區別有哪些”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。