【问题标题】:Overriding hashCode() method覆盖 hashCode() 方法
【发布时间】:2018-03-27 18:33:08
【问题描述】:

Joshua Bloch 谈到有效的 Java:

您必须在每个覆盖 equals() 的类中覆盖 hashCode()。 否则将导致违反总合同 对于 Object.hashCode(),这将阻止你的类运行 与所有基于哈希的集合正确结合,包括 HashMap、HashSet、Hashtable。

我重写的equals() 方法实现了用于比较Match 对象的模糊评分算法:

public class Match {

    private String homeTeam;

    private String awayTeam;

    public Match(String homeTeam, String awayTeam) {
        this.homeTeam = formatTeamName(homeTeam);
        this.awayTeam = formatTeamName(awayTeam);
    }
}

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Match that = (Match) o;

        final int threshold = 6;

        return (computeFuzzyScore(this.homeTeam, that.awayTeam) <= threshold || computeFuzzyScore(this.awayTeam, that.homeTeam) <= threshold) &&
                    computeFuzzyScore(this.homeTeam, that.homeTeam) > threshold && computeFuzzyScore(this.awayTeam, that.awayTeam) > threshold;

    }

    // formatTeamName(), computeFuzzyScore() have been left out for brevity.
}

这样这些对象就相等了:

Match match0 = new Match("Man.City", "Atl.Madryt");
Match match1 = new Match("Manchester City", "Atlético Madryt");

我应该如何覆盖hashCode() 方法来为这些对象生成相同的值?

【问题讨论】:

  • match0match 不一样,不是吗。所以他们最好拥有相同的hashCode()。你的equals() 也不应该返回true
  • 您应该在哈希码文档中发现哈希码对于等效实例应该是相同的,但可能是不同的。这意味着哈希码应该只使用equals中使用的变量。
  • 你确定这个equals方法是传递的吗?
  • 您的 equals 实现不是可传递的,但它需要符合 Object.equals 的约定。也就是说,如果 A 等于 B,B 等于 C,那么 A 最等于 C。如果前两个几乎不匹配模糊等式,而 A-C 比较不匹配,会发生什么情况?这就是为什么你很难想出一个好的 hashCode;你的平等被打破了。你应该使 equals 严格,并想出另一种模糊比较的方法,有点像 String.equals vs equalsIgnoringCase。
  • 是的。但说实话,你误用了equals。添加新方法。

标签: java oop


【解决方案1】:

重要的部分是如果match1 == match2 那么match1.hashCode() == match2.hashCode()。假设您有一个数据库,您可以在其中存储带有 id(数字)的匹配项,然后您可以让 hashCode 返回 id 并完成。

不使用数据库来跟踪匹配的 id,您可以完成 为每个团队分配一个固定长度的数字 id,并将两个 id 作为hashCode 的结果连接起来。

例如,"Manchester City" 团队可以是团队 1"Atlético Madryt" 团队 2。如果哈希是 32 位长,您可以让前 16 位为团队 1,最后 16 位为团队 2,如本表示所示。

// 16 bit for team 1 + 16 bit for team 2
0000000000000001        0000000000000010

这是一个有效的 32 位整数,将匹配 match1 == match2 然后 match1.hashCode() == match2.hashCode() 的规则

【讨论】:

  • 为每个实例添加id 会起作用,但是Map 会为每个实例创建一个存储桶(基本上因为这使用哈希码来订购它的存储桶)。
【解决方案2】:

正如 M. le Rutte 和 AxelH 的回答所说,equals 应该只对相同的对象返回 true(应该可以随时在代码中切换并在代码中呈现相同的结果,无论使用哪个)。

解决此问题的一种方法是使用包装类,如Remove duplicates from a list of objects based on property in Java 8 的回答中所述。 你这样做是为了让包装类只存储计算出的模糊值并比较和使用等号和哈希码中的值,然后你可以使用 unwrapp 来获取真实值。

另一种方法是像 yshavit 所说的那样做另一个类似于 String:equalsIgnoreCase 的 equal

【讨论】:

  • 我把Match.equals()的方法严格了,想出了另一个方法Match.isSimilarTo()进行模糊比较。
【解决方案3】:

我建议您使用其他方法名称,例如 fuzzyEquals 而不是 equals。您应该查看hashCodeequals 的用法。许多Java 类,例如HashMap,在未经您同意的情况下调用这两种方法,并要求它们严格遵守自己的想法。他们的想法不是你想要的,而是他们需要的。是这样的:

  • equals = homeTeam.equals && awayTeam.equals
  • hashCode = homeTeam.hashCode ^ awayTeam.hashCode

通过重命名,您 (a) 让 HashMap 和他的朋友们开心,(b) 避免混淆,(c) 提高可读性以及 (d) 为两种不同的事物提供两种不同的方法,您可以独立使用或进一步组合使用它们。

【讨论】:

    【解决方案4】:

    为了完整起见,您应该查看数据模型。这是暂时让你失望的那个

    • Match 介于两个 Team 之间。
    • Team 有一个name
    • 一个Teamalias (0..n)。

    你会有这样的想法:

    public Team{
    
        private final String name;
        private List<String> alias;
    
        public Team(Sting name){ ... }
    
        public boolean equals(Object obj){
            // check name
        }
    
        public int hashCode(){
            // hash the name
        }
    }
    

    然后,只需查看Match 即可以相同的方式使用此类。

    Team 类可以提供一种方法来检查与String 匹配的任何别名,如果在alias 中不匹配/找不到,它将使用您的算法检查name。如果此 alias 匹配,则将其添加到 List 以供将来研究。

    这样,您无需每次都运行模糊算法。如果您希望用户获得与他选择的任何输入匹配的Team,这可能会很有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-09
      • 1970-01-01
      • 1970-01-01
      • 2020-02-09
      • 1970-01-01
      • 2020-06-06
      • 2017-03-04
      • 1970-01-01
      相关资源
      最近更新 更多