2017-08-28-java-为什么要同时重写hasCode和equals
2017-09-29 09:31
363 查看
目录:
重写equals方法
重写hashCode方法
之前也是有点有点郁闷,为什么hashCode和equals要同时重写。
就是javadoc建议二者最好一起重写。
参考 https://www.oschina.net/question/82993_75533
上代码,一个类Worker,有id,name,age三个属性。
平时我们比较两个对象是否相等,都是通过equals方法进行比较,
比较的是它们在内存中的引用地址。
如果两个对象指向同一个地址,那么这两个对象就是相等的。
如下代码:
如上代码,我们平常一般是这么用的。
那么上面的w1和w2就应该是同一个员工,它们的equals方法就应该返回true。所以我们要对equals方法进行重写。
重写了equals之后,则下面的测试结果都为true。
以上代码来自HasmMap,
也就是,如果两个对象相等,那么他们的hashCode就一定要相等,不然这里这两个对象就会得到不同hash值从而存在不同的地方。
分割线插个小问题。
为什么要用到hashCode?
来自http://blog.csdn.net/fanfanjin/article/details/6881474
总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
所以,Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同
上面说的对象相同指的是用eqauls方法比较。
你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。
我们都知道HashSet相同的对象只会存在一次。
下面这个add方法来自HashSet。它里面利用了HashMap。那它如何判断两个对象是否相同?当然是通过依赖于上面Map中的hashCode的值。
那么现在如果我需要把所有的worker存放在一个set中。如下代码:
很明显,虽然重写了equals方法让w1与w2相等,但是由于没有重写hashCode()方法,导致存入了两个相等的对象。
所以,在这种情况下,我们就要重写hashCode方法。
怎么重写hashCode()方法?
获取hash值有多种算法,根据你的需要(或者说项目的大小,更具体就是你存在这个Set中的对象的数量)选择
重写的hashCode方法如下。这种算法就是简单线性处理。
我们可以使用Apache-commons-lang包下(common包可以认为是java第二大类库,自带的那些是第一大类库)提供的类来帮助重写equals方法和hashCode方法
总的来说,就是网上大家说的这几点:
尽量用同样的几个属性来生成hashCode和equals两个方法。
二者必须同时重写。
两个equals()返回true的对象,hashCode也要相同。
反之则不必,hashCode相同不一定要equals()=true
在ORM中,要使用get/set方法来获取时属性,因为有时候变量会被延迟加载。
例如上面的 使用id == id 可能就会出现这个问题,使用getId() == getId()就不会了。
重写equals方法
重写hashCode方法
之前也是有点有点郁闷,为什么hashCode和equals要同时重写。
就是javadoc建议二者最好一起重写。
参考 https://www.oschina.net/question/82993_75533
上代码,一个类Worker,有id,name,age三个属性。
public class Worker { private Integer id; private String name; private Integer age; public Integer getId() { return id; } public void setId(Integer id) { this.id = id } // 省略get/set方法 }
平时我们比较两个对象是否相等,都是通过equals方法进行比较,
比较的是它们在内存中的引用地址。
如果两个对象指向同一个地址,那么这两个对象就是相等的。
如下代码:
public class Test { public static void main(String[] args) { Worker w1 = new Worker(); Worker w2 = new Worker(); Worker w3 = w1; w1.setId(100); w2.setId(100); System.out.println(w1.equals(w2)); // false System.out.println(w1.equals(w3)); // true } }
如上代码,我们平常一般是这么用的。
重写equals方法
但是现在有个需求,如果员工的id相同,那么就是同一个员工。那么上面的w1和w2就应该是同一个员工,它们的equals方法就应该返回true。所以我们要对equals方法进行重写。
public class Worker { // 属性, get/set方法省略 @Override public boolean equals(Object obj) { if(!(obj instanceof Worker)) { return false; } if(this == obj) { // 引用相同 return true; } Integer objId = ((Worker)obj).getId(); if(null != objId && objId.equals(this.getId())) { return true; } else { return false; } } }
重写了equals之后,则下面的测试结果都为true。
public class Test { public static void main(String[] args) { Worker w1 = new Worker(); Worker w2 = new Worker(); Worker w3 = w1; w1.setId(100); w2.setId(100); System.out.println(w1.equals(w2)); // true System.out.println(w1.equals(w3)); // true } }
重写hashCode方法
现在又有了新的需求,我们需要把这些东西存在HashSet中,先看看以下代码。就过一眼就好了。// 放入方法, public V put(K key, V value) { if (key == null) return putForNullKey(value); int hash = hash(key.hashCode()); // 这一行 int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; } // 根据key的hashCode,用map自己的方法计算出一个hash值。 static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
以上代码来自HasmMap,
int hash = hash(key.hashCode());这一行我们可以知道,通过获取key的哈希值,然后通过hash方法计算出一个值,这个值作为比对的依据。
也就是,如果两个对象相等,那么他们的hashCode就一定要相等,不然这里这两个对象就会得到不同hash值从而存在不同的地方。
分割线插个小问题。
为什么要用到hashCode?
来自http://blog.csdn.net/fanfanjin/article/details/6881474
总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。你知道它们的区别吗?前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。如果详细讲解哈希算法,那需要更多的文章篇幅,我在这里就不介绍了。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。
这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
所以,Java对于eqauls方法和hashCode方法是这样规定的:
1、如果两个对象相同,那么它们的hashCode值一定要相同;
2、如果两个对象的hashCode相同,它们并不一定相同
上面说的对象相同指的是用eqauls方法比较。
你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。
我们都知道HashSet相同的对象只会存在一次。
下面这个add方法来自HashSet。它里面利用了HashMap。那它如何判断两个对象是否相同?当然是通过依赖于上面Map中的hashCode的值。
public boolean add(E e) { return map.put(e, PRESENT)==null; }
那么现在如果我需要把所有的worker存放在一个set中。如下代码:
public class Test { public static void main(Stirng[] args) { Worker w1 = new Worker(); Worker w2 = new Worker(); w1.setId(100); w2.setId(100); Set<Worker> workers = new HashSet<Worker>(); workers.add(w1); workers.add(w2); System.out.println(workers.size()); // 2 } }
很明显,虽然重写了equals方法让w1与w2相等,但是由于没有重写hashCode()方法,导致存入了两个相等的对象。
所以,在这种情况下,我们就要重写hashCode方法。
怎么重写hashCode()方法?
获取hash值有多种算法,根据你的需要(或者说项目的大小,更具体就是你存在这个Set中的对象的数量)选择
重写的hashCode方法如下。这种算法就是简单线性处理。
@Override public int hashCode() { final int PRIME = 31; int result = 1; result = PRIME * result + getId(); return result; }
我们可以使用Apache-commons-lang包下(common包可以认为是java第二大类库,自带的那些是第一大类库)提供的类来帮助重写equals方法和hashCode方法
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @Override public boolean equals(Object obj) { if(!(obj instanceof Worker)) { return false; } if(this == obj) { // 引用相同 return true; } Integer id = ((Worker) obj).getId(); return new EqualsBuilder().append(getId(), id).isEquals(); } @Override public int hashCode() { final int PRIME = 31; // initialNonZeroOddNumber, multiplierNonZeroOddNumber return new HashCodeBuilder(getId() % 2 == 0 ? getId() + 1 : getId(), PRIME).toHashCode(); }
总的来说,就是网上大家说的这几点:
尽量用同样的几个属性来生成hashCode和equals两个方法。
二者必须同时重写。
两个equals()返回true的对象,hashCode也要相同。
反之则不必,hashCode相同不一定要equals()=true
在ORM中,要使用get/set方法来获取时属性,因为有时候变量会被延迟加载。
例如上面的 使用id == id 可能就会出现这个问题,使用getId() == getId()就不会了。
相关文章推荐
- hascode和equals为什么要同时复写—Java源码中的奥秘
- Java基础之重写equals方法的同时为什么要重写hashCode方法
- Java里为什么hascode 和 equals方法 总是成对出现?
- (java面试)为什么重写equals的时候一般要重写hashcode
- 重写Equals为什么要同时重写GetHashCode
- JAVA中重写equals()方法的同时要重写hashcode()方法
- JAVA中重写equals()方法为什么要重写hashcode()方法?
- 为什么要重写java中对象的equals方法和hashCode方法以及如何重写
- java中的hasCode与equals理解
- Java 重写equals()时为什么要重写hashCode()方法
- JAVA中重写equals()方法为什么要重写hashcode()方法?
- JAVA中重写equals()方法的同时要重写hashcode()方法
- JAVA中重写equals()方法的同时要重写hashcode()方法
- JAVA中重写equals()方法为什么要重写hashcode()方法?
- (2) java中Object类源码 以及 equals hashcode ==三者区别联系 以及权限修饰符 以及重写和重载
- JAVA中重写equals()方法为什么要重写hashcode()方法?
- 为什么重写equals方法的同时也要重写hashcode方法?
- JAVA hashcode和equals为何要同时重写
- 重写equals和hasCode 方法 JDK1.7
- 重写equals方法的同时为什么要重写hashcode方法