==
基本類型
對于基本類型,== 的功能是比較值。
Object
比較對象在內(nèi)存中的地址。
equals
基本類型無equals方法。Object對象默認equals的實現(xiàn)如下:
/** * ... * @param obj the reference object with which to compare. * @return {@code true} if this object is the same as the obj * argument; {@code false} otherwise. * @see #hashCode() * @see java.util.HashMap */public boolean equals(Object obj) { return (this == obj);}有很長一段注釋,最終的實現(xiàn)我們可以看到還是用的 == 來比較兩個對象在內(nèi)存中的地址。
對于equals的默認實現(xiàn)——比較對象在內(nèi)存中的地址——有時候可能并不是我們期望的,比如有一個Student類:
class Student{ PRivate int id; private String name; public Student(int id){ this.id = id; } // ... getter and setter}當 Student.id 相同時,我們更愿意認為這是同一個學生,而下面這個測試是無法通過的:
@Testpublic void equalsStudent() throws Exception { Student st1 = new Student(2333); Student st2 = new Student(2333); assertEquals(st1, st2); // assertNotEquals(st1, st2);}為了達到我們的目的——相同的學號就認為是同一個人——我們可以重寫Student類的 equals 方法:
class Student { private int id; private String name; public Student(int id) { this.id = id; } @Override public boolean equals(Object o) { if (null == o) { return false; } Student std = (Student) o; if (this.id == std.id) { return true; } return super.equals(o); } // ... getter and setter}修改之后上面的測試就可以通過了。Demo畢竟是簡單的,當我們在實際的使用中需要重寫equals方法時還是需要遵守它的生成規(guī)則,這里貼出來供參考:
public boolean equals(Object obj) 指示其他某個對象是否與此對象“相等”。 equals 方法在非空對象引用上實現(xiàn)相等關系: 自反性:對于任何非空引用值 x,x.equals(x) 都應返回 true。 對稱性:對于任何非空引用值 x 和 y,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true。 傳遞性:對于任何非空引用值 x、y 和 z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 應返回 true。 一致性:對于任何非空引用值 x 和 y,多次調(diào)用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的信息沒有被修改。 對于任何非空引用值 x,x.equals(null) 都應返回 false。 Object 類的 equals 方法實現(xiàn)對象上差別可能性最大的相等關系;即,對于任何非空引用值 x 和 y,當且僅當 x 和 y 引用同一個對象時,此方法才返回 true(x == y 具有值 true)。 注意:當此方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規(guī)協(xié)定,該協(xié)定聲明相等對象必須具有相等的哈希碼。 參數(shù): obj - 要與之比較的引用對象。 返回: 如果此對象與 obj 參數(shù)相同,則返回 true;否則返回 false。 另請參見: hashCode(), Hashtable事情還沒有結束??聪旅娲a:
@Testpublic void studentSet() throws Exception { Student st1 = new Student(2333); Student st2 = new Student(2333); Set<Student> stds = new HashSet<>(); stds.add(st1); stds.add(st2); assertEquals(1, stds.size());}你會發(fā)現(xiàn)這段代碼測試不通過。set不是重復對象只會保留一份嗎,為什么不是 1 呢?
這里就要介紹 hashCode() 方法:
public int hashCode() 返回該對象的哈希碼值。支持此方法是為了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。 hashCode 的常規(guī)協(xié)定是: 在 Java 應用程序執(zhí)行期間,在對同一對象多次調(diào)用 hashCode 方法時,必須一致地返回相同的整數(shù),前提是將對象進行 equals 比較時所用的信息沒有被修改。從某一應用程序的一次執(zhí)行到同一應用程序的另一次執(zhí)行,該整數(shù)無需保持一致。 如果根據(jù) equals(Object) 方法,兩個對象是相等的,那么對這兩個對象中的每個對象調(diào)用 hashCode 方法都必須生成相同的整數(shù)結果。 如果根據(jù) equals(java.lang.Object) 方法,兩個對象不相等,那么對這兩個對象中的任一對象上調(diào)用 hashCode 方法不 要求一定生成不同的整數(shù)結果。但是,程序員應該意識到,為不相等的對象生成不同整數(shù)結果可以提高哈希表的性能。 實際上,由 Object 類定義的 hashCode 方法確實會針對不同的對象返回不同的整數(shù)。(這一般是通過將該對象的內(nèi)部地址轉(zhuǎn)換成一個整數(shù)來實現(xiàn)的,但是 JavaTM 編程語言不需要這種實現(xiàn)技巧。) 返回: 此對象的一個哈希碼值。 另請參見: equals(java.lang.Object), Hashtable當我們使用用哈希表實現(xiàn)的工具類時,這個方法的價值就體現(xiàn)了。由于之前我們沒有重寫這個方法,把Student對象存入HashSet時還是按照之前系統(tǒng)默認的方式計算他們的hash值,導致Set中存在兩個Student對象。
下面做一個簡單修改:
class Student { private int id; private String name; public Student(int id) { this.id = id; } @Override public boolean equals(Object o) { if (null == o) { return false; } // 有時候instanceof不合適的時候可以考慮用getClass()方法 if (getClass() != o.getClass()) { return false; } if (o instanceof Student) { Student std = (Student) o; if (this.id == std.id) { return true; } } return super.equals(o); } @Override public int hashCode() { return this.id; // return super.hashCode(); } // ... getter and setter}再運行下面的測試,你會發(fā)現(xiàn)和我們期待的一致了:
@Testpublic void studentSet() throws Exception { Student st1 = new Student(2333); Student st2 = new Student(2333); Set<Student> stds = new HashSet<>(); stds.add(st1); stds.add(st2); assertEquals(1, stds.size());}Demo中直接將id作為hashcode返回不是一種好的生成方式,具體的生成規(guī)則請參考上面的注釋。
再來看一個demo:
@Testpublic void stringTest() throws Exception { String str1 = "abc"; String str2 = "abc"; String str3 = new String("abc"); System.out.println(str1 == str2); System.out.println(str1 == str3); assertEquals(str1, str2); assertEquals(str1, str3);}結果會怎樣?沒錯,測試通過,輸出:true,false。
首先我們來看下String類重寫的equals()和hashCOde():
@Override public boolean equals(Object other) { if (other == this) { return true; } if (other instanceof String) { String s = (String)other; int count = this.count; if (s.count != count) { return false; } if (hashCode() != s.hashCode()) { return false; } for (int i = 0; i < count; ++i) { if (charAt(i) != s.charAt(i)) { return false; } } return true; } else { return false; }}@Override public int hashCode() { int hash = hashCode; if (hash == 0) { if (count == 0) { return 0; } for (int i = 0; i < count; ++i) { hash = 31 * hash + charAt(i); } hashCode = hash; } return hash;}從上面可以看出,String類重寫了這兩個方法,equals()中的邏輯是比較字符串中每個字符是否相同。因此 str1, str2, str3相同就可以理解了。 對于 str1 == str2 和 str1 != str3 這涉及到不同字符串創(chuàng)建方法。
在開始這個例子之前,同學們需要知道JVM處理String的一些特性。Java的虛擬機在內(nèi)存中開辟出一塊單獨的區(qū)域,用來存儲字符串對象,這塊內(nèi)存區(qū)域被稱為字符串緩沖池。當使用 String a = “abc”這樣的語句進行定義一個引用的時候,首先會在字符串緩沖池中查找是否已經(jīng)相同的對象,如果存在,那么就直接將這個對象的引用返回給a,如果不存在,則需要新建一個值為”abc”的對象,再將新的引用返回a。String a = new String(“abc”);這樣的語句明確告訴JVM想要產(chǎn)生一個新的String對象,并且值為”abc”,于是就在堆內(nèi)存中的某一個小角落開辟了一個新的String對象。
新聞熱點
疑難解答