基于哈希码比较含集合与可选类型的对象时的优化方案及排序忽略问题
看起来你已经有了一个可行的思路,但确实有不少可以优化的地方,咱们一步步来拆解你的问题:
问题1:有没有更优的实现方式?
当然有!你现在通过新建一个SomeRequest对象再取哈希码的方式,有点绕路,而且如果后续类里加了新字段、构造函数发生变化,这个方法很容易出错。推荐直接用成熟的工具类来计算指定字段的哈希值,不用额外创建对象,代码更清晰也更高效。
比如用Google Guava的Objects.hashCode():
private int getParameterHash(SomeRequest req) { // 只传入你关心的字段,直接计算哈希 return Objects.hashCode( req.longs, req.name, req.opLongs, req.fields ); }
或者用Apache Commons Lang的HashCodeBuilder,它的好处是可以自定义初始值和乘数,能降低哈希碰撞的概率:
private int getParameterHash(SomeRequest req) { return new HashCodeBuilder(17, 37) .append(req.longs) .append(req.name) .append(req.opLongs) .append(req.fields) .toHashCode(); }
另外要提一句:Optional的哈希码处理是没问题的——它的哈希值完全基于内部包裹的值,空Optional的哈希码是固定的,这部分你不用额外操心。
问题2:如何让Collection字段忽略顺序参与哈希计算?
要忽略Collection的顺序,核心是把它转换成不关心元素顺序的数据结构再参与哈希计算,具体分两种场景:
场景1:你的Collection里没有重复元素
这种情况最简单,直接把它转成HashSet就行——HashSet本身不关心元素顺序,哈希值只和元素本身有关:
private int getParameterHash(SomeRequest req) { // 先处理null情况,避免空指针 Set<Field> fieldSet = req.fields == null ? null : new HashSet<>(req.fields); return Objects.hashCode( req.longs, req.name, req.opLongs, fieldSet ); }
如果你的Field类实现了Comparable接口,也可以用TreeSet,效果是一样的——TreeSet会排序元素,但Set的哈希是所有元素哈希的总和,顺序不影响最终结果。
场景2:你的Collection里有重复元素,需要保留重复次数但忽略顺序
这时候HashSet就不行了——它会自动去重,丢失重复元素的信息。推荐用Guava的Multiset(多重集合),它可以统计每个元素出现的次数,哈希计算时会考虑元素和对应的出现次数,但完全忽略顺序:
private int getParameterHash(SomeRequest req) { Multiset<Field> fieldMultiset = req.fields == null ? null : HashMultiset.create(req.fields); return Objects.hashCode( req.longs, req.name, req.opLongs, fieldMultiset ); }
额外提醒:哈希碰撞的风险
哈希码相等不代表两个对象一定相等(虽然概率很低),如果你的业务场景对准确性要求极高,建议在哈希码相等后,再做一次全字段的equals校验——校验逻辑要和哈希计算逻辑保持一致:比如比较longs、name、opLongs,以及把fields转成Set/Multiset后再比较。
备注:内容来源于stack exchange,提问作者S.Dan

