`

覆盖Object的equals方法时准守的通用约定

    博客分类:
  • java
阅读更多

尽管Object是一个具体类,但是设计它主要是为了扩展。它所有的非final方法(equals、hashCode、toString、clone和finalize)都有明确的通用约定,因为它们被设计成是要被覆盖的。

任何一个类,它在覆盖这些方法的时候,都有责任遵守这些通用约定;如果不能做到这一点,其他依赖于这些约定的类(例如:HashMap和HashSet)就无法结合该类一起正常工作。

 

有一种"值类"不需要覆盖equals方法,即实例受控确保"每个值至多只存在一个对象"的类。枚举类型就属于这种类。

对于这样的类而言,逻辑相同与对象等同是一回事,因此Object的equals方法等同于逻辑意义上的equals方法。

 

如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们需要覆盖equals方法。

 

这通常属于“值类(value class)”的情形。值类仅仅是一个表示值的类,例如Integer或者Date。

程序员在利用equals方法来比较对象的引用时,希望知道它们在逻辑上是否相等,而不是想了解它们是否指向同一个对象。

为了满足程序员的要求,不仅必须重写equals方法,而且这样做也使得这个类的实例可以被用作映射表的键,或者集合的元素,使映射和集合表现出预期的行为。

 

 

什么时候应该覆盖Object.equals

如果类具有自己特有的”逻辑相等“概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为。

 

在覆盖equals方法的时候,你必须要遵守它的通用约定(自反性、对称性、传递性、一致性)。

为什么一定要遵守通用约定,因为其他的程序设计都认为你遵循了通用约定,例如其他程序认为:x.equals(y)跟y.equals(x)是一样的。

如果你没有遵循,其他的程序代码就会有问题。

list.contains(s);

 

在覆盖equals方法的时候,必须遵守它的通用约定。

equals方法实现了等价关系

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

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

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

一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true/false。

非空性:对于任何非null的引用值x,x.equals(null)必须返回false。(抛异常也不行)

 

对称性违反的例子

package com.ez.impl;
/**
 * 违反对称性
 */
public class CaseIgnoreString {
    private final String s;
    
    public CaseIgnoreString(String s){
        if(s==null)
            throw new NullPointerException();
        this.s=s;
    }
    
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof CaseIgnoreString){
            return s.equalsIgnoreCase(((CaseIgnoreString)obj).s);
        }else if(obj instanceof String){
            return s.equalsIgnoreCase((String)obj);
        }
        return false;
    }
    
    public static void main(String[] args) {
        CaseIgnoreString cs=new CaseIgnoreString("EZbcw");
        String str="ezbcw";
        System.out.println(cs.equals(str));
        System.out.println(str.equals(cs));
    }
}

 

假如你把不区分大小写的字符串对象放到一个集合中,此时list.contains(s)会返回什么结果呢?没人知道,在Sun的当前实现中,它碰巧返回false。

public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

 

一旦违反了equals约定,当其他对象面对你的对象时,你完全不知道这些对象的行为会怎么样。

 

传递性违反的例子

package com.ez.impl;
/**
 * 长方形
 */
public class Oblong {
    private final int width;
    private final int length;
    
    public Oblong(int width,int length){
        this.width=width;
        this.length=length;
    }
    
    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof Oblong)){
            return false;
        }
        Oblong oblong=(Oblong)obj;
        return oblong.width==width&&oblong.length==length;
    }
}

 

/**
 * 长方体
 */
public class Cuboid extends Oblong{

    private int height;
    
    public Cuboid(int width,int length,int height){
        super(width, length);
        this.height=height;
    }
    
    @Override
    public boolean equals(Object obj) {
        if(!(obj instanceof Oblong)){
            return false;   //不是长方形直接返回false
        }
        else if(!(obj instanceof Cuboid)){
            return obj.equals(this);    //长方形比较长和宽
        }
        return super.equals(obj)&&((Cuboid)obj).height==height; //长方体比较长宽高
    }
    
}

 

 

在Java平台类库中,有一些类扩展了可实例化的类,并添加了新的值组件。

例如,java.sql.Timestamp对java.util.Date进行了扩展,并增加了nanoseconds域。

Timestamp的equals实现确实违反了对称性,如果Timestamp和Date对象被用于一个集合中,或者以其他方式被混合在一起,则会引起不正确的行为。

Timestamp类有一个免责声明,告诫程序员不要混合使用Date和Timestamp对象。

 

覆盖equals时总要覆盖hashCode

 

如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。

 

如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定产生不同的整数结果。

 

例子:通过List的removeAll方法,重写类的equals和hashcode方法,来实现

package com.ez.test.mytests;

import java.util.ArrayList;
import java.util.List;

/**
 * 逻辑上认为User的id相同,就认为是同一个人,因为一个用户可以取多个名字,但是id是唯一的。
 * 重写equals为了list去重-apps.removeAll(apps1),重写equals必须同时重写hashcode。
 * 使用Eclipse自动生成的hashCode
 */
public class User 
{
	private int id;
	private String name;
    
	public static void main( String[] args )
    {
        List<User> apps=new ArrayList<User>();
        User app=new User();
        app.setId(1);
        app.setName("张山");
        User app1=new User();
        app1.setId(2);
        app1.setName("李四");
        User app2=new User();
        app2.setId(3);
        app2.setName("王五");
        apps.add(app);
        apps.add(app1);
        apps.add(app2);
        
        List<User> apps1=new ArrayList<User>();
        User app3=new User();
        app3.setId(1);
        app3.setName("孙六");
        User app4=new User();
        app4.setId(2);
        app4.setName("陈七");
        apps1.add(app3);
        apps1.add(app4);
        
        apps.removeAll(apps1);
        System.out.println("apps size="+apps.size()+", id="+apps.get(0).getId()+", name="+apps.get(0).getName());
    }
    
    
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof User)) {
			return false;
		}
		User other = (User) obj;
		if (id != other.id) {
			return false;
		}
		return true;
	}
    
}

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics