使用 Java HashSet 时要注意的一些地方

本文探讨了HashSet类的特点及其在Java中的应用。重点介绍了如何通过重写hashCode和equals方法确保元素的唯一性,并讨论了修改对象状态可能导致的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HashSet<T>类主要是设计用来做高性能集运算的,例如对两个集合求交集、并集、差集等。集合中包含一组不重复出现且无特性顺序的元素。

HashSet<T>的一些特性如下:

1、HashSet<T>中的值不能重复且没有顺序。
2、HashSet<T>的容量会按需自动添加。

下面是使用 Java HashSet 时要注意的一些地方:

package com.swsx.hashCode;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * 使用HashMap要注意的一些问题
 * @author Administrator
 *
 */
public class TestHashSet {

	public static void main(String[] args){
		Person p1 = new Person("张三",21);
		Person p2 = new Person("李四",22);
		Person p3 = new Person("张三",21);
		
		Set<Person> persons = new HashSet<Person>();
		persons.add(p1);
		persons.add(p2);
		persons.add(p3);
		
		/*
		 * HashSet是按hashCode来存储元素的,如果两个元素的hashCode相同且equals为true,则只存一个元素,
		 * Person的hashCode和equals方法已重写,由于p1和p3的hashCode相同且equals,所以p3没有被存入。
		 * 如果不重写hashCode,而只重写equals方法,则会造成HashSet存储重复的元素。
		 * 
		 */
		System.out.println("HashSet中有:" + persons.size() + "个元素:");
		Iterator<Person> iterator = persons.iterator();
		while(iterator.hasNext()){
			System.out.println(iterator.next());
		}
		
		if (persons.contains(p1)) {
			System.out.println("HashSet中有元素"+ p1);
		}else{
			System.out.println("HashSet中没有元素"+ p1);
		}
		
		/*
		 * 当改变元素值时,hashCode也变了.
		 */
		System.out.println("\np1改变前的hashCode:" + p1.hashCode());
		p1.setName("小武");
		System.out.println("p1改变后的hashCode:" + p1.hashCode() + "\n");
		
		System.out.println("HashSet中有:" + persons.size() + "个元素:");
		iterator = persons.iterator();
		while(iterator.hasNext()){
			System.out.println(iterator.next());
		}
		
		/*
		 * HashSet是按元素的hashCode存取的,当改变元素值时,hashCode也变了,
		 * 造成查找元素时的hashCode和存储时的hashCode不一致,结果就找不到元素了。
		 * 当我们遍历集合时,要查找的元素确实存在,只不过仍是按原来元素的hashCode存储的,
		 * 但查找时却是按新的hashCode来找的。
		 */
		if (persons.contains(p1)) {
			System.out.println("HashSet中有元素"+ p1);
		}else{
			System.out.println("HashSet中没有元素"+ p1);
		}
		
		/*
		 * 改回原来的元素值时,hashCode也改回来了,这时查找时的hashCode和
		 * 存储时的hashCode一致,元素就找到了了。
		 */
		p1.setName("张三");
		System.out.println("\np1改回来后的hashCode:" + p1.hashCode());
		
		System.out.println("HashSet中有:" + persons.size() + "个元素:");
		iterator = persons.iterator();
		while(iterator.hasNext()){
			System.out.println(iterator.next());
		}
		if (persons.contains(p1)) {
			System.out.println("HashSet中有元素"+ p1);
		}else{
			System.out.println("HashSet中没有元素"+ p1);
		}

	}
}

/**
 * 用于测试HashSet的类
 * @author Administrator
 *
 */
class Person{
	
	private String name; 
	private int age;
	
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	
	/**
	 * 重写父类hashCode方法(hash算法不唯一)
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	
	/**
	 * 重写父类的equals方法
	 */
	@Override
	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 "Person [name=" + name + ", age=" + age + "]";
	}
}

/*输出如下:

HashSet中有:2个元素:
Person [name=张三, age=21]
Person [name=李四, age=22]
HashSet中有元素Person [name=张三, age=21]

p1改变前的hashCode:776501
p1改变后的hashCode:759683

HashSet中有:2个元素:
Person [name=小武, age=21]
Person [name=李四, age=22]
HashSet中没有元素Person [name=小武, age=21]

p1改回来后的hashCode:776501
HashSet中有:2个元素:
Person [name=张三, age=21]
Person [name=李四, age=22]
HashSet中有元素Person [name=张三, age=21]

*/



总结:

     1.如果一个类重写的equals方法,那么也最好重写hashCode方法,尤其是要使用与hash算法有关的集合(如HashSet、HashMap)存储对象时,这是最佳实践。

     2.当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法中使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄漏。

<think>好的,用户之前问过如何用Java的Stream根据用户ID列表筛选数据,我之前给出的方案是建议使用HashSet来提高查询效率。现在用户补充说他们对性能要求不高,所以不需要用HashSet。我需要调整之前的方案,改用List的contains方法,同确保正确性和代码简洁性。 首先,用户可能的应用场景是什么?可能是一个小规模的数据处理,比如数据量不大,或者是一次性的操作,不需要特别优化性能。这候用List.contains虽然间复杂度是O(n),但实际影响不大,代码更简单直接。 接下来要确认用户的真实需求。用户明确说不需要HashSet,可能他们更在意代码的简洁,或者对性能真的不敏感。也有可能用户对集合类型不太熟悉,想用更直观的方式实现。这候需要调整示例代码,去掉HashSet的部分,直接用List的contains。 需要注意的是,当targetIds列表较大使用List.contains会导致性能下降,但既然用户已经说明性能不重要,可以忽略这一点。另外,要确保在示例中正确使用stream的filter方法,直接调用targetIds.contains(user.getId())。 同,提醒用户可能的注意事项,比如如果ID列表有重复项,或者需要保持顺序的话,可能需要不同的处理,但根据问题描述,可能用户只需要基本的筛选功能。此外,并行流在这种情况下可能不会带来好处,因为List.contains本身是线性搜索,但用户可能不需要考虑这些。 最后,保持回答结构清晰,提供修改后的代码示例,并比较两种方法的差异,让用户了解取舍。确保代码正确,没有语法错误,并且解释清楚改动的地方和原因。</think>以下是基于`List.contains()`的简化实现方案,适合数据量较小或对性能要求不高的场景: ```java // 原始数据 List<User> allUsers = Arrays.asList( new User(1L, "张三"), new User(2L, "李四"), new User(3L, "王五") ); // 目标ID列表(直接使用List) List<Long> targetIds = Arrays.asList(1L, 3L); // 流处理直接使用List.contains() List<User> filteredUsers = allUsers.stream() .filter(user -> targetIds.contains(user.getId())) .collect(Collectors.toList()); ``` **两种实现方式对比**: | 特性 | 使用HashSet | 使用List.contains() | |--------------------|--------------------------|--------------------------| | 间复杂度 | O(1) 快速查找 | O(n) 线性遍历 | | 内存消耗 | 需额外存储哈希表结构 | 无额外内存消耗 | | 适用场景 | 大数据量/高频查询 | 小数据量/单次查询 | | 代码复杂度 | 需要类型转换 | 直接使用原始列表 | **完整示例**: ```java public class SimpleUserFilter { public static void main(String[] args) { List<User> users = Arrays.asList( new User(101L, "小明"), new User(102L, "小红"), new User(103L, "小刚") ); List<Long> idsToFind = Arrays.asList(102L, 104L); List<User> result = users.stream() .filter(u -> idsToFind.contains(u.getId())) .collect(Collectors.toList()); // 输出结果: [User{id=102, name='小红'}] System.out.println(result); } } ``` **注意事项**: 1. 当目标ID列表存在重复值,结果仍会保持原始数据中的出现次数 2. 如果使用`ArrayList`,`contains()`方法的间复杂度为$O(n)$,当目标ID列表较大性能会显著下降 3. 对于10,000个目标ID的情况,遍历10,000次`contains()`将产生$O(n^2)$的间复杂度 4. 可通过`.parallelStream()`获得有限加速(但效果不如HashSet方案明显): ```java List<User> result = allUsers.parallelStream() .filter(user -> targetIds.contains(user.getId())) .collect(Collectors.toList()); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值