在面試中經常遇到這樣一個問題,==和equals()方法有什麼區別?
很多時候,我們回答的都不算完全。這裏總結一下:
==分兩種情況:
1.基本數據類型,比較的是值相等
2.引用類型,比較的是對象的地址
equals分兩種情況:
若沒有對equals()進行重寫,則比較的是引用類型的對象的地址,和==效果一樣
若對equals()進行了重寫,如String、Date等類,那麼比較的是值是否相等。
int i=2;
int j=2;
i==j; //true
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1==p2); //false
Person p3 = p2;
System.out.println(p2==p3); //true
若沒有重寫equals:
Person p1 = new Person();
p1.setName("張三");
Person p2 = new Person();
p2.setName("張三");
System.out.println(p1.equals(p2)); //false
Person p3 = p2;
System.out.println(p2.equals(p3)); //true
若重寫了equals:
String a = "123";
String b = "123";
System.out.println(a.equals(b)); //true
我們在重寫equals的時候,還得重寫hashCode()方法。那麼爲什麼要重寫hashCode()方法呢?因爲對象的比較有硬性規定:
1.應用執行期間,同一個對象內容不發生改變,經過多次調用,hashCode方法都必須始終返回同一個值。如果把對象重新copy到另外一個應用程序裏,hashcode可以和上一個的不一樣。
2.如果兩個對象根據equals(Object)方法比較是相等的,那麼所產生的的hashcode必須一樣。
3.如果兩個對象根據equals(Object)方法比較是不相等的,那麼產生的hashcode可能一樣,也可能不一樣。
若我們只重寫了equals()方法,沒有重寫hashCode()方法,那麼它會調用父類Object的hashCode()方法,那麼得到的hash值就不一樣:
public static void main(String[] args) {
Person p1 = new Person();
p1.setName("張三");
p1.setAge(23);
Person p2 = new Person();
p2.setName("張三");
p2.setAge(23);
System.out.println(p1.hashCode()); //664740647
System.out.println(p2.hashCode()); //804564176
System.out.println(p1.equals(p2)); //true
}
那麼我們重寫的hashCode()如何自定義呢?
第一步:定義一個初始值,一般來說取17
int result = 17;
第二步:分別解析自定義類中與equals方法相關的字段(假如hashCode中考慮的字段在equals方法中沒有考慮,則兩個equals的對象就很可能具有不同的hashCode)
情況一:字段a類型爲boolean 則[hashCode] = a ? 1 : 0;
情況二:字段b類型爲byte/short/int/char, 則[hashCode] = (int)b;
情況三:字段c類型爲long, 則[hashCode] = (int) (c ^ c>>>32);
情況四:字段d類型爲float, 則[hashCode] = d.hashCode()(內部調用的是Float.hashCode(d), 而該靜態方法內部調用的另一個靜態方法是Float.floatToIntBits(d))
情況五:字段e類型爲double, 則[hashCode] = e.hashCode()(內部調用的是Double.hashCode(e), 而該靜態方法內部調用的另一個靜態方法是Double.doubleToLongBits(e),得到一個long類型的值之後,跟情況三進行類似的操作,得到一個int類型的值)
情況六:引用類型,若爲null則hashCode爲0,否則遞歸調用該引用類型的hashCode方法。
情況七:數組類型。(要獲取數組類型的hashCode,可採用如下方法:s[0]*31 ^ (n-1) + s[1] * 31 ^ (n-2) + … + s[n-1], 該方法正是String類的hashCode實現所採用的算法)
第三步:對於涉及到的各個字段,採用第二步中的方式,將其依次應用於下式:
result = result * 31 + [hashCode];
補充說明:如果初始值result不取17而取0的話,則對於hashCode爲0的字段來說就沒有區分度了,這樣更容易產生衝突。
計算hashCode的注意事項:
1、不能包含equals方法中沒有的字段,否則會導致相等的對象可能會有不同的哈希值。(即對類中每一個重要字段,也就是影響對象的值的字段,也就是equals方法裏有比較的字段,進行操作)
2、String對象和Bigdecimal對象已經重寫了hashcode方法,這些類型的值可以直接用於重寫hashcode方法;
3、result = 31 result + [hashCode];,這裏面爲啥用個31來計算,而且很多人都是這麼寫的,這是因爲31是個神奇的數字,任何數n31都可以被jvm優化爲(n<<5)-n,移位和減法的操作效率比乘法的操作效率高很多!
4、Google首席Java架構師Joshua Bloch在他的著作《Effective Java》中提出了一種簡單通用的hashCode算法:
1).初始化一個整形變量,爲此變量賦予一個非零的常數值,比如int result = 17;
2).如果是對象應用(例如有String類型的字段),如果equals方法中採取遞歸調用的比較方式,那麼hashCode中同樣採取遞歸調用hashCode的方式。否則需要爲這個域計算一個範式,比如當這個域的值爲null的時候(即 String a = null 時),那麼hashCode值爲0。
這裏可以看一下我們自定義的示例:
public class TestHash {
public static void main(String[] args) {
Person p1 = new Person();
p1.setName("張三");
p1.setAge(23);
Person p2 = new Person();
p2.setName("張三");
p2.setAge(23);
System.out.println(p1.equals(p2));
}
}
class Person{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31*result + age;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Person person = (Person) obj;
if (!name.equals(person.getName())) {
return false;
}
return true;
}
}