Java中HashSet是否应被允许添加自身作为元素?
Java HashSet允许添加自身作为元素的深层原因
你提到的这个现象确实很有意思——按照Java集合框架的官方契约,Set是明确不允许包含自身作为元素的,但用HashSet<Object>却能轻松做到,就像你给出的示例代码:
Set<Object> mySet = new HashSet<>(); mySet.add(mySet); assertThat(mySet.size(), equalTo(1));
这个断言居然能通过,完全不符合我们对Set契约的预期(要么添加失败返回false,要么抛出异常)。
要搞懂这个问题,得从HashSet的底层实现说起:HashSet本质上是基于HashMap实现的,添加元素时,会把元素作为HashMap的key,value则是一个固定的空对象(PRESENT)。
那为什么添加自身能成功?核心原因有两点:
- 哈希值计算:HashMap判断key是否重复,第一步是计算哈希值。对于对象来说,默认的
hashCode()返回的是和内存地址相关的唯一值,所以当把mySet自身作为key时,这个哈希值是唯一的,HashMap会认为这是一个全新的key。 - 重复检查逻辑:当第一次调用
add(mySet)时,HashSet内部的HashMap是空的,根本不会触发重复元素的检查(因为没有已有的key可以对比),直接就把这个key存进去了。如果之后再调用一次add(mySet),这时候HashMap会用equals()方法对比已有的key和待添加的key,因为是同一个对象,equals()返回true,所以第二次添加会失败,size还是1。
那为什么Set契约规定不能包含自身?其实这是规范层面的约定,目的是避免递归操作(比如遍历、序列化)时出现无限循环的问题。但HashSet并没有在代码层面强制执行这个约定——它只遵守了Set关于“不允许重复元素”的核心规则,却没有额外检查元素是否是集合自身。
简单来说,这是HashSet基于HashMap实现的特性导致的“契约漏洞”,虽然符合代码的执行逻辑,但确实违反了Set的官方约定。
内容的提问来源于stack exchange,提问作者davidmerrick
相关产品推荐
相关产品推荐

