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

来源:互联网 发布:奇策软件 编辑:程序博客网 时间:2024/05/16 15:29
          equals()方法是Object类的方法,而Object是所有类的顶级父类,所有的类都会拥有Object的方法,也会拥有equals()方法。在Object类中,equals()方法的实现特别简单,比较的两个实例为同一个实例则相等。

 public boolean equals(Object obj) {        return (this == obj);    }

一.什么时候不需要覆盖equals()方法?

1.类的每个实例本质上都是唯一的。例如枚举类型,或者Thread类,他们的每一个实例都是唯一的。

2.不关心是否提供了“逻辑相等”的测试功能。

3.超类已经覆盖了equals()方法,从超类继承过来的行为对于子类也是适合的。

4.类是私有的或者包级私有的,可以确定他的equals()方法永远不会被调用。


二.什么时候需要覆盖equals()方法?

       如果类具有自己特定的“逻辑相等”概念。而且超类还没有覆盖equals()方法以实现期望的行为,这时我们就应该需要覆盖equals()方法。通常,需要覆盖equals()方法的类属于“值类”,值类是仅仅表示一个值的类,例如Integer类,程序员使用这些类的equals()方法比较对象的引用时,是希望知道它们在逻辑之上是否相等,而不是想知道它们是否指向同一个对象。

三.覆盖equals()方法的时候需要遵守的约定

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

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

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

4.一致性:对于任何非null的引用值x和y,只要equals()的比较操作在对象中所用的信息没有被修改,那么多次调用equals()方法必须返回相同的结果。

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

四.常见的违背equals约定的例子

1.对称性:

public final class CaseInsensitiveString {private final String s;public CaseInsensitiveString(String s){if(s == null)throw new NullPointerException();this.s = s;}@Overridepublic 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;}public static void main(String[] args) {CaseInsensitiveString c = new CaseInsensitiveString("Person");String s = "person";System.out.println(c.equals(s));System.out.println(s.equals(c));}}

在这个例子中,CaseInsensitiveString的equals()方法的目的是与普通的字符串进行操作,equals()方法知道普通的字符串,所以可以进行比较,但是String类中却不知道CaseInsensitiveString类,所以比较结果是false,这就违反了对称性,所以上面的equals()方法中不能有与String相互操作的代码部分。

3.传递型:

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

这是一个普通的点的类,接着可能他还有一个子类,就是为这个点着色:

public class ColorPoint extends Point {private final Color color;public ColorPoint(int x, int y ,Color color) {super(x, y);this.color = color;}}

如果这个类不覆盖父类的equals()方法,这样做不会违背equals的约定,但是颜色却会被忽略,所以必须覆盖equals()方法。
public boolean equals(Object obj) {if(!(obj instanceof ColorPoint))return false;return super.equals(obj)&&((ColorPoint) obj).color == color;}

上面的equals()方法在比较普通点和有色点的时候,可能会出现不同的结果,违背对称性。所以这种方式不行。
<pre name="code" class="java">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;}

上面的equals()方法满足了对称性,但是却违背的传递性,比如有三个点,分别是(1,3,Color.RED)、(1,3)、(1,3,Color.BLUE),第一个点与第二个点比较返回true,第二个点与第三个点比较也返回true,但是第一个点与第三个点之间比较会返回false,这就违背了传递性。这是因为我们无法在扩展可实例化的类的同时,既增加新的组件,同时又保留equals的约定。解决的办法是我们采用复合优先于继承,我们不让ColorPoint类扩展Point,而是在ColorPint类中加入一个私有的Point域。

public boolean equals(Object obj) {if(!(obj instanceof ColorPoint))return obj.equals(this);ColorPoint cp = (ColorPoint)obj;return cp.point.equals(obj)&&cp.color == color;}

五.实现高质量的equals()方法的诀窍

1.使用==操作符检查“参数是否为这个对象的引用”,如果是则返回true。这只是一种性能优化的手段。

2.使用instanceof操作符检查“参数是否为正确的类型”,如果不是,则返回false。

3.把参数转换成正确的类型。

4.对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配。

5.检查该类是否满足对称性、传递性、一致性。

6.其他注意事项

1.覆盖equals()方法的时候总是要覆盖hashCode()方法。

2.不要让equals()方法过于智能。

3.不要将equals()声明的中的Object对象替换为其他的类型


0 0
原创粉丝点击