==
面試的時候經常會遇到讓你說明==
和equals
的區別,通常會拿String類做舉例,比如下面代碼:
public class Test {
public static void main(String[] args) {
String s1="hello world";
String s2="hello world";
System.out.println(s1 == s2);
}
}
輸出結果爲:true
但是如果是下面的代碼:
public class Test {
public static void main(String[] args) {
String s3=new String("hello world");
String s4=new String("hello world");
System.out.println(s3 == s4);
}
}
輸出結果爲:false
爲什麼同樣的字符串值,一個是直接賦值給String,一個是通過new實例,2者比較卻截然不同。接下來我們先說下==
的結論,然後我們再證明,結論如下:
結論一:比較兩個值或對象指向的地址值是否相同。
結論二:如果是基本數據類型,則直接比較其值是否相等。
我們看看結論一:先利用JClassLib工具查看class文件字節碼指令信息,從以下圖看出,紅圈部分就是在使用==
比較判斷用的字節碼指令
那if_acmpne
是什麼意思呢?點擊 if_acmp 可以查看它的介紹,總的來說就是if_acmp
就是比較2個的地址是否相等,它是Java虛擬機的一個字節碼指令,因此在Java虛擬機裏對==
的這種比較是採用它們指向的地址是否相同,那怎麼知道2個值或對象指向的地址是否相同呢?由於Java沒有直接獲取自己的地址的方法,這邊可以用寫好的 AddressUtil.java 這個工具類可以拿到對象地址,代碼如下:
public class Test {
public static void main(String[] args) {
String s1="hello world";
String s2="hello world";
AddressUtil.printAddresses("s1的地址",s1);
AddressUtil.printAddresses("s2的地址",s2);
System.out.println(s1 == s2);
}
}
輸出結果爲:
s1的地址: 0x76b18c570
s2的地址: 0x76b18c570
true
可以看到s1地址和s2的地址是相同的,因此符合s1 == s2
比較的結果是true
。接下來看下面的代碼:
public class Test {
public static void main(String[] args) {
String s3=new String("hello world");
String s4=new String("hello world");
AddressUtil.printAddresses("s3的地址",s3);
AddressUtil.printAddresses("s4的地址",s4);
System.out.println(s3 == s4);
}
}
輸出的結果爲:
s3的地址: 0x76b18c5d0
s4的地址: 0x76b18c628
false
可以看到s3 的地址和s4的地址是不一樣的,因此符合s3 == s4
比較的結果是false
。那麼思考一下,爲什麼s3和s4的地址不相同呢?
我們再來看看結論二:如果是基本數據類型,則直接比較其存儲的 “值”是否相等。我們看下面代碼:
public class Test {
public static void main(String[] args) {
int a=1;
int b=1;
System.out.println(a==b);
}
}
輸出結果爲true
,這裏a和b都是基本數據類型,我們來看看它是怎麼直接比較存儲值是否相等的 ,同樣用JClassLib工具查看class文件字節碼指令信息,如下圖:
看到紅圈部分if_icmpne
在官網有解釋,點擊if_icmpne可以查看,總的倆說if_icmpne
指令就是用來比較int值是否相等的,依此類推,你能證明float、double、long、char等等基本數據類型也是比較存儲值是否相等嗎?
equals
接下來看equals方法,代碼如下:
public class Test {
public static void main(String[] args) {
String s1="hello world";
String s2="hello world";
System.out.println(s1.equals(s2));
}
}
輸出結果爲true
再看下面代碼:
public class Test {
public static void main(String[] args) {
String s3=new String("hello world");
String s4=new String("hello world");
System.out.println(s3.equals(s4));
}
}
輸出結果爲true
從上面看出,不管是直接賦值給String的還是new實例出來的,equals比較都是true,那equals是比較地址的值還是比較的是內容呢?先說下equals的結論:
結論一:默認比較的是兩個值或對象指向的地址值是否相同
結論二:String類重寫了equals()方法,比較的是內容是否相同
來看看結論一,首先都知道Object是所有類的父類,任何類都默認繼承Object,而String的equals方法重寫的是Object類equals方法:java.lang.Object#equals
,來看看Object類裏equals方法的代碼是怎麼實現的:
public boolean equals(Object obj) {
return (this == obj);
}
居然默認使用了==
比較,前面說了==
比較是比較的是地址是否相同,因此結論一是對的。
然後再來看結論二,String類重寫了equals()方法,來看看java.lang.String#equals
的代碼:
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;
}
從上面可以看出String的equals方法代碼邏輯是比較字符串內容的,依此類推Integer、Double、Float對象是不是也是重寫equals了方法。
總結
- ==和equals比較的是兩個值或對象指向的地址值是否相同
- 如果比較內容是否相同需要重寫equals方法,在重寫的方法實現比較內容的邏輯,比如String、Integer類就是重寫equals了方法
延伸:對象作爲 map 的 key 時,爲什麼需要重寫 equals 方法和 hashCode 方法?