为什么存放在HashSet里面的对象,如果重写了equals()方法一定要写重写hashcode()方法,也就是说为什么要保证equals()方法比较相等的对象,其hashcode()方法返回值也要一样才可以。
首先,我给出一个例子大家看看,写一个Person类,只是覆盖了equals()方法。
class Person{
private String name;
private int age;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return this.name + " :" + this.age;
}
}
下面给出测试类的代码:
public class HashSetResearch {
public static void main(String[] args) {
Set<Person> s = new HashSet<Person>();
Person p1 = new Person(22,"zhongyao");
Person p2 = new Person(22,"zhongyao");
s.add(p1);
s.add(p2);
for(Person temp : s){
System.out.println(temp);
}
}
}
程序运行结果为:
zhongyao :22
zhongyao :22
在HashSet中,不可以装重复的对象,这个是大家都知道的,具体描述可以见jdk中的HashSet类的相关javadoc描述。
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
e==null ? e2==null : e.equals(e2), 这说明在HashSet里不能拥有和e相同的element的,相同的条件是同时为null或者equals()方法返回true。这时候你可能会问,那为什么上面的p2会被加入到s中呢?
在你调用HashSet的时候发生了很多事情,其中就有用到对象的hashcode()方法,我将把整个流程的调用过程详细列出。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
这个HashSet源代码中的add方法。
map是HashSet实例中的一个成员变量:
private transient HashMap<E,Object> map;
PRESENT也是一个成员变量,不过比较特别:
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
这个只是一个”傀儡值”,后面你会看到,在放入到map中是会作为value。
既然它调用了HashMap的put(K k, V v)方法,我们就去看看这个方法。这个代码不是很长,认真看还是很好懂的。为了方便,我还是把我的个人理解的注释放到代码之中了
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);//这个就是键值为空,不用多说了
//获取你放入的key对象的hashcode,看上面就知道这个key指向的就是你想要
//插入的对象,当然在HashMap里做了进一步的处理,事实上就是一些逻辑运算
//有兴趣的可以自己查看
int hash = hash(key.hashCode());
//这个i很有用处,从下面的代码可以看出它是定位你要插入的对象放入到table中的
//位置的,这个table是一个Entry类的数组,是个成员变量,后面会给大家看这个类的
//源代码。从indexFor()的源代码来看只有一行,简单的说就是i=hash&(talbe.length-1)
//有点hash函数的味道。
int i = indexFor(hash, table.length);
//这个for循环就是为你要插入的对象找到一个更精确的位置,可能在table中i的地方已经有
//人了,那么就要找下一个,如果大家有比较好的数据结构的功底的话应该比较容易理解,
//这里处理hash冲突的方法就是在表中具有相同hashcode那一项做链表处理(链地址法)
//这些仅仅从for语句的括弧中的三句就可以看的出来
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//以下的这个条件满足,你要插入的对象才不会被插入
//首先第一个就是hashcode,这个不难理解,记得运算是可逆的
//((k = e.key) == key这个的意思是你要插入的对象和当前遍历到的e指向同一个对
//像,当然不能被加入。后面这个key.equals(k)就不多说了。
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);// do nothing
//以上是存在相同时做出新值代替旧值
return oldValue;
}
}
modCount++;
//通过上面的for循环找到一个位置了,那么就可以在该方法里直接加如就可以了,这个
//就交给你们去查看了
addEntry(hash, key, value, i);
return null;
}
这个在所有的set中几乎是差不多的,大家可以自己看看。提一点,在TreeSet中,还额外要求compareTo()返回一样,即equals()返回true时,compareTo()要返回0
问题:
在加入到hashset之后,修改对象的状态和其它的一样,那么也是可以的,不会自动断裂,这个就是我想要了解了,这个破坏了set的唯一性。
hashCode
当使用toString方法的时候返回一个 "类型名@#$%#^%$ "的东西,比如一个****@4e57de。"@ "前面的是你的类名,后面的就是散列码的16进制表示。
hashCode 叫哈希代码或称散列码,简单的说就是通过哈希算法算出来的一大窜数字之类的东西和内存有关。默认的实现是将对象内部地址转化为整数作为HashCode,这当然能保证每个对象具有不同的HasCode,因为不同的对象内部地址肯定不同(废话)。因此你可以简单理解为对象在内存中的地址 担不是绝对物理地址。
hashCode()
Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。
那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?
当我们向HashSet集合中添加元素时,它是按hash算法来存储集合中的元素。首先 HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。如果两个元素通过equals方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同位置,也就添加成功。
简单的说,HashSet集合判断两个元素的标准是两个对象通过equals方法比较相等,并且两个对象的hashCode()方法返回值也相等。看下面例子:
import java.util.*;
//类A的equals方法总是返回true,但没有重写其hashCode()方法
class A{
public boolean equals(Object obj){
return true;
}
}
//类B的hashCode()方法总是返回1,但没有重写其equals()方法
class B{
public int hashCode(){
return 1;
}
}
//类C的hashCode()方法总是返回2,但没有重写其equals()方法
class C{
public int hashCode() {
return 2;
}
public boolean equals(Object obj) {
return true;
}
}
public class TestHashSet{
public static void main(String[] args){
HashSet books = new HashSet();
//分别向books集合中添加2个A对象,2个B对象,2个C对象
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);
}
}
运行上面的程序,看到如下运行结果:
[B@1,B@1,C@2,A@c17164, A@de6ced]
从上面的程序可以看出,即使两个A对象通过equals比较返回true,但HashSet依然把它们当成2个对象;即使2个B对象的hashCode()返回相同的值(都是1),HashSet依然会把它们当成2个对象。
总结:如果需要某个类的对象保存到HashSet集合中,重写这个类的equals()方法和hashCode()方法,应该尽量保证两个对象通过equals比较返回true时,它们的hashCode方法返回值也是相等.
为什么会先调hashCode()方法呢?
大家可以想想,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。
通过hashCode()方法返回的是hashcode码。这样一来,当集合要添加新的元素时,先调用这个元素的hashCode()方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。
分享到:
相关推荐
- 为了避免哈希冲突带来的性能问题,在覆盖`equals()`方法的同时,通常也需要覆盖`hashCode()`方法。 #### 实现示例 以下是一个简单的类`Person`,展示了如何正确覆盖`equals()`和`hashCode()`方法: ```java ...
在Java编程语言中,`hashCode()`和`equals()`方法是对象身份验证的关键组成部分,它们主要用于对象的比较和哈希表(如HashMap、HashSet等)的操作。理解这两个方法的工作原理对于编写高效和可靠的代码至关重要。 ...
在集合类(如`HashSet`)中,元素的存储依赖于`hashCode()`和`equals()`方法。如果两个元素的`hashCode()`相同,则需要通过`equals()`方法进一步确认是否为相同的元素。这在处理大量数据时可以显著提高效率。 #### ...
在Java编程语言中,反射(Reflection)是一种强大的...但需要注意的是,滥用反射可能导致安全问题和性能下降,因此在实际使用中需谨慎权衡。同样,正确实现和使用hashcode也是优化程序性能和保证数据结构正确性的关键。
在添加元素时,HashSet会调用对象的hashCode()方法生成哈希码,然后根据哈希码快速定位元素在HashMap中的位置。如果已有元素占据了这个位置,那么会调用equals()方法来判断新元素是否与已存在元素相等。如果equals()...
在Java编程语言中,集合框架提供了多种数据结构来存储和操作数据,其中`TreeMap`、`TreeSet`、`HashSet`以及`HashMap`是最常用的数据结构之一。这些集合类各自有着独特的特性和应用场景,下面将对它们进行详细介绍。...
在Java编程语言中,`equals()`, `hashCode()` 和 `toString()` 是三个非常重要的方法,它们主要用于对象的比较、哈希存储以及打印对象信息。这三个方法是Java对象的基础特性,对于理解和开发高质量的Java程序至关...
`hashCode()`方法则返回对象的一个整数值,这个值用于在基于哈希表的数据结构(如`HashSet`、`HashMap`等)中定位对象。哈希码的目的是快速定位对象,通过计算对象的哈希码,可以将对象映射到一个桶或者存储区域,...
在Java编程语言中,序列化(Serialization)和重写`hashCode`及`equals`方法是两个重要的概念,它们各自有着特定的用途,并且在某些情况下相互关联。下面将详细阐述这两个概念及其应用。 首先,Java序列化是将一个...
在Java编程中,`equals()` 和 `hashCode()` 方法是Object类中的两个重要方法,它们在处理对象相等性以及在哈希表(如HashSet、HashMap)中起到关键作用。当自定义类时,有时需要根据业务逻辑重写这两个方法以满足...
3. **避免负数**:虽然Java允许`hashCode()`返回负数,但通常建议返回非负整数,以避免在某些容器中遇到问题。 4. **性能**:尽管`hashCode()`的计算速度不是首要考虑因素,但过于复杂的计算可能会降低程序性能。...
在Java中,HashMap和HashSet都使用了开放寻址和链地址两种解决冲突的方法,具体取决于哈希码的分布和内部数组的大小。 总结来说,HashMap和HashSet的共同点在于它们都基于哈希表结构,通过计算对象的哈希码来决定...
在Java编程语言中,`hashCode()` 和 `equals()` 方法是两个非常重要的概念,尤其是在处理对象比较和哈希表(如 `HashMap` 或 `HashSet`)时。这两个方法来源于 `Object` 类,是所有Java类的基类,因此,每个自定义类...
在Java编程中,`hashCode()`和`equals()`方法是两个非常重要的概念,它们对于理解和使用Java中的集合框架(尤其是`HashSet`和`HashMap`)至关重要。本文将详细介绍这两个方法的基本原理及其在Java中的实现方式。 ##...
哈希表,又称散列表,是...总的来说,哈希表通过高效的散列函数和冲突解决策略实现了快速的数据存取,而hashCode()方法则是这一过程中的关键一环,它与equals()方法协同工作,确保了对象在散列集合中的正确定位和比较。
在Java编程语言中,`hashCode()`方法是每个对象都具备的一个关键组成部分,它与`equals()`方法一起工作,用于对象的比较和哈希表(如`HashMap`、`HashSet`等)的操作。本篇文章将深入探讨如何在Java中生成一个合适的...
在Java编程语言中,`equals()`和`hashCode()`方法是两个非常重要的概念,尤其是在处理对象比较和容器(如HashMap和HashSet)操作时。这两个方法在Java的类库中有着核心地位,尤其是对于类实例的比较和存储。接下来,...
在使用HashMap时,需要注意的是,如果自定义的键类没有重写equals()和hashCode()方法,可能会导致哈希冲突,影响性能。而HashSet在处理重复元素时,会依赖于对象的equals()方法来判断两个元素是否相等,因此,对于...
在`Set`接口中,特别是对于基于哈希表的实现(如`HashSet`),`hashCode`方法的作用尤为重要: - **定位元素**: 当向`HashSet`添加元素时,会首先调用该元素的`hashCode`方法,计算出其哈希码。哈希码被用来确定该...