在程序设计中,有不少的“公约”,遵照约定去实现你的代码,会让你避开不少坑,这些公约是前人总结出来的设计规范。java
Object类是Java中的万类之祖,其中,equals和hashCode是2个很是重要的方法。程序员
这2个方法老是被人放在一块儿讨论。最近在看集合框架,为了打基础,就决定把一些细枝末节清理掉。一次性搞清楚!算法
下面开始剖析。框架
Object类中默认的实现方式是 : return this == obj 。那就是说,只有this 和 obj引用同一个对象,才会返回true。函数
Object 的equals方法this
public boolean equals(Object obj) { return (this == obj); }
而咱们每每须要用equals来判断 2个对象是否等价,而非验证他们的惟一性。这样咱们在实现本身的类时,就要重写equals.设计
注: 重写equals的缘由:是验证对象是否等价,而非验证他们的惟一性。code
按照约定,equals要知足如下规则。对象
自反性: x.equals(x) 必定是true递归
对null: x.equals(null) 必定是false
对称性: x.equals(y) 和 y.equals(x)结果一致
传递性: a 和 b equals , b 和 c equals,那么 a 和 c也必定equals。
一致性: 在某个运行时期间,2个对象的状态的改变不会不影响equals的决策结果,那么,在这个运行时期间,不管调用多少次equals,都返回相同的结果。
一个例子
class Test { private int num; private String data; public boolean equals(Object obj) { if (this == obj) return true; if ((obj == null) || (obj.getClass() != this.getClass())) return false; //能执行到这里,说明obj和this同类且非null。 Test test = (Test) obj; //data == test.data 这里为了说明String常量池的存在。 return num == test.num&& (data == test.data || (data != null && data.equals(test.data))); } public int hashCode() { //重写equals,也必须重写hashCode。具体后面介绍。 } }
Test类对象有2个字段,num和data,这2个字段表明了对象的状态,他们也用在equals方法中做为评判的依据。
在第8行,传入的比较对象的引用和this作比较,这样作是为了 save time ,节约执行时间,若是this 和 obj是 对同一个堆对象的引用,那么,他们必定是qeuals 的。
接着,判断obj是否是为null,若是为null,必定不equals,由于既然当前对象this能调用equals方法,那么它必定不是null,非null 和 null固然不等价。
而后,比较2个对象的运行时类,是否为同一个类。不是同一个类,则不equals。getClass返回的是 this 和obj的运行时类的引用。若是他们属于同一个类,则返回的是同一个运行时类的引用。注意,一个类也是一个对象。
一、有些程序员使用下面的第二种写法替代第一种比较运行时类的写法。应该避免这样作。
if((obj == null) || (obj.getClass() != this.getClass())) return false; if(!(obj instanceof Test)) return false; // avoid 避免!
它违反了公约中的对称原则。
它违反了公约中的对称原则。
例如:假设Dog扩展了Aminal类。
dog instanceof Animal 获得true
animal instanceof Dog 获得false
这就会致使
animal.equls(dog) 返回true dog.equals(animal) 返回false
仅当Test类没有子类的时候,这样作才能保证是正确的。
二、按照第一种方法实现,那么equals只能比较同一个类的对象,不一样类对象永远是false。但这并非强制要求的。通常咱们也不多须要在不一样的类之间使用equals。
三、在具体比较对象的字段的时候,对于基本值类型的字段,直接用 == 来比较(注意浮点数的比较,这是一个坑)对于引用类型的字段,你能够调用他们的equals,固然,你也须要处理字段为null 的状况。对于浮点数的比较,我在看Arrays.binarySearch的源代码时,发现了以下对于浮点数的比较的技巧:
if ( Double.doubleToLongBits(d1) == Double.doubleToLongBits(d2) ) //d1 和 d2 是double类型 if( Float.floatToIntBits(f1) == Float.floatToIntBits(f2) ) //f1 和 f2 是d2是float类型
四、并不老是要将对象的全部字段来做为equals 的评判依据,那取决于你的业务要求。好比你要作一个家电功率统计系统,若是2个家电的功率同样,那就有足够的依据认为这2个家电对象等价了,至少在你这个业务逻辑背景下是等价的,并不关心他们的价钱啊,品牌啊,大小等其余参数。
五、最后须要注意的是,equals 方法的参数类型是Object,不要写错!
这个方法返回对象的散列码,返回值是int类型的散列码。 对象的散列码是为了更好的支持基于哈希机制的Java集合类,例如 Hashtable, HashMap, HashSet 等。
关于hashCode方法,一致的约定是: 重写了euqls方法的对象必须同时重写hashCode()方法。
若是2个对象经过equals调用后返回是true,那么这个2个对象的hashCode方法也必须返回一样的int型散列码
若是2个对象经过equals返回false,他们的hashCode返回的值容许相同。(然而,程序员必须意识到,hashCode返回独一无二的散列码,会让存储这个对象的hashtables更好地工做。)
在上面的例子中,Test类对象有2个字段,num和data,这2个字段表明了对象的状态,他们也用在equals方法中做为评判的依据。那么, 在hashCode方法中,这2个字段也要参与hash值的运算,做为hash运算的中间参数。这点很关键,这是为了遵照:2个对象equals,那么 hashCode必定相同规则。
也是说,参与equals函数的字段,也必须都参与hashCode 的计算。
合乎情理的是:同一个类中的不一样对象返回不一样的散列码。典型的方式就是根据对象的地址来转换为此对象的散列码,可是这种方式对于Java来讲并非惟一的要求的 的实现方式。一般也不是最好的实现方式。
相比 于 equals公认实现约定,hashCode的公约要求是很容易理解的。有2个重点是hashCode方法必须遵照的。约定的第3点,其实就是第2点的 细化,下面咱们就来看看对hashCode方法的一致约定要求。
第一:在某个运行时期间,只要对象的(字段的)变化不会影响equals方法的决策结果,那么,在这个期间,不管调用多少次hashCode,都必须返回同一个散列码。
第二:经过equals调用返回true 的2个对象的hashCode必定同样。
第三:经过equasl返回false 的2个对象的散列码不须要不一样,也就是他们的hashCode方法的返回值容许出现相同的状况。
总结一句话:等价的(调用equals返回true)对象必须产生相同的散列码。不等价的对象,不要求产生的散列码不相同。
在编写hashCode时,你须要考虑的是,最终的hash是个int值,而不能溢出。不一样的对象的hash码应该尽可能不一样,避免hash冲突。
那么若是作到呢?下面是解决方案。
接下来让你认为重要的字段(equals中衡量相等的字段)参入散列运,算每个重要字段都会产生一个hash份量,为最终的hash值作出贡献(影响)
最后把全部的份量都总和起来,注意并非简单的相加。选择一个倍乘的数字31,参与计算。而后不断地递归计算,直到全部的字段都参与了。
int hash = 7; hash = 31 * hash + 字段1贡献份量; hash = 31 * hash + 字段2贡献份量; ..... return hash;
首先须要明确 equals 与hashcode的关系
一、若是两个对象相同(即用equals比较返回true),那么它们的hashCode值必定要相同;
二、若是两个对象的hashCode相同,它们并不必定相同(即用equals比较返回false)
自个人理解:
因为为了提升程序的效率才实现了hashcode方法,先进行hashcode的比较,若是不一样,那没就没必要在进行equals的比较了,这样就大大减小了equals比较的次数,这对比须要比较的数量很大的效率提升是很明显的,一个很好的例子就是在集合中的使用;
咱们都知道java中的List集合是有序的,所以是能够重复的,而set集合是无序的,所以是不能重复的,那么怎么能保证不能被放入重复的元素呢,但靠equals方法同样比较的话,若是原来集合中之后又10000个元素了,那么放入10001个元素,难道要将前面的全部元素都进行比较,看看是否有重复,欧码噶的,这个效率可想而知,所以hashcode就应遇而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,那么在后面定义进来的数据只要看对应的hashcode地址上是否有值,那么就用equals比较,若是没有则直接插入,只要就大大减小了equals的使用次数,执行效率就大大提升了。
继续上面的话题,为何必需要重写hashcode方法,其实简单的说就是为了保证同一个对象,保证在equals相同的状况下hashcode值一定相同,若是重写了equals而未重写hashcode方法,可能就会出现两个没有关系的对象equals相同的(由于equal都是根据对象的特征进行重写的),但hashcode确实不相同的。