覆盖equals方法的权宜之计,组合优于继承

当你创建自己的类型,一种有效的建议就是去重新实现那些属于Object类的一些方法——toString()、equals()、hashcode()。没错,这些都是正确的建议。但是,对于这些方法有时我们并不能很有效地去为我们的类给出高效的实现,比如说eqauls()。

假设有一个类,point:

/*
 * created by:yh
 * date:2011-08-18
 */
public class Point {

	private int x,y;

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

	@Override
	public boolean equals(Object arg0) {
		if (arg0==null) {
			return false;
		}

		if (!(arg0 instanceof Point)) {
			return false;
		}

		Point tmp=(Point)arg0;

		return tmp.x==this.x&&tmp.y==this.y;
	}

}

可以看到,我们在这个类中,有两个成员变量,分别用来存储Point的x坐标和y坐标。并且,我们覆盖了基类的equals(),给出了我们自己的实现,这没有太大的问题。

然后,我们考虑到,有些点是有颜色的。然后,我们需要一个ColorPoint继承Point,并对其加以扩展。

public class ColorPoint1 extends Point {

	private int x;
	private int y;
	private String color;

	public ColorPoint1(int x, int y,String color) {
		super(x, y);
		this.color=color;
	}

}

可以看到,在代码中,我们定义了一个ColorPoint1类,继承自Point。同时增加了一个值组件(Color)。然后,我们考虑,该如何实现它自己的equals方法呢?

实现1:比较所有的值域:x,y,color,也就是说,只有坐标相同,并且颜色也相同,我们才认为这样的点是相同的。实现如下:

@Override
	public boolean equals(Object arg0) {
		if (!(arg0 instanceof ColorPoint)) {
			return false;
		}

		return super.equals(arg0)&&((ColorPoint1)arg0).color==this.color;
	}

没错,看似正确的逻辑。

下面我们创建两个点:

Point p1=new Point(1,2);

ColorPoint1 c1=new ColorPoint1 (1,2,"red");

你会发现c1.equals(p1)和p1.equals(c1);返回值居然不同。前者的比较逻辑正如ColorPoint1
的equals()那样,首先p1不是ColorPoint1
的实例,它没有color字段,所以这样的比较总是返回false。而后一个的比较逻辑如Point的equals(),它仅仅比较坐标值,所以它会返回true.

有人会问,这有什么问题。这确实有问题,它违反了实现equals()方法的规范之一——对称性。即:x.equals(y)返回值应该等于y.equals(x)的返回值。如果违反这样的约定,总是会出现问题。

当然,你可以在进行混合比较的时候,忽略颜色值:

@Override
	public boolean equals(Object arg0) {
		if (!(arg0 instanceof Point)) {
			return false;
		}

		//normal point 忽略颜色值,注意这里调用的是Point的equals的实现逻辑,而非Object的
		if (!(arg0 instanceof ColorPoint)) {
			return arg0.equals(this);
		}

		return super.equals(arg0)&&((ColorPoint1)arg0).color==this.color;
	}

这个能够满足对称性,但是却牺牲了传递性。

ColorPoint1 c1=new ColorPoint1 (1,2,"red");

Point p1=new Point(1,2);

ColorPoint1 c2=new ColorPoint1 (1,2,"blue");

c1.equals(p1)返回true,p1.equals(c1)返回true,但是c1.equals(c2)却返回false.

你当然可以在ColorPoint的equals的实现逻辑上,仍然沿用Point的机制——只比较他们的坐标值,但是这样的结果确实不能令人接受的。一个红颜色的点怎么能等于一个蓝颜色的点?

由此,你能够看到,一个矛盾的存在——在既想增加值组件,又不想失去抽象和多态特性的情况下,很难把握好子类的equals()方法的实现逻辑。

当然,在《Effective Java》中还是给出了,一个面对这种问题的权益之计:将继承转化为组合。

实现如下:

public class ColorPoint {

	private String color;
	private Point point;

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

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

}

这种实现,给出了对等对象的比较,也就是在同一类层次的比较,这样就不会出现子类和父类这样跨层次的比较,也就能避免失去对称性和传递性的问题。也就是说,有颜色的坐标点和没有颜色的坐标点始终是不等的。

当然,这里只是给出了面对这种问题的一种解决方案而已。在实际的编码过程中,我们需要去考虑采用继承还是组合,哪种实现更为合理。并且去考虑改变实现方式所付出的代价。

覆盖equals方法的几个规范:自反性、对称性、传递性、一致性。

高效的实现equals()的几个参考:

(1)使用==比较参数是否为当前对象的引用。

(2)使用instanceOf比较参数的类型是否正确。

(3)将类型转化为当前对象的类型

(4)比较所有关键的值域

(5)自我检测是否满足规范。

                                                                                                                           ——摘自《Effective
Java》

原文发布时间为:2011-08-19

本文作者:vinoYang

本文来自合作伙伴CSDN博客,了解相关信息可以关注CSDN博客。

时间: 2024-09-12 09:41:17

覆盖equals方法的权宜之计,组合优于继承的相关文章

Java中判断对象是否相等的equals()方法使用教程_java

Object类中的equals方法用于检测一个对象是否等于另一个对象.在Object类中,这个方法判断两个对象是否具有相同的引用,如果两个对象具有相同的引用,它们一定是相等的.从这点上看,将其作为默认操作也是合乎情理的.然而,对于多数类类说,这种判断并没有什么意义,例如,采用这种方式比较两个PrintStream是否相等就完全没有意义.然而,经常需要检测两个对象状态的相等性,如果两个对象的状态相等,就认为这两个对象是相等的.所以一般在自定义类中都要重写equals比较. 下面给出编写一个完美eq

问一个Object的equals方法的问题

问题描述 thinking in java上面的例子:class Value{ int i;}public class Equals{public static void main(String[] args){Value value1 = new Value();Value value2 = new Value();value1.i = 100;value2.i = 100;System.out.println(value1.equals(value2));}}输出是false.然后我覆盖了h

为什么我new了3个Teacher对象,也覆盖了hashcode跟equals方法,而TreeSet中只有一个对象

问题描述 publicclassTeacherimplementsComparable<Teacher>{privateStringname;privateintage;privatedoublesalary;privateDatebirth;publicTeacher(Stringname,intage,doublesalary,Datebirth){super();this.name=name;this.age=age;this.salary=salary;this.birth=birth

Java编程中的equals方法使用全解_java

通过下面的例子掌握equals的用法 package cn.galc.test; public class TestEquals { public static void main(String[] args) { /** * 这里使用构造方法Cat()在堆内存里面new出了两只猫, * 这两只猫的color,weight,height都是一样的, * 但c1和c2却永远不会相等,这是因为c1和c2分别为堆内存里面两只猫的引用对象, * 里面装着可以找到这两只猫的地址,但由于两只猫在堆内存里面存

java必学必会之equals方法_java

一.equals方法介绍 1.1.通过下面的例子掌握equals的用法 package cn.galc.test; public class TestEquals { public static void main(String[] args) { /** * 这里使用构造方法Cat()在堆内存里面new出了两只猫, * 这两只猫的color,weight,height都是一样的, * 但c1和c2却永远不会相等,这是因为c1和c2分别为堆内存里面两只猫的引用对象, * 里面装着可以找到这两只猫

如何编写高质量equals方法

什么是equals方法 指示其他某个对象是否与此对象相等,equals方法存在Object类中,我们编写的类继承Object,可以覆盖Object的equals方法来实现我们的逻辑,去判断两个对象是否相等. Object类中的equals方法 一起来看看Object类中的源代码 public boolean equals(Object obj) { return (this == obj); } 我们可以观察到几点: equals方法是public修饰的,外部类是可以访问的 equals方法的返

如何在Java中避免equals方法的隐藏陷阱

译者注 :你可能会觉得Java很简单,Object的equals实现也会非常简单,但是事实并不是你想象的这样,耐心的读完本文,你会发现你对Java了解的是如此的少.如果这篇文章是一份Java程序员的入职笔试,那么不知道有多少人会掉落到这样的陷阱中. 摘要 本文描述重载equals方法的技术,这种技术即使是具现类的子类增加了字段也能保证equal语义的正确性. 在<Effective Java>的第8项中,Josh Bloch描述了当继承类作为面向对象语言中的等价关系的基础问题,要保证派生类的e

对于java equals方法的疑惑。

问题描述 对于java equals方法的疑惑. String s1 = new String("123"); String s2 = new String("123"); System.out.println(s1.equals(s2)); Test t1=new Test("123"); Test t2=new Test("123"); System.out.println(t1.equals(t2)); 为何上面输出的是

java为什么要重写hashCode和equals方法

  如果不被重写(原生)的hashCode和equals是什么样的?       不被重写(原生)的hashCode值是根据内存地址换算出来的一个值.       不被重写(原生)的equals方法是严格判断一个对象是否相等的方法(object1 == object2).   为什么需要重写equals和hashCode方法?       在我们的业务系统中判断对象时有时候需要的不是一种严格意义上的相等,而是一种业务上的对象相等.在这种情况下,原生的equals方法就不能满足我们的需求了