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

覆盖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值的相关信息,否则会导致内存泄露问题。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值