覆盖equals需要遵守的通用约定

  • Post author:
  • Post category:其他



覆盖equals需要遵守的通用约定:


覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重。

因此在equals方法的时候,必须要遵守通用约定:自反性、对称性、传递性、一致性;

1、自反性:对于任何非空的引用值x,x.equals(x)必须返回true;

2、对称性:对于任何非空的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true;

3、传递性:对于任何非空的引用值想,x,y,z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。

4、一致性:对于任何非空的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一直的返回true,或者一直的返回false。

5、对于任何非null的引用值x,x.equals(null)必须返回false。

通过系列两个类验证覆盖equals方法必修要遵守的通用约定:

Point类:

public class Point {
	private final int x;
	private final int y;
	
	public Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
	@Override
	public boolean equals(Object o) {
		if (!(o instanceof Point))
		return false;
		Point p = (Point) o;
		return p.x == x && p.y == y;
	}

}

继承自Point的子类ColorPoint:

public class ColorPoint extends Point {
	private final Color color;
	public ColorPoint(int x, int y, Color color) {
		super(x, y);
		this.color = color;
	}
	@Override
	public boolean equals(Object o) {
		if (!(o instanceof ColorPoint))
			return o.equals(this);
		return super.equals(o) && ((ColorPoint) o).color == color;
	}

a)自反性验证:

Point p = new Point(10,15);
ColorPoint cp = new ColorPoint(10,15,Color.BLUE);
System.out.println(p.equals(p) + " ------ "+cp.equals(cp));   //true ------ true

b) 对称性验证:

Point p = new Point(10,15);
ColorPoint cp = new ColorPoint(10,15,Color.BLUE);
System.out.println(p.equals(cp) + " ------ "+cp.equals(p));   //true ------ true

c) 传递性验证:

Point p = new Point(10,15);
ColorPoint cp = new ColorPoint(10,15,Color.BLUE);
ColorPoint cp1 = new ColorPoint(10,15,Color.RED);
System.out.println(p.equals(cp) + " ------ "+cp.equals(cp1) + "------"+p.equals(cp1));   //true------ false ------ true

通过上述验证,类Point和ColorPoint重写的equals满足自反性、对称性,但是不满足传递性,因此上述重写的equals方法存在问题;

在面向对象中,我们无法在可扩展可实例化的类时,既增加新的组件,同时又保留equals约定,解决办法是使用“复合优于继承”,不在让子类扩展父类,而是在子类中加入一个可私有的父类域和一个公有的视图方法。

代码如下:

public class ColorPoint {
  private final Point point;
  private final Color color;

  public ColorPoint(int x, int y, Color color) {
	  if (color == null)
		throw new NullPointerException();
	  point = new Point(x, y);
	  this.color = color;
  }

  public Point asPoint() {
	return point;
  }

  @Override 
  public boolean equals(Object o) {
	  if (!(o instanceof ColorPoint))
		return false;
	  ColorPoint cp = (ColorPoint) o;
	  return cp.point.equals(point) && cp.color.equals(color);
  }

  @Override 
  public int hashCode() {
	return point.hashCode() * 33 + color.hashCode();
  }
}

传递性的执行结果为:

Point p = new Point(10,15);
ColorPoint cp = new ColorPoint(10,15,Color.BLUE);
ColorPoint cp1 = new ColorPoint(10,15,Color.RED);
System.out.println(p.equals(cp) + " ------ "+cp.equals(cp1) + "------"+p.equals(cp1));   //false ------ false ------ false 

满足自反性、对称性和传递性,三者缺一不可。


在覆盖equals时总要覆盖hashCode方法:


原因:


为什么再覆盖equals方法时总要覆盖hashCode方法?


不这么做会违反hashCode的通用约定,导致该类无法结合所有基于散列的集合一起正常运作,类集合有hashMap、hashSet。

需要做逻辑相等判断的类,覆盖equals方法,如果还需要在散列表(HashMap、HashSet)中作为key,需要覆盖hashcode方法。

覆盖hashCode方法通用约定:

1、在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对同一对象的多次调用,hashCode方法都必须始终返回同一个值。在一个应用程序与另外一个应用程执行过程中,执行hsahCode方法所返回的值可以不一致。

2、如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中的hashCode方法都必须产生同样的整数结果。

3、如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中的hashCode方法,则不一定要求hashCode方法必须产生不同的结果。但开发者应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。

———-摘自《Effective Java》

上述通用约定的实现例如ColorPoint中的覆盖方法hashCode():

@Override 
  public int hashCode() {
	return point.hashCode() * 33 + color.hashCode();
  }

扩展-equals() + hashCode()(解析hashCode通用约定):

1.若重写了equals(Object obj)方法,则有必要重写hashCode()方法。

2.若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数:

覆盖hashCode方法,但不覆盖equals方法,仍然会导致数据的不唯一性

3.若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的int数:

4.若两个对象hashCode()返回相同int数,则equals(Object obj)不一定返回true:

5.若两个对象hashCode()返回不同int数,则equals(Object obj)一定返回false:

6.同一对象在执行期间若已经存储在集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。



版权声明:本文为weixin_43708793原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。