Java的equals方法实现及其细节

  判断两个对象是否等价,是OOP编程中常见的需求(下面围绕Java来进行阐述)。编程

  考虑这样几种状况:经过某个特征值来判断两个对象是否“等价”,当这两个对象等价时,判断结果为true,不然结果为false。ide

  固然,这里的“特征值”不会只是简单的“对象引用”,事实上,Object类(Java的“对象世界”的根)中实现的equals方法,就是把“特征值”设定为“对象引用”来进行判断等价性的,所以能够得知,Object类中equals方法只是简简单单地返回this引用和被判断的obj的引用的“==运算”的值。测试

  可是不少状况下,并非要求两个对象只有引用相同时(此时两者为一个对象)才“断定为等价”,这就须要ADT设计者来界定两个实例对象判断等价的条件,即设定要比较的特征值。this

  举个例子,在某个社交软件中,要求每一个用户的用户名(name)必须独一无二,那么在每次增长新用户的时候,都要对该用户的注册名进行判断,若是当前用户名已经被占用,则没法为该用户建立帐号,只能要求该新用户从新选择设定用户名。spa

  这个时候,能够肯定,判断两个对象是否等价的特征值就是name(不妨把它设定为一个String类型的私有属性),这里对这个实现过程进行模拟。设计

  这里说明一下equals内代码的我的写法:对象

    第一步,先判断引用值是否相等,此时person1.equals(person1)这样的状况,就能够很快返回结果true。blog

    第二步,判断类型是否匹配,若是两个对象等价,前提是它们必定为相同的类型,此时person1.equals(null)这样的状况,也能进行判断并返回结果false。排序

    第三步,循序渐进地按照预设的特征值进行对象的等价性判断。开发

  运行结果:

  这里说明几点:

  1.类中的equals方法是必定要重写/覆盖(Override)的,由于要让它按照设计的需求来根据特征值判断等价性。

    这里的特征值,就是String类型的name属性,表示每一个Person对象的名字。因为在equals方法中只设定了这一个须要比较的特征值,所以只要两个Person类对象的name相同,那么他们的判断结果就是相同。

  2.类中的hashCode方法须要重写/覆盖

    事实上,当实现了1以后,就能保证判断两个对象等价性是否成立了(此时已经能保证程序中person1.equals(person2)值为true。可是这样获得的equals方法是有很大限定性的。好比把person1加入到一个HashSet中,此时判断HashSet中是否包含person2,因为在设计时,特征值只是name,那么此时指望HashSet.contains(person2)的值也应为true,但若是不实现hashCode方法,返回值只能是false。

    对于这个缘由,能够把Java中每一个实例对象的存储过程都想象成“将包含该对象的数据‘抛到’一个桶里”,为了更快地比价,就把整个程序运行时的空间,分红至关多的“桶”,并为每一个桶编号,对于桶内装载的数据,有这样的规定:为每一个实例对象进行编号,只有编号相同的两个对象,它们才有可能分配到一个桶里。这样一来,要想判断两个对象是否等价(便是否能让equals方法返回true),只须要访问这个桶就能够了,由于这两个对象必定是出如今相同的桶里的。步骤1已经实现了“找到两个对象以后,根据某个特征值进行判断”,可是并未实现“让两个对象分配到一个桶里”。这就是问题的关键所在。因此为了保证两个对象分配到相同的“桶”里,就要重写它们的hashCode方法,Java中为每种类型都默认实现了该类型的hashCode方法。下面的实现了hashCode的代码中,因为特征值是name,为了保证这两个Person类对象等价,那么它们的name必定相同,那考虑到name(Sting类型)已经实现了hashCode,此时就简单地把它们的name的hashCode值进行返回便可。这样就能保证,若是两个Person对象的name若是相同,那么它们的hashCode必定相同,同时也便于下一步判断。

    注:重点在于理解这个“桶”的概念,经过这个抽象过程,便也能够很好地理解“Java中两个等价的对象必定有相同的hashCode值,但两个拥有相同hashCode值的对象不必定等价”这句话。这句话的重点就在于考虑“桶”是如何装载的、以及它“装载”的是什么类型对象等等细节。

  这里给出未实现hashCode的Person类,并展现其测试代码: 

  测试结果:

  可见,未实现hashCode时,set.contains(person2)为false,即此时HashSet类型在检索person2时,发现它不在其装载对象(perosn1)所在的“桶”里,因而直接返回false。

  此时,从新实现代码:

  此时再次测试上述测试代码,测试结果:

  能够看到,尽管测试set未装载person2,但根据重写的equals断定等价性规则,person2也是被断定符合等价性的,所以在实现了hashCode后,便也能让持有对象按照设定的规则判断其等价性。

 

  固然,上述实现代码以及测试都是基于特征值为name来进行实现的,在现实生活中,好比“居民身份证”来讲,判断两个对象是否“等价”(便是否为同一我的),特征值天然就包括name(名字),sex(性别),age(年龄)等等属性,考虑到使用居民身份证的频繁使用以及普遍的应用场景,每一个居民就理所应当地拥有了一个额外的“属性”: 身份证号。这个独一无二的值,既实现了每一个对象的区别,又能很方便地进行排序(从而进行检索等操做)。

  因而可知,现实生活中到处体现着“ADT设计者的智慧”...

  Java为程序开发者提供了灵活的设定“特征值”的方法,所以在设计一种须要的数据类型时,能够仔细地思考一下两个对象判断等价的依据(特征值)到底是什么,这样实现的equals方法,每每给ADT的使用过程带来了极大的便利。

 

  from Steven Shen

  编辑于2018.6.19

    修改于2018.6.20

相关文章
相关标签/搜索