什么是规范表示'用于equals()方法的字段(Joshua Bloch)

时间:2014-06-26 10:01:36

标签: java equals

在第3章第8项:

public final class CaseInsensitiveString {
    private final String s;

    public CaseInsensitiveString(String s) {
        if (s == null)
            throw new NullPointerException();
        this.s = s;
    }

    @Override public boolean equals(Object o) {
        return o instanceof CaseInsensitiveString &&
            ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
    }
    // remainder omitted
}

在描述了equals()方法的问题后,他继续在比较字段的上下文中讨论这个类。

  

对于某些类,例如上面的CaseInsensitiveString,字段比较比简单的相等测试更复杂。如果是这种情况,您可能希望存储该字段的规范形式,因此equals()方法可以对这些规范形式进行廉价的精确比较,而不是更昂贵的不精确比较。这种技术最适合不可变的类;如果对象可以更改,则必须使规范形式保持最新。

所以我的问题(我仔细检查了'规范'意味着什么):Bloch在谈论什么?规范形式是什么?我已经准备好被告知答案很简单(大概是他的编辑会告诉他增加更多),但我想看到其他人这么说。

他还在下一个项目9中提到hashCode()的相同内容。

为了在上下文中给出它,他还讨论了equals()的{​​{1}}方法的错误版本:

CaseInsensitiveString

3 个答案:

答案 0 :(得分:6)

您应该为其添加另一个final字段并存储值s.toUpperCase()。 此新字段将是规范表示 s字段。方法equals()的新实现(参见下面的代码)将更便宜。这种方法仅适用于不可变类。

如果您覆盖hashCode(),则不应忘记覆盖equals()

public final class CaseInsensitiveString {

  private final String s;
  private final String sForEquals; //field added for simplifier equals method

  public CaseInsensitiveString(String s) {
      if (s == null) {
          throw new IllegalArgumentException(); //NullPointerException() - bad practice
      }
      this.s = s;
      this.sForEquals = s.toUpperCase();
  }

  @Override
  public boolean equals(Object o) {
      return o instanceof CaseInsensitiveString &&
          ((CaseInsensitiveString) o).sForEquals.equals(this.sForEquals);
  }

  @Override
  public int hashCode(){
      return sForEquals.hashCode();
  }
  // remainder omitted
}

答案 1 :(得分:2)

术语 canonical 有一些不同的用法。它指的是具有多个表示的值(或者可能是几个相等的变化值)。然后经常选择一个特定的表示(或值)作为规范表示。

示例:整数集:规范{2,3,5} = {3,5,2} = {2,2,5,3} = ....

对于普通的java String也存在问题。 Unicode中的相同文本可以用不同的方式表示:ĉ或者作为一个代码点"\u0109" SMALL-LETTER-C-WITH-CIRCUMFLEX,或者作为两个代码点c SMALL-LETTER-C和零宽度^ COMBINED-DIACRITICAL-MARK-CIRCUMFLEX("\u0063\u0302")。

因此,在某些情况下,即使是普通的字符串也应该规范化:

String s = "...";
String s1 = Normalizer.normalize(s, Normalizer.Form.NFKD);

这使用Normalizer来分解字符串。这样做的好处是,可以排序,“c”和“ĉ”保持在一起。可以使用正则表达式删除组合变音符号,并且将具有ASCII版本。

事实上,不同的操作系统处理Unicode名称的方式不同,并不总是版本控制系统遵循跨平台规范化。

仅在与Normalizer.normalize进行String.equals比较后才确实表示Unicode文本相等。

答案 2 :(得分:0)

您的问题分为两部分:

规范形式意味着"标准化形式 - 在这种情况下是字段的小写版本,用于比较。每次值更改时,都必须更新小写副本,因此这个设计选择的开销很大。此外,这个想法只是针对性能的优化,并且坦率地不推荐,因为它过早优化"

equals的非对称性允许代码a.equals(b)而不是b.equals(a),从而违反了equals合约。在您的示例中,String可能等于您的类的实例,因为它的equals()方法允许,但在字符串中实现equals() class不允许将您的类的实例视为等于String