如何获取或实现基于对象引用而非哈希码的C# ReferenceSet
刚好这个需求我之前也碰到过,其实不用自己从头造轮子,JDK本身就有现成工具能满足,自己实现也很简单,下面给你拆解几种方案:
一、JDK自带的快速方案:利用IdentityHashMap
JDK里的java.util.IdentityHashMap是专门为引用相等判断设计的Map实现——它判断键是否相等用的是==而非equals(),也不会调用对象的hashCode(),而是直接基于对象内存地址计算哈希值。
要把它当成Set用非常简单,直接取它的keySet()即可:
Set<Object> referenceSet = new IdentityHashMap<>().keySet();
这个keySet完全符合你的需求:添加元素时,只有集合里没有同一个引用的对象才会成功,查询、删除操作也都是基于引用判断的。
二、自己实现一个完整的IdentityHashSet
如果想要一个更“正统”的Set类(而非依赖Map的keySet),可以自己包装IdentityHashMap实现Set接口,逻辑很直白:把元素作为Map的键,值用一个固定占位符(比如Boolean.TRUE)占位即可:
import java.util.*; public class IdentityHashSet<E> implements Set<E> { private final IdentityHashMap<E, Boolean> backingMap = new IdentityHashMap<>(); @Override public int size() { return backingMap.size(); } @Override public boolean isEmpty() { return backingMap.isEmpty(); } @Override public boolean contains(Object o) { return backingMap.containsKey(o); } @Override public boolean add(E e) { return backingMap.put(e, Boolean.TRUE) == null; } @Override public boolean remove(Object o) { return backingMap.remove(o) != null; } @Override public void clear() { backingMap.clear(); } // 剩余Set接口方法直接委托给backingMap的keySet即可 @Override public Iterator<E> iterator() { return backingMap.keySet().iterator(); } @Override public Object[] toArray() { return backingMap.keySet().toArray(); } @Override public <T> T[] toArray(T[] a) { return backingMap.keySet().toArray(a); } @Override public boolean containsAll(Collection<?> c) { return backingMap.keySet().containsAll(c); } @Override public boolean addAll(Collection<? extends E> c) { return backingMap.keySet().addAll(c); } @Override public boolean retainAll(Collection<?> c) { return backingMap.keySet().retainAll(c); } @Override public boolean removeAll(Collection<?> c) { return backingMap.keySet().removeAll(c); } }
这个实现的外部表现完全是标准Set,所有相等性判断都基于对象引用,内部依赖IdentityHashMap处理核心逻辑,不用自己手写哈希和相等判断的复杂代码。
三、第三方库参考(无需引入也能借鉴思路)
如果你想看看成熟的实现,比如Guava库中的Sets.newIdentityHashSet(),它的核心逻辑和上面我们自己写的完全一致——就是用IdentityHashMap作为底层存储,包装成Set对外提供接口,源码思路非常清晰。
总结一下:优先用JDK自带的IdentityHashMap.keySet(),简单高效;如果需要独立的Set类,自己包装一个的代码量也很小;第三方库的实现也都是基于这个思路,没有什么复杂的黑魔法。
内容的提问来源于stack exchange,提问作者Anonymous

