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