天天看點

面試 | 如何了解equals()方法和hashCode()

1 equals()方法

Object類中的方法,預設檢測一個對象是否等于另外一個對象,即判斷兩個對象是否具有相同的引用。

public class Employee {
	@Override
	public boolean equals(Object obj) {
		// TODO Auto-generated method stub
		return super.equals(obj);
	}
}
           
public class Test {
	public static void main(String[] args) {
		Employee employee1 = new Employee();
		Employee employee2 = new Employee();
		Employee employee3 = employee1;
		
		System.out.println(employee1.equals(employee2));   // false
		System.out.println(employee1.equals(employee3));   // true
		System.out.println(employee3.equals(employee2));   // false
	}
}
           

在實際使用過程中,比較兩個對象的引用往往沒有任何意義,需要比較的是對象的狀态,即對象的屬性是否相等。是以在需要比較類之間是否相等時,都需要重寫equals方法。

public class Employee {
	String name;
	String salary;
	
	@Override
	public boolean equals(Object object) {
		if (this == object) return true;  // 與自身比較傳回true,同時處理null與null的比較
		
		if (object == null) return false;
		
		if (!(object instanceof Employee))
			return false;
		
		Employee employee = (Employee)object;
		if (this.name == employee.name) return true;
		
		return false;
	}
}
           
public class Test {
	public static void main(String[] args) {
		Employee employee1 = new Employee();
		employee1.name = "xiaoLi";
		Employee employee2 = new Employee();
		employee2.name = "xiaoLi";
		Employee employee3 = new Employee();
		employee3.name = "xiaoZhao";
		
		System.out.println(employee1.equals(employee2));  // true
		System.out.println(employee1.equals(employee3));  // false
		System.out.println(employee3.equals(employee2));  // false
	}
}
           

如何處理子類與父類之間equals()?

現在有一個子類Manager繼承Employee類,并重寫父類的equals方法:

public class Manager extends Employee {
        int age;
	@Override
	public boolean equals(Object object) {
		if (!super.equals(object)) return false;
		
		Manager manager = (Manager) object;
		return name == manager.name && age == manager.age;
	}
}
           

但是下面的測試很有意思:

public class Test {
	public static void main(String[] args) {
		Employee employee1 = new Employee();
		Employee employee2 = new Manager();
		Manager manager = new Manager();
		
		employee1.name = "xiaoLi";
		employee2.name = "xiaoHua";
		manager.name = "xiaoLi";
		manager.age = 20;
		
		System.out.println(employee1.equals(manager));   // true
        System.out.println(manager.equals(employee1));    // 抛異常 
	}
}
           

manager.equals(employee1)抛出類不能轉換的異常。我們知道java語言規範要求equals方法具有自反性、對稱性,傳遞性和一緻性;這裡明顯違反了對稱性,即對于任何引用x和y,當且僅當y.equals(x)傳回true,x.equals(y)也應該傳回true。

那麼是哪裡出了問題呢?

子類Manager重寫equlas(),會把object參數轉換成Manager類型的對象,這裡就是父類employee1轉換成子類manager,明顯會報異常,也就是說equals方法沒有校驗到object參數和this不屬于同一個類的情況。即instanceof關鍵詞并不能解決子類的問題。可以把

換成

通過類名進一步判斷兩個類是否相等。但具體的使用還需搭配使用場景。

是以,正确重寫自定義對象的equals方法的步驟:

  • 顯式參數類型應該為Object類型,以便覆寫Object類的equals方法;
  • 檢測this與顯式參數object是否為同一個引用; if (this == object) return true;
  • 檢測object是否為null,如果是則傳回fasle; if (object == null) return false;
  • 比較thsi與object是否屬于同一個類;

    如果equals的語義在每個子類中有所改變,就使用getClass檢測。

    if (getClass() != object.getClass()) return false;

    如果所有的子類都擁有統一的語義,就使用instanceOf檢測。

    if (!(object instanceOf ClassName)) return false;

  • 将object轉換為相應的類類型變量;

    ClassName objectName = (ClassName)object;

  • 對需要比較的屬性進行比較,基本資料類型用==,對象類型用equals,相等傳回true,否則傳回false.

    注意:如果在子類中重新定義equals,則需要包含調用super.equals(other).

2 hashCode()方法

hashCode()方法也是Object類中的一個預設方法,它預設作用是傳回對象的存儲位址,方法傳回一個整型值的散列碼;如果某個對象重寫了equals()方法,如上面Employee類通過name屬性比較兩個employee對象是否相等,由于Employee類沒有重寫hashCode()方法,是以即使employee1.equals(employe2)傳回true,employee1和employee2的hashCode值也不一樣,即在記憶體中可以同時存在,那麼比較的意義也就不存在了。是以,重寫equals()方法同時也需要重寫hashCode()方法,即如果x.equals(y)傳回true,那麼x.hashCode()值必須與y.hashCode()值相同。

看個示例:

public class HashCode {
	public static void main(String[] args) {
		String str = "china";
		StringBuffer sBuffer = new StringBuffer(str);
		System.out.println(str.hashCode() + " , " + sBuffer.hashCode());
		//result: 94631255 , 366712642
		
		String stri = new String("china");
		StringBuffer sBuffer2 = new StringBuffer(stri);
		System.out.println(stri.hashCode() + " , " + sBuffer2.hashCode());
		//result: 94631255 , 1829164700
	}
}
           

str對象和stri對象的hashCode值相等,這是因為String類重寫了hashCode方法;而sBuffer和sBuffer2的hashCode值不相等,這是因為StringBuffer類繼承Object類預設的hashCode(),傳回的是對象的存儲位址。

是以,如果要比較兩個對象是否相等,除了重寫equals()方法還需要重寫hashCode()方法。注意equal()比較的屬性值與計算hashCode的屬性值要一緻;如Employee類根據name屬性判斷對象是否相等,則hashCode()方法也需要根據name屬性值計算hashCode值。

那麼如何重寫hashCode()方法呢?

hashCode方法應該傳回一個整形數值,也可以是負數,并合理的組合屬性的散列碼,以便能夠讓各個不同的對象産生均勻的散列碼。下面有常用的幾種方法作為參考:

public int hashCode() {
	return 7*Objects.hashCode(name) 
		+ 11*Double.hashCode(height) 
		+ 13*Integer.hashCode(age);
}
           

這個方法的好處是當name,height或者age屬性為null時,hashCode值為0.否則傳回參數的hashCode值。

public int hashCode() {
	return Objects.hash(name, height, age);
}
           

組合各屬性的散列碼,計算hashCode值的方法與第一種方法類似。感興趣的可以檢視Objects類的源碼。

3 ==符号

Java中,對于八大基本資料類型,= = 符号比較的是字面值;對于引用資料類型,==符号比較的是引用的位址。