覆盖equals时请遵守通用约定

覆盖equals时请遵守通用约定

       equals方法起来似乎很简单,但是许多覆盖方式会导致很多错误,在此就不多说"覆盖(重写)"的含义。
        在java中使用equals方法通常用来比较两个对象是否相等,如果你还在困扰equals和==的区别或者不知道为什么要覆盖equals方法,请先了解清楚再往下看。

什么时候不需要覆盖equals方法?

1.类的每个实例本质都是唯一的,例如Thread类,Object提供的equals方法对这些类来说正好适用。
2.不关系类是否提供了"逻辑相等"的测试功能。
3.超类已经覆盖了equals,从超类继承过来的行为对于子类也同样合适。
4.类是私有的或包是私有的,可以确定他的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.一致性:对于任何非null的引用值x,y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或一致地返回false.
5.对于非null的引用值x,x.equals(null)必须返回false.
看了上述概念觉得就像是数学公式一样枯燥,不如看看代码如何实现。- -
1.自反性(对象必须等同于自身)
 String s = "helloworld";  
 System.out.println(s.equals(s));  
运行结果:显而易见为true
2.对称性(任何两个对象对于 (他们是否相等吗?)的问题都必须保持一致)
public class CaseInsensitiveString {
	private String s;
	public CaseInsensitiveString(String s) {
		if (s == null)
			throw new NullPointerException();
		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;
	}
	public static void main(String[] args) {
		CaseInsensitiveString s1 = new CaseInsensitiveString("Helloworld");
		String s2 = "helloworld";
		System.out.println("s1 == s2 ? " + s1.equals(s2));
		System.out.println("s2 == s1 ? " + s2.equals(s1));
	}
}
运行结果:s1 == s2 ? true  s2 == s1 ? false
解释:通过equals方法判断忽略大小写的字符串是否相等,出现这样的结果是因为:我们在进行s1.equals(s2)的时候,是因为s1是CaseInsensitiveString类型的,它会执行到上面equals的代码,而s2是String类型的,s2.equals(s1)的比较自然是String中的equals方法(此方法并没有比较字符串相等的方法)
改进后:
public class CaseInsensitiveString {

	private String s;

	public CaseInsensitiveString(String s) {
		if (s == null)
			throw new NullPointerException();
		this.s = s;
	}
	@Override
	public boolean equals(Object obj) {
		return obj instanceof CaseInsensitiveString && ((CaseInsensitiveString) obj).s.equalsIgnoreCase(s);
	}

	public static void main(String[] args) {
		CaseInsensitiveString s1 = new CaseInsensitiveString("Ss");
		CaseInsensitiveString s3 = new CaseInsensitiveString("SS");
		System.out.println("s1 == s3 ? " + s1.equals(s3));
		System.out.println("s3 == s1 ? " + s3.equals(s1));
	}
}
运行结果:s1 == s3 ? true  s3 == s1 ? true
3.传递性
现在假设我们有一个类Point和一个Point的子类ColorPoint分别如下:
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 obj) {
		if (!(obj instanceof Point)) 
			return false;
		Point p = (Point) obj;
		return p.x == x && p.y == y;
	}
}

ColorPoint继承Point,不过比Point类多一个颜色属性。当我们把ColorPoint与Point对象的引用和Point与ColorPoint进行比较

import java.awt.Color;

public class ColorPoint extends Point {
	private final Color color;

	public ColorPoint(int x, int y, Color color) {
		super(x, y);
		this.color = color;
	}

	public static void main(String[] args) {
		Point p1 = new Point(1, 2);
		ColorPoint cp1 = new ColorPoint(1, 2, Color.BLACK);
		System.out.println("p1 == cp1 ? " + p1.equals(cp1));
		System.out.println("cp1 == p1 ? " + cp1.equals(p1));
	}
}
运行结果:p1 == cp1 ? true  cp1 == p1 ? true
为什么会遇到这样的情况?明明不同类型,执行结果为true。
仔细看过发现ColorPoint没有提供equals方法,直接继承了Point类,使用的当然是Point的equals方法,很明显颜色信息被忽略掉了。
改进ColorPoint类增加equals方法
import java.awt.Color;

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

	public static void main(String[] args) {
	
		ColorPoint p1 = new ColorPoint(1, 2, Color.BLACK);
		Point p2 = new Point(1, 2);
		ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
		System.out.println("p1 == p2 ? " + p1.equals(p2));
		System.out.println("p2 == p3 ? " + p2.equals(p3));
		System.out.println("p1 == p3 ? " + p1.equals(p3));
	}
}
运行结果:p1 == p2 ? true p2 == p3 ? true   p1 == p3 ? false
原因:因为第三种情况比较了颜色信息。
总结:我们无法在扩展可实例化的类的同时,既增加新的值得组件,又同时保留equals约定

改进:复合优先于继承的规则
public class ColorPoint{
    enum Color{
        RED,BLACK,WHITE
    }
    private final  Color color;
    private final Point point;
    public ColorPoint(int x, int y, Color color) {
        this.color = color;
        point = new Point(x, y);
    }

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

    public static void main(String[] args){
        ColorPoint p1 = new ColorPoint(1, 2 , Color.BLACK);
        Point p2 = new Point(1, 2);
        ColorPoint p3 = new ColorPoint(1, 2 , Color.WHITE);
        System.out.println("p1.equals(p2): " + p1.equals(p2));
        System.out.println("p2 equals(p1): " + p2.equals(p1));
        System.out.println("p2.equals(p3): " + p2.equals(p3));
        System.out.println("p1.equals(p3): " + p1.equals(p3));
    }
    
}
运行结果:p1.equals(p2): false  p2 equals(p1): false  p2.equals(p3): false  p1.equals(p3): false
一致性:如果两个对象相等,它们就必须始终相等,除非它们中有一个对象(或两个都)被修改了。
非空性:所有的对象都必须不等于null。因此,我们在覆盖equals方法时,必须先检查其正确类型。

覆盖equals方法的建议:

1.使用==操作符检查 ”参数是否为这个对象的引用“。

2.使用instanceof操作符检查 ”参数是否为正确的类型“。

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

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

5.当完成equals方法后,要检查是否满足四个特性,最好测试检查。

6.覆盖equals时总要覆盖hashCode

7.不要企图让equals方法过于智能

8.不要将equals声明中的Object对象替换为其他的类型。






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值