Effective Item 5 - 覆盖equals方法时需要注意

多数情况下,我们不会去覆盖equals方法。

什么时候不需要覆盖equals?

·类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的。

·超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的。

·当确定该类的equals方法不会被调用时,比如类是私有的。

如果要问什么时候需要覆盖equals?

答案正好和之前的问题相反。

即,类需要一个自己特有的逻辑相等概念,而且超类提供的equals不满足自己的行为。

(PS:对于枚举而言,逻辑相等和对象相等都是一回事。)

既然只好覆盖equals,我们就需要遵守一些规定:

·自反性 (reflexive):对于任何一个非null的引用值x,x.equals(x)为true。

·对称性 (symmetric):对于任何一个非null的引用值x和y,x.equals(y)为true时y.equals(x)为true。

·传递性 (transitive):对于任何一个非null的引用值x、y和z,当x.equals(y)为true 且 y.equals(z)为true 则 x.equals(z)为true。

·一致性 (consistent):对于任何一个非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)的结果依然一致。

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

其实这些规定随便拿出一个都是很好理解的。

难点在于,当我遵守一个规定时有可能违反另一个规定。

自反性就不用说了,很难想想会有人违反这一点。

关于对称性,下面提供一个反面例子:

class CaseInsensitiveString {

	private final String s;

	public CaseInsensitiveString(String s) {
		if (s == null)
			this.s = StringUtils.EMPTY;
		else
			this.s = s;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof CaseInsensitiveString)
			return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);
		if (obj instanceof String)
			return s.equalsIgnoreCase((String) obj);
		return false;
	}

}

这个例子显然违反对称性,即x.equals(y)为true 但 y.equals(x)为false。

不仅是在显示调用时,如果将这种类型作为泛型放到集合之类的地方,会发生难以预料的行为。

而对于上面这个例子,在equals方法中我就不牵扯其他类型,去掉String实例的判断就可以了。

关于传递性,即,当x.equals(y)为true 且 y.equals(z)为true 则 x.equals(z)为true。

这个规定在对类进行扩展时尤其明显。

比如,我用x,y描述某个Point:

class Point {
	private final int x;
	private final int y;

	public Point(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof Point))
			return false;
		Point p = (Point) obj;
		return p.x == x && p.y == y;
	}

}

现在我想给Point加点颜色:

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 obj) {
		if (!(obj instanceof ColorPoint))
			return false;
		return super.equals(obj) && ((ColorPoint) obj).color == color;
	}

}

似乎很自然的提供了ColorPoint的equals方法,但他连对称性的没能满足。

于是我们加以修改,令其满足对称性:

@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof Point))
			return false;
		if (!(obj instanceof ColorPoint))
			return obj.equals(this);
		return super.equals(obj) && ((ColorPoint) obj).color == color;
	}

好了,接下来我们就该考虑传递性了。

比如我们现在有三个实例,1个Point和2个ColorPoint....然后很显然,不满足<当x.equals(y)为true 且 y.equals(z)为true 则 x.equals(z)为true>。

事实上,我们无法在扩展可实例化类的同时,既增加新的值组件,又保留equals约定。

于是我索性不用instanceof,改用getClass()。

这个确实可以解决问题,但很难令人接受。

如果我有一个子类没有覆盖equals,此时equals的结果永远是false。

既然如此,我就放弃继承,改用复合(composition)。

以上面的ColorPoint作为例子,将Point变成ColorPoint的field,而不是去扩展。

代码如下:

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;
	}

	/**
	 * Returns the point-view of this color point.
	 */
	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();
	}
}

关于一致性,即如果两者相等则始终相等,除非有一方被修改。

这一点与其说equals方法,到不如思考写一个类的时候,这个类应该设计成可变还是不可变。

如果是不可变的,则需要保证一致性。

考虑到这些规定,以下是编写equals的一些建议:

·第一步使用"=="操作验证是否为同一个引用,以免不必要的比较操作。

·使用instanceof检查参数的类型。

·检查所有关键的field,对float和double以外的基本类型field直接使用"=="比较。

·回过头来重新检查一遍:是否满足自反性、对称性、传递性和一致性。

Effective Item 5 - 覆盖equals方法时需要注意

时间: 2024-10-10 08:25:28

Effective Item 5 - 覆盖equals方法时需要注意的相关文章

Effective Item 6 - 覆盖equals方法时不要忘记hashCode方法

任何覆盖了equals方法的类都需要覆盖hashCode方法. 忽视这一条将导致类无法与基于散列的数据结构一起正常工作,比如和HashMap.HashSet和Hashtable. 下面是hashCode相关规范: ·在程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这个对象调用多少次hashCode,起结果必须始终如一地返回同一个证书. 如果是同一个程序执行多次,每次调用的结果可以不一致. ·如果两个对象根据equals方法比较是相等的,那么两个对象的hashCo

【Java实战】源码解析为什么覆盖equals方法时总要覆盖hashCode方法

1.背景知识 本文代码基于jdk1.8分析,<Java编程思想>中有如下描述: 另外再看下Object.java对hashCode()方法的说明: /** * Returns a hash code value for the object. This method is * supported for the benefit of hash tables such as those provided by * {@link java.util.HashMap}. * <p> *

Effective Java 第三版——10. 重写equals方法时遵守通用约定

Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化. 在这里第一时间翻译成中文版.供大家学习分享之用. 10. 重写equals方法时遵守通用约定 虽然Object是一个具体的类,但它主要是为继承而设计的.它的所有非 final方法(equals.hashCode.toStr

半夜思考, 为什么建议重写 equals()方法时, 也要重写hashCode()方法

我说的半夜, 并不是真正的半夜, 指的是在我一个人的时候, 我会去思考一些奇怪的问题. 这次思考的如题所示, 为什么重写 equals() 方法时, 强烈建议重写 hashCode() 方法, 这个答案, 应该大多数人都知道, 是为了减少 equals() 方法的调用, 只有当两个对象的 hashCode 相等时, 才会去调用 equals()方法去判断两个对象是否相等, 减少了equals()的调用, 提高了效率 . 话是这么说,  的确, 可以减少很多次的 equals()方法的调用, 但是

Effective Java - 注意覆盖equals

平时很难遇到需要覆盖equals的情况. 什么时候不需要覆盖equals? 类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的. 超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的. 当确定该类的equals方法不会被调用时,比如类是私有的. 如果要问什么时候需要覆盖equals? 答案正好和之前的问题相反. 即,类需要一个自己特有的逻辑相等概念,而且超类提供的equals不满足自己的行为. (PS:对于枚举而言,逻辑相等

effectiveJava(6)覆盖equals方法

实现高质量equals方法的诀窍: 1.使用==操作符检查"参数是否为这个对象的引用".如果是,则返回true.这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做. 2.使用instanceof操作符检查"参数是否为正确的类型".如果不是,返回false.一般说来,所谓"正确的类型"是指equals方法所在的类.有些情况下是指该类所实现的某个接口 3.把参数转换成正确的类型.因为转换之前进行过instanceof测试,所以确保会成功.

重写equals()方法时,需要同时重写hashCode()方法

package com.wangzhu.map; import java.util.HashMap; /** * hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,<br/> * 这样的散列集合包括HashSet.HashMap以及HashTable.<br/> * 能否可以直接根据hashCode值判断两个对象是否相等呢?<br/> * 答案:肯定是不可以的,因为不同的对象可能会生成相同的hashCode值.<br/> * 虽然不能根据h

第八条:覆盖equals时请遵守通用约定

==是物理相等 equals是逻辑相等 因为每个类的实例对象本质上都是唯一的 ,利用物理相等(==)是指一个实例只能相等于它自己. 利用逻辑相等是(equals)指 一个实例是否和另一个实例的某些关键域相等,从而来判断这两实例是否相等. Object类的equals方法的实现:物理相等的话就逻辑相等. 什么时候不需要覆盖Object类的equals方法? 1.希望这个类的实例只能和自身相等,不覆盖equals方法,只继承Object类的equals方法. 我们比较这个类的实例对象是否相等,无论是

覆盖equals时请遵守通用约定——Effective Java 读书笔记

如果满足了以下任一条件,这就正是所期望的结果: 类的每个实例本质上都是唯一的. 不关心类是否提供了"逻辑相等"的测试功能. 超类已经覆盖了equals,从超类继承过来的行为对于子类也是适合的. 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用. 如果类具有自己特有的"罗吉相等"概念(不同于对象等同的概念),而且超类还,没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法. equals方法实现了等价关系: 自反性 对称性 传递