天天看点

面试 | 如何理解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中,对于八大基本数据类型,= = 符号比较的是字面值;对于引用数据类型,==符号比较的是引用的地址。