2024-01-10  阅读(49)
原文作者:猫巳 原文地址: https://moonce.blog.csdn.net/article/details/121792523

一、简介

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

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

当你不扩展任何东西(其实扩展了java.lang.Object)时,将callSuper设置为true是一个编译时错误,因为它将使生成的equals()hashCode()实现具有与简单地从java.lang.Object继承这些方法一样的行为:只有相同的对象才会互相相等,并且会有相同的hashCode。当你扩展另一个类时,不把callSuper设置为true会产生一个警告,因为除非父类没有(重要的)字段,否则lombok不能为你生成一个考虑到你的父类所声明的字段的实现。你需要编写你自己的实现,或者依靠callSuper的链式工具。你也可以使用lombok.equalsAndHashCode.callSuper配置键。

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

Lombok 1.14.0中的新增功能:要在equals(如果相关,还有canEqual)方法的另一个参数上添加注释,可以使用onParam=@_u({@AnnotationsHere})。不过要小心!这是一个实验特性。有关更多详细信息,请参阅有关onX功能的文档。

Lombok 1.18.16中的新内容:生成的hashCode()的结果可以通过设置cacheStrategyCacheStrategy.NEVER以外的值进行缓存。如果注释类的对象可以以任何方式被修改,从而导致hashCode()的结果改变,请不要使用这个方法。

二、示例比较

1. 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;
        }
      }
    }

2. 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)
如果设置为truelombok在生成equalshashCode方法时将直接访问字段而不是使用getters(如果有的话)。注释参数 “doNotUseGetters”,如果明确指定的话,将优先于这个设置。

lombok.equalsAndHashCode.callSuper = [call | skip | warn] (默认: warn)
如果设置为calllombok将为子类生成对hashCodeequals的父类实现的调用。如果设置为skip,则不会产生这样的调用。默认行为warnskip一样,只是多了一个额外的警告。

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

四、附属说明

Arrays是 "深度 "比较/哈希编码,这意味着包含自己的数组将导致StackOverflow错误。不过,这种行为与ArrayList等没有区别。

您可以放心地假设所使用的hashCode实现不会在lombok的不同版本之间发生变化,但是这种保证并不是一成不变的;如果使用另一种哈希算法可以显著提高性能,那么将在未来的版本中进行替换。

出于相等的目的,浮点数和双精度浮点数的2NaN(不是数字)值被视为相等,虽然'NaN==NaN’返回false。这类似于java.lang.Doubleequals方法,并且实际上是确保将对象与自身的精确副本进行比较时返回true表示相等所必需的。

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

尝试排除不存在或无论如何都会被排除的字段(由于它们是静态或瞬态的)会在命名字段上产生警告。

如果一个方法被标记为包含,并且它与一个字段有相同的名称,那么它将取代该字段(方法被包含,字段被排除)。

lombok 1.16.22之前,包含/排除可以通过@EqualsAndHashCode注解的ofexclude参数完成。这种旧式的包含机制仍然被支持,但在未来将被废弃。

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

如果一个要包含的字段存在一个getter,它将被调用,而不是使用一个直接的字段引用。这种行为可以使用字段引用:

    @EqualsAndHashCode(doNotUseGetters = true)

如果您已经通过lombok.configlombok.addNullAnnotations配置了nullity注释风格,则生成的equals方法以及任何canEqual方法的参数都将使用可为Null的注释进行注释。如果将@NonNullByDefault样式注释与严格的空性检查结合使用,则需要执行此操作。

参考文献

【1】@EqualsAndHashCode | Equality made easy: Generates hashCode and equals implementations from the fields of your object.


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文