探究HashSet如何判断元素相同

        下面程序提供了A、B、C三个类,他们分别重写了equals(),hashcode(),两个方法中的一个或全部,通过此程序我们能大概看到HashSet判断元素相同的标准 。

package shi;

import java.util.HashSet;

//类A重写了equals()方法,使equals()方法总是返回true,但是没有重写hashCode()方法
class A {
	
	public boolean equals(Object obj) {
		return true;
	}
}

//类B重写了hashCode()方法,使hashCode()方法总是返回1,但是没有重写equals()方法
class B {
	
	public int hashCode() {
		return 1;
	}
}

//类C重写了两个方法,hashCode()方法总是返回2,equals()方法方法总是返回true
class C {
	
	public boolean equals(Object obj) {
		return true;
	}
	public int hashCode() {
		return 2;
	}
	
}

public class Practice1 {

	public static void main(String[] args) {
		HashSet<Object> set = new HashSet<Object>();
		//向set中添加两个A对象,两个B对象,两个C对象
		set.add(new A());
		set.add(new A());
		set.add(new B());
		set.add(new B());
		set.add(new C());
		set.add(new C());
		System.out.println(set);
	}

}

程序输出结果:

[shi.A@647e05, shi.B@1, shi.B@1, shi.C@2, shi.A@10dea4e]             

        class C 重写了两个方法,hashCode()总是返回2,equals()总是返回true。所以HashSet将两个C对象当成了一个。即使两个对象通过equals()方法比较返回为true,但是HashSet依然当成两个对象,即使两个对象的hashCode()返回值相同,HashSet也当成两个对象。

        如果两个对象通过equals()比较返回true,但是hashCode()返回值不同时,这将导致HashSet会把这两个对象保存在Hash表的不同位置,从而使两个对象都可以添加成功,这就与Set集合的规则冲突了。

注意:在重写equals()和hashSet()方法时,如果两个对象通过equals()方法比较为true时,应该使他们的hashCode()返回相同的值。


<think>嗯,我现在要理解HashSet是如何判断元素是否重复的。记得以前学过一点Java的集合框架,HashSet好像是实现了Set接口,而Set的特点是不允许重复元素。那它是怎么做到的呢?应该和哈希表有关吧。 首先,HashSet内部是不是用了一个HashMap来存储元素?因为听说HashSet的add方法其实是把元素作为HashMap的键,而值则是一个固定的对象,比如PRESENT。这样的话,当添加元素时,其实是在调用HashMap的put方法,返回的是之前存在的值,如果返回null,说明这个键不存在,也就是新元素;否则就是已经存在,也就是重复了。 但是具体判断重复的逻辑可能不只是依赖哈希码。因为虽然哈希码用来确定元素在哈希表中的位置,但不同的对象可能有相同的哈希码,也就是哈希冲突。这时候应该还要用equals方法来比较对象是否真的相等。所以,HashSet判断元素是否重复应该是先比较哈希码,如果哈希码对应的位置没有元素,就直接插入;如果有的话,再逐个用equals方法检查是否相等。如果equals返回true,就认为是重复元素,不插入;否则,处理哈希冲突,可能放到链表或者红黑树里。 那这样的话,正确的判断重复的条件应该是两个对象的哈希码相同,并且equals方法返回true。所以,如果两个对象的哈希码不同,HashSet会认为它们不重复;即使哈希码相同,但equals返回false,还是会认为不重复,只是放在同一个哈希桶里。 不过,用户自定义的对象如果没有重写hashCode和equals方法的话,默认使用的是Object类的方法。Object的hashCode是根据内存地址生成的,而equals也是比较地址。这样的话,即使两个对象内容相同,但地址不同,会被认为是不同的对象,HashSet就不会认为重复。所以,为了让HashSet正确判断内容重复的对象,必须重写这两个方法。例如,String类已经重写了,所以两个内容相同的字符串会被视为重复,无法加入HashSet两次。 举个例子,假设有一个自定义的Person类,有name和age属性。如果没有重写hashCode和equals,那么即使两个Person实例的name和age相同HashSet也会认为它们不同。但如果重写了,根据name和age生成hashCode,并且equals比较这两个属性的话,HashSet就能正确识别重复。 所以总结一下,HashSet判断元素是否重复的步骤是: 1. 当调用add方法添加元素时,计算元素hashCode。 2. 根据哈希码找到对应的哈希桶(位置)。 3. 如果该位置没有元素,直接插入,不重复。 4. 如果有元素,则用equals方法逐个比较该位置的元素(可能是链表或树结构)。 5. 如果有任一比较结果为true,则元素重复,不插入;否则,添加到该桶中。 另外,好的哈希函数应该尽量减少冲突,这样能提高HashSet的性能,因为如果很多元素的哈希码相同,会导致链表过长,查找效率下降,Java 8之后当链表长度超过阈值时会转为红黑树,这样查询效率会好一些。 可能的误区是认为只要哈希码相同就会判定重复,实际上必须同时满足哈希码相同和equals返回true。反过来,如果两个对象equals返回true,它们的哈希码必须相同,这是Java中的约定,否则在使用哈希集合时会出现问题,比如无法正确识别重复元素。 所以,为了正确使用HashSet,存储的对象必须正确实现了hashCode和equals方法,确保相等的对象有相同的哈希码。</think>HashSet 判断元素是否重复的机制基于以下两个关键方法:`hashCode()` 和 `equals()`。具体步骤如下: --- ### **1. 哈希码(`hashCode()`)的作用** - **计算哈希值**:当向 HashSet 添加元素时,首先调用元素的 `hashCode()` 方法计算哈希值。 - **定位存储位置**:根据哈希值确定元素在内部哈希表中的存储位置(称为“桶”或“bucket”)。 --- ### **2. `equals()` 的作用** - **处理哈希冲突**:如果多个元素的哈希值相同(哈希冲突),HashSet 会在这个位置的链表中,通过 `equals()` 方法逐个比较元素内容。 - **判断实际相等性**:只有 `equals()` 返回 `true` 时,才会判定为重复元素,拒绝添加;否则视为不同元素,允许插入。 --- ### **关键规则** 1. **必须同时满足**: - 哈希值相同。 - `equals()` 返回 `true`。 2. **Java 规范要求**: - 如果两个对象 `equals()` 为 `true`,它们的 `hashCode()` **必须相同**。 - 如果 `hashCode()` 不同,即使 `equals()` 为 `true`,HashSet 仍会视为不同元素(因哈希值不同直接放入不同桶)。 --- ### **示例说明** ```java HashSet<String> set = new HashSet<>(); set.add("Apple"); set.add("Apple"); // 重复,不会被添加 // String 类已正确重写 hashCode() 和 equals(),因此能正确去重。 ``` --- ### **自定义对象的注意事项** - **必须重写 `hashCode()` 和 `equals()`**:否则默认使用 `Object` 类的方法(基于内存地址),导致内容相同的对象无法被识别为重复。 ```java class Person { String name; int age; @Override public int hashCode() { return Objects.hash(name, age); // 根据 name 和 age 生成哈希值 } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; return age == person.age && Objects.equals(name, person.name); } } // 此时,两个 name 和 age 相同的 Person 对象会被 HashSet 视为重复。 ``` --- ### **性能优化** - **减少哈希冲突**:设计良好的 `hashCode()` 方法能分散元素到不同桶中,提升查询效率。 - **Java 8+ 优化**:当链表长度超过阈值(默认8),哈希桶会转为红黑树,将查找时间从 O(n) 优化至 O(log n)。 --- ### **总结** HashSet 通过 `hashCode()` 快速定位元素位置,再通过 `equals()` 精确判断是否重复。确保自定义对象的这两个方法正确实现,是高效使用 HashSet 的关键。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值