JAVA学习DAY14:Set接口与HashSet

6 阅读3分钟

今天顺延学习了Set接口和HashSet,理解了Set与List的核心区别,验证了HashSet元素不重复和遍历无序的特性。但在用自定义Product对象测试时,发现了一个反直觉的现象:两个属性完全相同的Product对象居然都能存进HashSet。这个悬念留到了今天结束,等待明天解开。


一、Set接口

Set是Collection接口下的另一个重要分支,它与List最大的不同体现在两个方面。第一,Set中的元素不能重复,任何重复的元素都会被自动过滤掉,这个特性在需要去重的场景中非常有用。第二,Set不保证元素的顺序,遍历出来的顺序和插入的顺序可能完全不同,也不会按大小排序。List是有序可重复的,Set是无序不重复的,这个根本区别决定了它们各自适用的场景。


二、HashSet

HashSet是Set接口最常用的实现类,它的底层实际上是一个HashMap。HashMap在存储键值对时,键不能重复,HashSet正是利用了这个特性,只使用键来存储元素,值用一个统一的占位对象填充。因此HashSet具备了HashMap的核心特点:查询、插入、删除的效率都很高,平均时间复杂度接近O(1)。

今天通过HashSetDemo验证了它的两个核心特性。首先是去重:向HashSet中多次添加同一个字符串或同一个整数,最终集合中只保留一份,重复添加被自动忽略。其次是无序:按照一定顺序插入元素,遍历输出时顺序完全被打乱,没有规律可循。这种无序是由底层哈希表的存储机制决定的,元素根据哈希值散列到不同的桶中,遍历时就按桶的顺序输出。


三、自定义对象的去重"意外"

今天最困惑的环节是ProductSetTest。创建了两个Product对象,它们的name、price、origin、stock属性完全相同,按理说应该代表"同一个商品",但把它们都add进HashSet后,发现两个对象都成功存进去了,Set的大小是2而不是1。

这个现象一开始让人很意外,因为HashSet不是会自动去重吗?后来仔细一想才明白问题所在:HashSet判断元素是否重复,依赖的是元素的hashCode方法和equals方法。而自定义的Product类目前并没有重写这两个方法,它使用的是从Object继承来的默认实现。Object默认的hashCode返回的是对象的内存地址,equals比较的是对象的内存地址。两个Product对象即使属性完全相同,它们在内存中是两个不同的对象,地址不同,所以HashSet认为它们是不同的元素,自然都能存进去。

这个发现引出了一个新的问题:怎样才能让HashSet根据对象的内容而不是地址来判断是否重复?答案应该是重写hashCode和equals方法,但具体怎么重写、重写后HashSet内部又是如何工作的,老师说悬念留到明天。今天先把这个疑问记下来,期待明天的解答。