当前位置: 首页 > 知识库问答 >
问题:

在覆盖equals()和hashCode()方法时使用Primiry Key(id)

华章横
2023-03-14

在阅读了大量文档和文章后,我真的对equals()hashCode()方法感到非常困惑。主要是,有各种各样的例子和用法让我太困惑了。

那么,你能澄清一下以下几点吗?

1?

@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (getClass() != o.getClass()) return false;
   
   // code omitted
}

2.如果有一个唯一的键,例如Private String isbn;,那么我们应该只使用这个字段吗?或者我们应该将它与getClass()组合,如下所示?

@Override
public boolean equals(Object o) {
   if (this == o) return true;
   if (getClass() != o.getClass()) return false;
   Book book = (Book) o;
   return isbn == book.isbn;
}

3.naturaid怎么样?据我所知,它用于唯一字段,例如,<代码>私有字符串isbn 。它的用途是什么?它是否与方法有关?

共有3个答案

马阳曦
2023-03-14

关于getClass()的用法,一切都很简单。

方法equals()需要类型为Object的参数。

在执行强制转换和比较属性之前,确保您正在拨打同一个类的实例,这一点很重要,否则最终可能会出现ClassCastException。如果对象不属于同一个类,那么它们显然是不相等的,因此可以使用getClass()

当你谈论“自然”时,比如一本书的ISBN编号与“id”,我想你指的是持久性实体的自然键与关系数据库中使用的代理键。

在这一点上有不同的观点,一般推荐的方法(参见Hibernate用户指南的链接和下面的其他参考)是在您的应用程序中使用自然id(一组唯一属性,也称为业务密钥)和实体仅在数据库中持久化后获得的ID。

您可能会遇到基于代理id实现的hashCode()equals(),并进行防御性空值检查以防止实体处于瞬态且其id为null的情况。根据这样的实现,瞬态实体不会等于处于持久状态的实体,具有相同的属性(除了非空id)。我个人认为这种方法是不正确的。

以下代码示例取自最新的官方Hibernate 6.1用户指南

示例142。自然Id等于/hashCode

@Entity(name = "Book")
public static class Book {

    @Id
    @GeneratedValue
    private Long id;
    private String title;
    private String author;

    @NaturalId
    private String isbn;

    //Getters and setters are omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Book book = (Book) o;
        return Objects.equals(isbn, book.isbn);
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn);
    }
}

上述使用业务键的代码在指南中表示为最终方法,与基于代理键的实现不同,代理键被称为原始实现(请参阅示例139)。

这里描述了选择ID与自然键的相同推理:

如果您必须覆盖equals()和hashCode()方法

>

  • 打算将持久类的实例放在一个集合中(推荐用于表示多值关联的方法),并

    打算使用分离实例的重新附加

    Hibernate仅在特定会话范围内保证持久标识(数据库行)和Java标识的等价性。因此,一旦我们混合在不同会话中检索到的实例,如果我们希望对Sets进行有意义的语义学,就必须实现equals()和hashCode()。

    最明显的方法是通过比较两个对象的标识符值来实现equals()/hashCode()。如果值相同,则两者必须是相同的数据库行,因此它们相等(如果将两者添加到一个集合中,则集合中只有一个元素)。不幸的是,我们不能对生成的标识符使用这种方法!Hibernate将只为持久对象分配标识符值,新创建的实例将没有任何标识符值!此外,如果一个实例未保存且当前在一个集中,则保存该实例将为该对象分配一个标识符值。如果equals()和hashCode()基于标识符值,则哈希代码将更改,从而破坏集合的约定。有关此问题的完整讨论,请参阅Hibernate网站。注意,这不是Hibernate问题,而是对象标识和相等的正常Java语义。

    我们建议使用业务密钥相等来实现equals()和hashCode()。

    有关更多信息,请查看@Vlad Mihalcea最近(2021年9月15日)撰写的关于如何使用自然密钥改进缓存查询结果的文章使用JPA和Hibernate映射@NaturalId业务密钥的最佳方法,以及这些问题:

    >

  • JPA hashCode()/equals()困境

    在equals和hashCode中是否应该考虑JPA实体的id字段?

  • 萧嘉茂
    2023-03-14
    匿名用户

    1. 如果实体中没有任何唯一字段(id字段除外),那么我们应该使用getClass()方法还是在equals()方法中仅使用id字段,如下所示
    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (getClass() != o.getClass()) return false;
      
      // code omitted
    }
    

    在#equals实现中比较类时,我们实现了以下两个目标:

    1. 因此,我们确保不会将苹果与桔子进行比较(尽管这可能是正确的)
    2. 您省略的代码必须对某个已知类执行对象o的强制转换,否则我们将无法从对象o中提取所需信息,因此,我们使#equals方法安全-例如,在调用Set#add时,没有人期望得到ClassCastException。使用实例似乎不是一个好主意,因为它违反了等于的对称和传递契约

    另外值得注意的是,调用o.getClass()可能会导致意外行为。当对象o是代理时,有些人更喜欢调用Hibernate。取而代之的是获取类(o),或者实现其他技巧。

    在阅读了大量文档和文章后,我对equals()和hashCode()方法感到非常困惑。主要是,有各种各样的例子和用法让我太困惑了

    @Override
    public boolean equals(Object o) {
       if (this == o) return true;
       if (getClass() != o.getClass()) return false;
       Book book = (Book) o;
       return isbn == book.isbn;
    }
    

    这是一个非常有争议的话题,以下是对这个问题的一些想法:

    • 为每个DB表维护PK列是个好主意-它几乎不花费任何费用,但简化了很多事情-想象一下有人要求您删除一些行,而不是从tbl中删除id=...您需要编写从tbl中删除field d1=... and field d2=... and...
    • PK不应该是复合的,否则您可能会对从tbl
    • 中选择count(不同字段1,字段2)之类的查询感到惊讶
    • 实体只有在存储在数据库中时才会获得其ID的论点,这就是为什么我们不能在equals和hashCode中依赖或代理ID是错误的,是的,这是大多数JPA项目的常见情况/行为,但是您总是可以选择手动生成和分配ID,下面的一些示例:
      • Eclipse Link用户指南:"默认情况下,实体Id必须由应用程序设置,通常在调用持久化之前。可以使用@GeneratedValue让Eclipse Link生成Id值。"-我相信很明显@GeneratedValue只是一个额外的功能,没有人阻止您创建自己的对象工厂。
      • Hibernate用户指南:“可以分配简单标识符的值,这只是意味着应用程序本身将在持久化实体之前将值分配给标识符属性。”
      • 一些流行的持久存储(Cassandra、MongoDB)没有开箱即用的自动增量功能,但是没有人会说这些存储不允许实现一些高级想法,如DDD等。
      • 银行卡PAN-看起来很独特,但你甚至不能将其存储在DB中(我相信SSN、VIN也是安全敏感的)

      PS。Vlad Mihalcea提供了有趣的hashCode实现:

          @Override
          public boolean equals(Object o) {
              if (this == o) return true;
       
              if (!(o instanceof Book))
                  return false;
       
              Book other = (Book) o;
       
              return id != null &&
                     id.equals(other.getId());
          }
       
          @Override
          public int hashCode() {
              return getClass().hashCode();
          }
      

      关于HBN文档,问题是它们的合成案例与现实世界没有任何共同之处。让我们考虑他们的虚拟作者/书籍模型,并尝试扩展它。。。想象一下,我是一个出版商,我想记录我的作者、他们的书和草稿。书籍和草稿有什么区别?书已分配isbn,草稿尚未分配,但草稿可能会成为书(也可能不会)。在这种情况下,如何为草稿保留java equals/hashCode契约?

    蒙光华
    2023-03-14

    这一切都归结为你的类实际代表什么,它的身份是什么,以及JVM何时应该认为两个对象实际上是相同的。使用类的上下文决定了它的行为(在这种情况下——与另一个对象相等)。

    默认情况下,Java仅当两个给定对象实际上是一个类的同一实例时才认为它们“相同”(使用比较)。虽然在严格的技术验证中是有意义的,但Java应用程序通常用于表示业务领域,其中可以构造多个对象,但它们仍应被视为相同的。例如一本书(如你的问题所示)。但是,一本书和另一本书是什么意思呢?

    看,这要看情况而定。

    当你问某人是否读过某本书时,你给他们一个书名和作者,他们会尝试将其与他们读过的书“匹配”,看看其中是否有一本书符合你提供的标准。因此,在这种情况下,等于将检查给定书籍的标题和作者是否与另一本相同。易于理解的

    现在想象你是托尔金的粉丝。如果你是波兰人(像我一样),你可以阅读多个《指环王》的翻译,但是(作为粉丝)你会知道一些翻译有点过分,你想避开它们。标题和作者是不够的,你正在寻找一本具有特定ISBN标识符的书,这将让你找到该书的某个版本。由于ISBN还包含有关标题和作者的信息,因此在这种情况下,不需要在equals方法中使用它们。

    第三个(也是最后一个)与书籍相关的示例与图书馆相关。上述两种情况很容易在图书馆发生,但从图书馆员的角度来看,书籍也是另一种东西:一种“物品”。图书馆中的每本书(这只是一个假设,我从未使用过这样的系统)都有自己的标识符,它可以完全独立于ISBN(但也可以是ISBN加上额外的东西)。当你在图书馆还书时,重要的是图书馆标识符,在这种情况下应该使用它。

    总而言之:一本书作为一个抽象概念并没有一个单一的“平等定义”。这取决于语境。假设我们创建了这样一组类(很可能在多个上下文中):

    • 书籍
    • 图书编辑
    • 图书项目
    • 图书订单(尚未在图书馆中)

    BookBookEdition更像是一个值对象,而BookItemBookOrder是实体。值对象仅由其值表示,即使它们没有标识符,也可以与其他值相等。另一方面,实体可以包括值,甚至可以由值对象组成(例如,BookItem可以在其库ID字段旁边包含一个BookEdition字段),但它们有一个标识符,用于定义它们是否与另一个相同(即使它们的值发生变化)。书籍在这里不是一个很好的例子(除非我们想象将一个图书馆标识符重新分配给另一本书),但更改了用户名的用户仍然是同一个用户——由其ID标识。

    关于检查传递给equals方法的对象的类,强烈建议(但编译器不以任何方式强制执行)在转换对象之前验证对象是否为给定类型,以避免出现ClassCastException。为此,应使用instanceofgetClass()。如果对象满足预期类型的要求,您可以强制转换它(例如Book其他=(Book)对象;),只有这样您才能访问书籍的属性(图书ID、isbn、标题、作者)-Object类型的对象没有这样的字段或访问它们的权限。

    您在问题中没有明确询问这一点,但是使用instanceofgetClass()可能同样不清楚。经验法则是:使用getClass(),因为它有助于避免对称问题。

    自然ID可能因上下文而异。如果是图书版本,ISBN是一个自然ID,但如果只是一本书,它将是一对标题和作者(作为一个单独的类)。您可以在文档中阅读更多关于Hibernate中自然ID的概念。

    重要的是要理解,如果您在数据库中有一个表,它可以映射到更复杂域中的不同类型的对象。ORM工具应该帮助我们管理和映射数据,但是定义为数据表示的对象是(或者更确切地说:通常应该是)与域模型不同的抽象层。

    然而,例如,如果您被迫使用图书项作为数据建模类,libraryId可能是数据库上下文中的一个ID,但isbn不是一个自然ID,因为它不能唯一标识图书项。如果BookEdition是数据建模类,则它可以包含由数据库自动生成的ID(数据库上下文中的ID)和ISBN,在这种情况下,ISBN将是自然ID,因为它在BookEdition上下文中唯一标识BookEdition。

    为了避免这些问题并使代码更加灵活和描述性,我建议将数据视为数据,将域视为域,这与域驱动设计有关。自然ID(作为一个概念)仅存在于代码的域级别,因为它可以变化和发展,您仍然可以使用相同的数据库表将数据映射到这些不同的对象中,具体取决于业务上下文。

    这是一个代码片段,其中包含上述类和一个表示数据库中表行的类。

    数据模型(可能由Hibernate等ORM管理):

    // database table representation (represents data, is not a domain object)
    // getters and hashCode() omitted in all classes for simplicity
    
    class BookRow {
    
        private long id;
        private String isbn;
        private String title;
        // author should be a separate table joined by FK - done this way for simplification
        private String authorName;
        private String authorSurname;
        // could have other fields as well - e.g. date of addition to the library
        private Timestamp addedDate;
    
        @Override
        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (object == null || getClass() != object.getClass()) {
                return false;
            }
            BookRow book = (BookRow) object;
            // id identifies the ORM entity (a row in the database table represented as a Java object)
            return id == book.id;
        }
    }
    

    域模型:

    // getters and hashCode() omitted in all classes for simplicity
    
    class Book {
    
        private String title;
        private String author;
    
        @Override
        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (object == null || getClass() != object.getClass()) {
                return false;
            }
            Book book = (Book) object;
            // title and author identify the book
            return title.equals(book.title)
                   && author.equals(book.author);
        }
    
        static Book fromDatabaseRow(BookRow bookRow) {
            var book = new Book();
            book.title = bookRow.title;
            book.author = bookRow.authorName + " " + bookRow.authorSurname;
            return book;
        }
    }
    
    class BookEdition {
    
        private String title;
        private String author;
        private String isbn;
    
        @Override
        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (object == null || getClass() != object.getClass()) {
                return false;
            }
            BookEdition book = (BookEdition) object;
            // isbn identifies the book edition
            return isbn.equals(book.isbn);
        }
    
        static BookEdition fromDatabaseRow(BookRow bookRow) {
            var edition = new BookEdition();
            edition.title = bookRow.title;
            edition.author = bookRow.authorName + " " + bookRow.authorSurname;
            edition.isbn = bookRow.isbn;
            return edition;
        }
    }
    
    class BookItem {
    
        private long libraryId;
        private String title;
        private String author;
        private String isbn;
    
        @Override
        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (object == null || getClass() != object.getClass()) {
                return false;
            }
            BookItem book = (BookItem) object;
            // libraryId identifies the book item in the library system
            return libraryId == book.libraryId;
        }
    
        static BookItem fromDatabaseRow(BookRow bookRow) {
            var item = new BookItem();
            item.libraryId = bookRow.id;
            item.title = bookRow.title;
            item.author = bookRow.authorName + " " + bookRow.authorSurname;
            item.isbn = bookRow.isbn;
            return item;
        }
    }
    

     类似资料:
    • 问题内容: 如果我有 如果我比较A的2个实例但没有覆盖equals方法,是否可以获得预期的结果? 问题答案: 如果我比较A的2个实例但没有覆盖equals方法,是否可以获得预期的结果? 这取决于您的期望:) 默认实现将为您提供 引用相等性-换句话说,当您比较两个引用时,仅当它们是对同一对象的引用时才返回true。 通常,您将重写以实现“值相等”,在这种情况下,两个不同的对象通常被认为具有相等的字段

    • 问题内容: 好的,我从很多地方和来源都听说过,每当我覆盖equals()方法时,我也需要覆盖hashCode()方法。但是请考虑以下代码 这里的输出为true,完全按照我想要的方式为false,我根本不关心重写hashCode()方法。这意味着hashCode()覆盖是一种选择,而不是每个人都说的强制性选择。 我想要第二次确认。 问题答案: 它对您有用,因为您的代码未使用任何需要API的功能(Ha

    • 菜单栏: Code —> Generate —> equals() and hashCode() 右键菜单:Generate —> equals() and hashCode() 快捷键: Mac: command + N Windows\/Linux: Alt + Insert —> equals() and hashCode()

    • 问题内容: 在java中为什么需要覆盖equals和hashcode方法?什么时候用到? 问题答案: 让我们尝试通过一个示例来理解它,如果我们不进行覆盖而覆盖并尝试使用。 假设我们有一个类像这样那样的两个对象是相等的,如果他们等于(和生成) 仅覆盖 如果仅覆盖被覆盖,则在你第一次调用时将散列到某个存储桶,而在调用时将散列到其他存储桶(因为它们具有不同的)。因此,尽管它们是相等的,但由于它们没有散列

    • 我试图覆盖提到的方法为我的: MyObject: 如何重写hashcode(),equals()和compareTo()方法? 目前我有以下几点: 我读到通过id比较是不够的,这是对象是数据库的持久实体(见这里)。 此类型的所有对象的名称和编号不是唯一的。 那么我应该如何覆盖它呢? 我还需要比较它里面的hashMap吗? 我很困惑。该对象唯一独特的地方是map myMap,它将在生命周期的后期填充

    • 我在为自定义员工类实现hashmap时有问题。 > 我只重写了employee类的。所以,对于相同的对象,我得到相同的hashcode值。如果我不重写equals(),那就没问题了,对吧?因为object类的)比较了引用(在这里,我得到了相同的引用来表示相等的对象)。无论如何,根据我的逻辑,我永远不会为不同的emp对象得到相同的哈希值。所以,让