Java周二生效! 遵守“平等”合约

今天,我们开始了新的篇章。 新的章节涵盖了所有对象通用的方法。 所有对象共有哪些方法? 既然每个对象最终都继承自宾语那将是该对象上的方法。 我们今天很高兴谈论的方法是等于方法。

等于方法似乎很容易覆盖,但实际上很容易使自己陷入困境,成为有效的Java细节。 那么为什么我们必须覆盖等于方法? 好吧,实际上我们不必这样做。 如果您的班级满足以下任何要求,则无需覆盖等于方法:

  • 一个类的每个实例本质上都是唯一的。线例如,让两个实例相等是没有意义的。如果不希望将两个类型的对象进行比较。 这确实有风险,因为如果其他人正在使用您的类,您可能无法确定他们是否愿意比较该类的两个实例。超类实现了等于适用于该类的方法。 这样的例子是等于在摘要清单 thus none needed在数组列表。If the class is private or package-private and you can be sure that 等于 will never be called on the class。If the class uses instance control。 This is something like the singleton model we talked about在a previous item or enums where there is only a single instance。 In these cases logical equality is the same as instance equality。

因此,我们已经过了很多时间,可以避免覆盖等于功能。 但是,如果我们确实需要覆盖此函数,该怎么办。 好吧,我们必须履行职能合同。 为了满足此函数的约定,我们必须满足非空对象的四个属性:

  • 反身:对象应等于自身(X.equals(X)=真)对称的:如果X.equals(y) = true, 然后y.equals(X) must equal true as well。(And if the former returns false, the latter should as well)传递性的:作为对称属性的扩展。 如果X.equals(y) = true和y.equals(z) = true然后X.equals(z)必须等于true。一致的:的多个调用X.equals(y)应该一致地返回相同的结果。 因此执行不应有任何副作用。最后一项(对于属性而言)较少,因为对于任何非nullX。X.equals(null)应该等于假。

当您初次阅读它们时,上述属性可能看起来很艰巨且非常数学化,应该认真对待。 话虽如此,一旦您了解它们,它就相当简单了。

反身-这第一个属性非常简单。 对象应该等于自己。 这通常很容易实现,尤其是在不覆盖等于函数,我们确实免费获得此属性。

对称-下一个属性更肉。 这个说,如果X说等于ÿ,ÿ should also saÿ it's equal to X. Let's look at an eXample of class that does not meet this propertÿ.

public final class CaseInsensitiveString {
  private final String value;

  public CaseInsensitiveString(String value) {
    this.value = Objects.requireNonNull(value);
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof CaseInsensitiveString) {
      return value.equalsIgnoreCase((CaseInsensitiveString)o);
    } else if (o instanceof String) {
      // This breaks symmetry.
      return value.equalsIgnoreCase(o);
    }
    return false;
  }
}

在上面的课程中,我们看到如果串被传递到CaseInsentive串为了检查是否相等,我们检查一个不区分大小写的字符串。 但是,如果将字符串与不区分大小写的字符串进行比较,则会考虑字符串的大小写。 因此,由于caseInsensitive串.equals(string) is not necessarily equals to string.equals(caseInsensitive串)。 那么我们将如何解决上述实现? 通过简化:

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

传递性的这是事情变得真正有趣的地方。 可传递属性表示,如果a。等于(b)和b。等于(c)那应该意味着等于(c)。 因此,让我们看一个如何破坏此属性的示例。 考虑以下类别:

public class Animal {
  private final int numberOfLegs;

  public Animal(int numberOfLegs) {
    this.numberOfLegs = numberOfLegs;
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Animal)) {
      return false;
    }

    return ((Animal)o).numberOfLegs == numberOfLegs;
  }
}

因此,正如您所看到的,我们有一个非常简单(且超级设计)的示例类来保存动物,显然,我们确定两只动物是否相同的方式就是它们的腿数相同。 现在让我们扩展这个类:

public class Dog extends Animal {
  private final String breed;

  public Dog(String breed, int numberOfLegs) {
    super(numberOfLegs);
    this.breed = breed;
  }
}

好的,所以我们在该类的基础上又添加了一个值动物已经有。 我们应该如何编写equals函数? 让我们看一个尝试:

@Override
public boolean equals(Object o) {
  if(!(o instanceof Dog)) {
    return false;
  }

  Dog dog = (Dog) o;

  return super.equals(dog) && breed.equals(dog.breed);
}

因此,我们毫不奇怪地断言,如果一条狗的腿长相同且品种相同,它就等于另一条狗。 看起来很简单,但是您看到我们可能破坏了什么财产吗? 我们实际上打破了对称性。 让我们看看如何。 考虑以下两个对象。

Animal animal = new Animal(4);
Dog pitbull = new Dog("Pitbull", 4);

所以如果我们有animal.equals(pitbull)它会回来真正但是,如果我们翻转并执行pitbull.equals(动物)它会回来假。 哎呀 也许,如果我们使equals函数变得更聪明,我们可以解决此问题。

@Override
public boolean equals(Object o) {
  if(!(o instanceof Animal)) {
    return false;
  }

  if(!(o instanceof Dog)) {
    return o.equals(this)
  }

  Dog dog = (Dog) o;

  return super.equals(dog) && breed.equals(dog.breed);
}

大! 我们解决了对称性问题,但是我们如何处理传递属性?

Dog pitbull = new Dog("Pitbull", 4);
Animal animal = new Animal(4);
Dog basset = new Dog("Basset", 4);

在这种情况下pitbull.equals(动物)等于真正和animal.equals(basset) equals 真正 but pitbull.equals(basset)不等于真正。 哦哦 那么该怎么办? 我们该如何解决? 好吧事实证明这个问题不是真的可以解决。 那就对了。 我们在这里看到的是面向对象语言中这些等价关系的基本问题。 解决该问题有时被建议的一种建议方法是简单地使用getClass()而不是实例 checks. This has the effect of only allowing equivalence if the implementing classes are of the same type. This however does violate the Liskov Substitution Principle和breaks some concepts of object-oriented design. The Liskov Substitution Principle simply states that an object of a subtype type should be able to replace any existence of one of it's parent's types. The method that 有效的Java pitches is to favor composition over inheritance which is a tip later in the book. The high level synopsis of this technique is, instead of inheriting the type, you simple hold an instance of that type allowing better control of how the different pieces of data can be used和then we don't get into trouble with the Liskov substitution principle.

一致性:这使我们保持一致。 这基本上可以归结为不依赖不可靠的资源。 因此,请勿将随机数生成器用作平等检查的一部分。

非零:这最后一个很简单。 如果有人通过空值到等于功能,只需返回假而且不要扔一个空指针异常。

最后,让我们看一些高质量的步骤等于根据有效Java实现。

  • 使用==操作员检查对象是否相同。 这是一个不错的性能优化。使用实例运算符,以确保为您提供了正确类型的对象,并且还要处理非零需求。将对象转换为正确的类型。对于该类的每个“重要”字段,请检查是否相等。 对于原始外部浮动和双(您应该使用浮点比较和双。compare()分别)检查是否与==。 对于引用类型,请使用递归等于()电话。 为了避免NullPointerExceptions,请考虑使用对象等于进行这些比较。 其他需要考虑的事情是,您是否可以将较便宜的字段与较昂贵的字段进行比较。始终覆盖hashCode如果您覆盖等于。别太聪明Make sure you are meeting the override correctly。 It's public boolean 等于(Object o)不public boolean 等于(MyType o)。 This is one of the reason using the @Override annotation is useful。

哇,这比您最初考虑的要多得多,这绝对是我的工作。 值得吗? 好吧,一旦你违反了等于合同,不知道其他对象在处理类中的对象时将如何操作。 在集合中使用您的类时,这一点尤其明显。

那有一个简单的按钮吗? 有一种。 如前所述,Lombok是Java开发和摆脱样板的好工具。 龙目岛有一个很棒的注释@EqualsAndHashCode。 我强烈建议您使用它,而不要自己编写。 IDE通常还内置有可帮助生成这些方法的工具。 所有这些自动生成功能都很棒,但是我仍然认为了解什么才是好的平等函数至关重要,而且它可以帮助您成为更好的开发人员并更全面地了解魔术的产生方式。

那么您对等于方法? 有恐怖故事吗? 有奇怪的错误吗? 让我们在评论中知道。

from: https://dev.to//kylec32/effective-java-tuesday-obey-the-equals-contract-4df4

发布了0 篇原创文章 · 获赞 0 · 访问量 676

猜你喜欢

转载自blog.csdn.net/cunxiedian8614/article/details/105690880