Skip to content

@EqualsAndHashCode

YellowStar5 edited this page Aug 8, 2019 · 2 revisions

@EqualsAndHashCode

让相等变得简单: 从你对象的字段中生成 hashCode 和 equals 的实现

任何类定义都可以用 @EqualsAndHashCode 注释,让lombok生成 equals(Object other)hashCode() 方法的实现。默认情况下,它将使用所有非静态,非瞬时的字段,但你可以通过使用 @EqualsAndHashCode.Include@EqualsAndHashCode.Exclude 标记类型成员来修改使用哪些字段(甚至指定要使用各种方法的输出)。或者,你可以通过使用 @EqualsAndHashCode.Include 并使用 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 标记它们来准确指定你希望使用的字段或方法。

如果将 @EqualsAndHashCode 应用于继承自其他类的类,则此功能会变得有点棘手。通常,为这些类自动生成 equalshashCode 方法是个坏主意,因为超类也定义了一些字段,这些字段也需要 equals/hashCode 代码,但不会生成此代码。通过将 callSuper 设置为true,你可以在生成的方法中包含超类的 equalshashCode 方法。对于 hashCodesuper.hashCode() 的结果包含在哈希算法中。对于 equals,如果父类实现认为它不等于传入的对象,则生成的方法将返回false。请注意,并非所有 equals 实现都能正确处理这种情况。但是,lombok生成的 equals 实现 可以 正确处理这种情况,因此如果你的超类也有一个lombok生成的equals方法,你可以安全地调用你的超类 equals。如果你有一个明确的超类,那么你必须为callSuper指定值来确认你已经考虑过超类了,否则做会导致警告。

如果不继承任何对象(不算 java.lang.Object),将 callSuper 设置为true会导致编译时错误,因为它会直接将java.lang.Object的 equals() 和 hashCode()实现作为该类的equals() 和 hashCode()实现; 只有相同的对象才彼此相等并具有相同的 hashCode 。扩展另一个类时不将 callSuper 设置为true会生成警告,因为除非超类没有用来判断相等的字段,否则lombok无法为你生成考虑超类声明的字段的实现。你需要编写自己的实现,或者依赖 callSuper 链工具。你还可以使用 lombok.equalsAndHashCode.callSuper 配置键。

在 Lombok 0.10 中新增:除非你的类是 final 并且继承自 java.lang.Object ,否则 lombok 会生成一个 canEqual 方法,这意味着JPA代理仍然可以等于它们的基类,但是添加新状态的子类不会破坏 equals 合约。本文解释了为什么需要这种方法的复杂原因:如何在Java中编写一个相等方法。如果层次结构中的所有类都是 scala case 类和带有 lombok 生成的 equals 方法的类的混合,则所有相等都将“正常工作”。如果你需要编写自己的 equals 方法,那么如果更改 equalshashCode,则应始终覆盖 canEqual

Lombok 1.14.0 中的新功能:要将注释放在 equalsohter 参数(以及相关的canEqual )方法上,可以使用 onParam=@__({@AnnotationsHere}) 。但要小心!这是一个实验性功能。有关更多详细信息,请参阅有关 onX 功能的文档。

with Lombok

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  @EqualsAndHashCode.Exclude private Shape shape = new Square(5, 10);
  private String[] tags;
  @EqualsAndHashCode.Exclude private int id;
  
  public String getName() {
    return this.name;
  }
  
  @EqualsAndHashCode(callSuper=true)
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }
}

Vanilla Java

 import java.util.Arrays;

public class EqualsAndHashCodeExample {
  private transient int transientVar = 10;
  private String name;
  private double score;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  private int id;
  
  public String getName() {
    return this.name;
  }
  
  @Override public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof EqualsAndHashCodeExample)) return false;
    EqualsAndHashCodeExample other = (EqualsAndHashCodeExample) o;
    if (!other.canEqual((Object)this)) return false;
    if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
    if (Double.compare(this.score, other.score) != 0) return false;
    if (!Arrays.deepEquals(this.tags, other.tags)) return false;
    return true;
  }
  
  @Override public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final long temp1 = Double.doubleToLongBits(this.score);
    result = (result*PRIME) + (this.name == null ? 43 : this.name.hashCode());
    result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
    result = (result*PRIME) + Arrays.deepHashCode(this.tags);
    return result;
  }
  
  protected boolean canEqual(Object other) {
    return other instanceof EqualsAndHashCodeExample;
  }
  
  public static class Square extends Shape {
    private final int width, height;
    
    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
    
    @Override public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof Square)) return false;
      Square other = (Square) o;
      if (!other.canEqual((Object)this)) return false;
      if (!super.equals(o)) return false;
      if (this.width != other.width) return false;
      if (this.height != other.height) return false;
      return true;
    }
    
    @Override public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      result = (result*PRIME) + super.hashCode();
      result = (result*PRIME) + this.width;
      result = (result*PRIME) + this.height;
      return result;
    }
    
    protected boolean canEqual(Object other) {
      return other instanceof Square;
    }
  }
}

支持的配置键

lombok.equalsAndHashCode.doNotUseGetters = [ true | false ](默认值:false)

如果设置为true,则在生成 equalshashCode 方法时,lombok 将直接访问字段而不是使用 getter(如果可用)。 如果明确指定,注解参数 doNotUseGetters 优先于此设置。

lombok.equalsAndHashCode.callSuper = [ call | skip | warn ](默认:warn) 如果设置为 call,且你的类继承了其他类,则lombok将生成对超类中的 hashCodeequals 实现的调用。 如果设置为 skip ,则不会生成此调用。 默认行为 warn 类似于 skip,额外多一个警告。

lombok.equalsAndHashCode.flagUsage = [ warning | error ](默认:未设置) 如果已配置,Lombok会将@EqualsAndHashCode的任何用法标记为警告或错误。

Small print

数组是 deep compared/hashCoded,这意味着包含自身的数组将导致StackOverflowErrors。但是,这种行为与 ArrayList 并没有什么不同。

您可以安全地假设使用的 hashCode 实现不会在 lombok 的版本之间发生变化,但是这种保证不是一成不变的。如果使用备用哈希算法可以获得显着的性能提升,那么将在未来的版本中进行替换。

出于相等的目的,浮点数和双精度数的2个 NaN(非数字)值被认为是相等的,尽管 NaN == NaN 将返回 false。这类似于 java.lang.Doubleequals 方法,对于相等(equality),实际上需要确保将对象与其自身的精确副本进行比较时返回 true

如果有任何名为 hashCodeequals 的方法,则无论返回类型如何,都不会生成任何方法,而是发出警告。这两种方法需要彼此同步,除非lombok生成所有方法,否则lombok无法保证同步,因此如果一种或两种方法已经存在,则你总是会收到警告。你可以使用 @lombok.experimental.Tolerate 标记任何方法以将其隐藏在lombok中。

尝试排除不存在或已经被排除的字段(因为这些字段是静态的或瞬态的)会导致出现关于该字段的警告。

如果方法被标记为包含并且它与字段具有相同的名称,则它将替换该字段(包括该方法,不包括该字段)。

在 lombok 1.16.22 之前,可以使用 @EqualsAndHashCode 注释的 ofexclude 参数来完成包含/排除。现在仍然支持这种旧式包含机制,但将来会被弃用。

默认情况下,任何以 $ 符号开头的变量都会自动排除。你只能通过使用 @ EqualsAndHashCode.Include 注解来包含它们。

如果存在要包含的字段的 getter,则调用 getter 而不是使用直接字段引用。可以抑制此行为: @EqualsAndHashCode(doNotUseGetters = true)