到底该怎么写好equals函数?
2013-11-26 15:55
253 查看
equals函数,顾名思义,用来比较两个对象是否相等。
往本质想,这是物质唯一性问题。
那么在这之前,我么你需要定义一下,什么叫“相等”?如下是一个equals方法的几个通用的基本性质,可以作为定义的参考:
自反性, x.equals(x) == true 恒成立
对称性,若 x.equals(y)==true ,则 y.equals(x)==true
传递性,若 x.equals(y)==true && y.equals(z)==true,则 x.equals(z)==true
一致性,若 x.equals(y)==true,则 任意次数的调用equals结果都应为true
非空性,若 x!=null ,则 x.equals(null) == false
大家都同意的情况下,我们继续。
有的人写equals方法,连instanceof和向上转型(转换为Person类)都没有,这显然是错误的的。假设:有一个Person类和另一个Dog类,如果他们各自的对象的的name和age都一样,在this.name和this.age判断地方能够通过,可是我们就认为是一样的对象么?!显然很可笑。狗和人怎么能一样呢?
可是有了instanceof判断就能保证equals方法的正确性了么?不一定,我们继续说上面的Person类。在继承出现之前,instanceof判断的equals是OK的。如果出现了继承呢?
假设现在有个Woman类和Man类都继承于Person类。其中一个m对象和另一个w对象的名字和年龄都一样,在上述Person的equals方法通过instanceof判断,然后我们就认为他们是相等的?显然很可笑吧!所以说equals方法的instanceof判别法限制了系统的可扩展性。那我们在子类中覆盖equals方法可以么?下面会说到这个。
那怎么办呢?用getClass()方法判断他们的类型是否一样就行了!嗯,于是男人和女人的对比又OK了。如果两个男人年龄和姓名都一样,那么他们是相等的。
上述代码中,只需把上述obj instanceof Person修改为getClass()==obj.getClass()即可。
大概描述为:如果S是T的子类,那么任意T的对象都可以用S的对象来置换。
这么说好抽象,给大家说个例子:有个AbstractSet,而HashSet和TreeSet都继承自AbstractSet类,现在有两个对象h和t(h和t在语义上可以互相置换),他们中的元素分别都相等,可是在上述的equals方法中判断getClass()的时候,这两个的runtime class显然不一样,故而判断他们集合不相等?!看来这个getClass()也不是万能的!这时候我们又要请出instanceof判别方法了,把getClass()==obj.getClass()换成obj instanceof AbstractSet即可。
到底是什么导致有的地方要用getClass()有的地方要用instanceof呢?
如果没有统一的equals语义,例如:Man类和Woman类的equals语义不一样,即在Man里面的equals方法和Woman的equals方法的语义很有可能不一样,那么我们的父类里就需要用getClass()来判别。当然,我们也可以在子类中重写equals方法,在equals方法里面继续用instanceof判别,可是这样又会带来另一个问题。
s.equals(p),只要p不是Student类型(例如这个p是Teacher类型),那么就false。满足预期想法。
t.equals(p),只要p不是Teacher类型(例如这个p是Student类型),那么就false。满足预期想法。
p.equals(s),只要s不是Person类型,那么就false,可事实上s是Student类型,而Student继承了Person,故而s是Person类型,故而结果为true。满足预期。
p.equals(t),只要t不是Person类型,那么就false,可事实上t是Teacher类型,而Teacher继承了Person,故而t是Person类型,故而结果为true。满足预期。
矛盾来了,p.equals(t)恒为true,而t.equals(p)呢?不一定。只有当p为Teacher类型,其结果才为true。这就违背了“对称性”。
如果对称性成立,更为要命的是:因为p.equals(s)为true,则由对称性,s.equals(p)为true,又因为p.equals(t)恒为true,则根据传递性,s.equals(t),老师和学生的equals语义一样了!这和我们设计的初衷不一样,我们设计的初衷是语义不同,所以我们才用instanceof来设计。
所以,如果语义不同,还是建议在父类中用getClass()来作为判别方式。如果语义相同,建议用instanceof判别法。
显式参数命名为otherObject,稍后需要将它转换为另一个叫做other的变量
检测this与otherObject是否引用同一个对象。if (this == otherObject ) return true;
检测otherObject是否为null,如果为null,则返回false。if ( otherObject == null ) return false;
比较this与otherObject是否属于同一个类。(吐槽一下,OSchina的编辑器连缩进都没有)
a) 如果父类的子类的equals方法有相同的语义,则使用instanceof判别法。 if( otherObject instanceof ParentClassName ) return false;
b) 如果父类的子类的equals方法有不同的语义,则使用getClass()判别法。 if( getClass()!=otherObject.getClass() ) return false;
把otherObject转换为乡音的类类型变量。ParentClass other = (ParentClass) otherObject;
使用==比较primitive域,使用equals比较对象域。 return field1==other.field1 && field2 == other.field2 && .... ;
如果在子类中重新定义了equals,就要在其中先调用super.equals(other)来比较是否相等。
参考文献:
《Core Java Vol. 1. 8th 》
Wiki置换原则: http://en.wikipedia.org/wiki/Liskov_substitution_principle
往本质想,这是物质唯一性问题。
那么在这之前,我么你需要定义一下,什么叫“相等”?如下是一个equals方法的几个通用的基本性质,可以作为定义的参考:
自反性, x.equals(x) == true 恒成立
对称性,若 x.equals(y)==true ,则 y.equals(x)==true
传递性,若 x.equals(y)==true && y.equals(z)==true,则 x.equals(z)==true
一致性,若 x.equals(y)==true,则 任意次数的调用equals结果都应为true
非空性,若 x!=null ,则 x.equals(null) == false
大家都同意的情况下,我们继续。
到底该不该用instanceof来判断?
这里有种比较方法: http://tieba.baidu.com/p/2235863036 也是很多人常用的比较方法,不想点链接的我在这里给大家打出来:public class Person { …… public boolean equals(Object obj) { if( this==obj ) return true; if( obj!=null ) { if( obj instanceof Person ) { Person p = (Person)obj; if( this.name.equals( p.name ) && this.age == p.age ) { return true; } } } return false; } …… }
有的人写equals方法,连instanceof和向上转型(转换为Person类)都没有,这显然是错误的的。假设:有一个Person类和另一个Dog类,如果他们各自的对象的的name和age都一样,在this.name和this.age判断地方能够通过,可是我们就认为是一样的对象么?!显然很可笑。狗和人怎么能一样呢?
可是有了instanceof判断就能保证equals方法的正确性了么?不一定,我们继续说上面的Person类。在继承出现之前,instanceof判断的equals是OK的。如果出现了继承呢?
假设现在有个Woman类和Man类都继承于Person类。其中一个m对象和另一个w对象的名字和年龄都一样,在上述Person的equals方法通过instanceof判断,然后我们就认为他们是相等的?显然很可笑吧!所以说equals方法的instanceof判别法限制了系统的可扩展性。那我们在子类中覆盖equals方法可以么?下面会说到这个。
那怎么办呢?用getClass()方法判断他们的类型是否一样就行了!嗯,于是男人和女人的对比又OK了。如果两个男人年龄和姓名都一样,那么他们是相等的。
上述代码中,只需把上述obj instanceof Person修改为getClass()==obj.getClass()即可。
到底该不该用getClass()来判断?
我们加入了getClass() 很多人都觉得满意了,这时候有个科学家又跳出来了,他说:这不符合OO的置换原则。这里是置换原则的链接: http://en.wikipedia.org/wiki/Liskov_substitution_principle大概描述为:如果S是T的子类,那么任意T的对象都可以用S的对象来置换。
这么说好抽象,给大家说个例子:有个AbstractSet,而HashSet和TreeSet都继承自AbstractSet类,现在有两个对象h和t(h和t在语义上可以互相置换),他们中的元素分别都相等,可是在上述的equals方法中判断getClass()的时候,这两个的runtime class显然不一样,故而判断他们集合不相等?!看来这个getClass()也不是万能的!这时候我们又要请出instanceof判别方法了,把getClass()==obj.getClass()换成obj instanceof AbstractSet即可。
到底是什么导致有的地方要用getClass()有的地方要用instanceof呢?
所有的子类是否有统一的equals语义?
如果有统一的equals语义,例如:HashSet和TreeSet的equals语义就是,只要元素相等,那么集合就想等。则需要使用instanceof判别法,这样符合“替换原则”。如果没有统一的equals语义,例如:Man类和Woman类的equals语义不一样,即在Man里面的equals方法和Woman的equals方法的语义很有可能不一样,那么我们的父类里就需要用getClass()来判别。当然,我们也可以在子类中重写equals方法,在equals方法里面继续用instanceof判别,可是这样又会带来另一个问题。
子类中重写equals方法?
这样导致的问题就是equals方法的设计不符合equals方法的对称性。假如现在有两个类: Person、Student和Teacher,分别有对象p、s和t,而且在Student和Teacher里面重写了方法,判别方式为instanceof。那么s.equals(p),只要p不是Student类型(例如这个p是Teacher类型),那么就false。满足预期想法。
t.equals(p),只要p不是Teacher类型(例如这个p是Student类型),那么就false。满足预期想法。
p.equals(s),只要s不是Person类型,那么就false,可事实上s是Student类型,而Student继承了Person,故而s是Person类型,故而结果为true。满足预期。
p.equals(t),只要t不是Person类型,那么就false,可事实上t是Teacher类型,而Teacher继承了Person,故而t是Person类型,故而结果为true。满足预期。
矛盾来了,p.equals(t)恒为true,而t.equals(p)呢?不一定。只有当p为Teacher类型,其结果才为true。这就违背了“对称性”。
如果对称性成立,更为要命的是:因为p.equals(s)为true,则由对称性,s.equals(p)为true,又因为p.equals(t)恒为true,则根据传递性,s.equals(t),老师和学生的equals语义一样了!这和我们设计的初衷不一样,我们设计的初衷是语义不同,所以我们才用instanceof来设计。
所以,如果语义不同,还是建议在父类中用getClass()来作为判别方式。如果语义相同,建议用instanceof判别法。
equals方法的通用判别流程
下面给出一个较好的通用判别流程:显式参数命名为otherObject,稍后需要将它转换为另一个叫做other的变量
检测this与otherObject是否引用同一个对象。if (this == otherObject ) return true;
检测otherObject是否为null,如果为null,则返回false。if ( otherObject == null ) return false;
比较this与otherObject是否属于同一个类。(吐槽一下,OSchina的编辑器连缩进都没有)
a) 如果父类的子类的equals方法有相同的语义,则使用instanceof判别法。 if( otherObject instanceof ParentClassName ) return false;
b) 如果父类的子类的equals方法有不同的语义,则使用getClass()判别法。 if( getClass()!=otherObject.getClass() ) return false;
把otherObject转换为乡音的类类型变量。ParentClass other = (ParentClass) otherObject;
使用==比较primitive域,使用equals比较对象域。 return field1==other.field1 && field2 == other.field2 && .... ;
如果在子类中重新定义了equals,就要在其中先调用super.equals(other)来比较是否相等。
下面是一个例子:
import java.util.*; /** * This program demonstrates the equals method. * @version 1.11 2004-02-21 * @author Cay Horstmann */ public class EqualsTest { public static void main(String[] args) { Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15); Employee alice2 = alice1; Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15); Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1); System.out.println("alice1 == alice2: " + (alice1 == alice2)); System.out.println("alice1 == alice3: " + (alice1 == alice3)); System.out.println("alice1.equals(alice3): " + alice1.equals(alice3)); System.out.println("alice1.equals(bob): " + alice1.equals(bob)); System.out.println("bob.toString(): " + bob); Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15); Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000); System.out.println("boss.toString(): " + boss); System.out.println("carl.equals(boss): " + carl.equals(boss)); System.out.println("alice1.hashCode(): " + alice1.hashCode()); System.out.println("alice3.hashCode(): " + alice3.hashCode()); System.out.println("bob.hashCode(): " + bob.hashCode()); System.out.println("carl.hashCode(): " + carl.hashCode()); } } class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public void raiseSalary(double byPercent) { double raise = salary * byPercent / 100; salary += raise; } public boolean equals(Object otherObject) { // a quick test to see if the objects are identical if (this == otherObject) return true; // must return false if the explicit parameter is null if (otherObject == null) return false; // if the classes don't match, they can't be equal if (getClass() != otherObject.getClass()) return false; // now we know otherObject is a non-null Employee Employee other = (Employee) otherObject; // test whether the fields have identical values return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay); } public int hashCode() { return 7 * name.hashCode() + 11 * new Double(salary).hashCode() + 13 * hireDay.hashCode(); } public String toString() { return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]"; } private String name; private double salary; private Date hireDay; } class Manager extends Employee { public Manager(String n, double s, int year, int month, int day) { super(n, s, year, month, day); bonus = 0; } public double getSalary() { double baseSalary = super.getSalary(); return baseSalary + bonus; } public void setBonus(double b) { bonus = b; } public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; Manager other = (Manager) otherObject; // super.equals checked that this and other belong to the same class return bonus == other.bonus; } public int hashCode() { return super.hashCode() + 17 * new Double(bonus).hashCode(); } public String toString() { return super.toString() + "[bonus=" + bonus + "]"; } private double bonus; }
参考文献:
《Core Java Vol. 1. 8th 》
Wiki置换原则: http://en.wikipedia.org/wiki/Liskov_substitution_principle
相关文章推荐
- csdn 到底怎么了?不准转载?
- [转]十五天精通WCF——第七天 Close和Abort到底该怎么用才对得起观众
- 资深HR告诉你到底怎么写一份好的简历(非常全面)
- 搞IT的到底怎么了
- 回呀回呀文呀文,素数呀素数。。。。到底怎么回文呀??(泪奔ing)
- ARC是什么东东?retain和release到底怎么玩?(★firecat推荐★)
- 2006-4-23 我到底该怎么办?
- 十五天精通WCF——第七天 Close和Abort到底该怎么用才对得起观众
- JavaScript 社区由一个库引发的“smoosh门”事件到底怎么回事?
- 到底怎么理解反射???
- 程序员到底怎么了
- 轮胎扎了“钉子”该怎么办?到底要不要拔掉?
- web前端到底怎么学?干货资料!
- 电到底是怎么工作的?
- 来讲讲Git这个玩意到底该怎么用?
- 库存扣多了,到底怎么整
- 2007-01-15 到底应该怎么办
- 怎么理解面向对象和面向过程到底的本质区别?
- 十五天精通WCF——第七天 Close和Abort到底该怎么用才对得起观众
- 小白被QT5虐了,请问到底怎么发布EXE程序?