您好,登錄后才能下訂單哦!
本篇內(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方法可被重寫
在說明之前先看下例子
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 }
== 兩側(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
==兩側(cè)中d是基本數(shù)據(jù)類型,另一側(cè)為包裝類型,這時會導(dǎo)致另一側(cè)自動拆箱,Integer轉(zhuǎn)為int,轉(zhuǎn)換方法為Integer#intValue,然后才去比較。這樣==兩側(cè)就都是int類型。故二者相等。了解自動拆箱能更清楚這點。
== 兩側(cè)都是對象類型,因此會比較地址。而這兩個對象在創(chuàng)建時符合Integer享元數(shù)據(jù)故從緩存池[-128,127]中獲取,二者引用地址指向的都是緩存池中Integer=3的內(nèi)存地址,故二者相等
==兩側(cè)中c是基本數(shù)據(jù)類型,另一側(cè)為包裝類型,比較原理與a == d一致。
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é)束
== 兩側(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 }
== 兩側(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比較時左側(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,如果使用==則比較的是引用地址。
針對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í)!
免責聲明:本站發(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)容。