Java @Override equals(): 当 this.getClass() != o.getClass() 失败但不应该

Java @Override equals(): When this.getClass() != o.getClass() fails but shouldn't

我的 MyClass class:

中有 equals() 的 @Override
@Entity( name = "MyClass" )
@Table( name = "my_class" )
public class MyClass extends MySuperClass
{
  ...
  @Override
  public boolean equals( Object o )
  {
    if ( this == o )
    {
      return true;
    }
    if ( o == null || this.getClass() != o.getClass() )
    {
      return false;
    }
    if ( !super.equals( o ) )
    {
      return false;
    }
    MyClass that = ( MyClass ) o;
    return this.var1.equals( that.var1 ) && this.var2.equals( that.var2 );
  }
  ...
}

相当标准。事实上,它遵循 Java 最佳实践。
后来我在另一个子包中有这个 class(我的控制器 class):

...
package com.a.b.api.controllers;
...
import com.a.b.jpa.models.MyClass;
...
MyClass myObject1 = new MyClass( var1, var2 );
MyClass myObject2 = this.myClassRepository.getById( 1 ); // SpringBoot/Jpa/Hibernate

if ( myObject2.equals( myObject1 ) )
{
   ...do something...
}
...
this.myClassRepository.save( myObject1 );
...

我的问题是 .equals() 总是在这里失败:

if ( o == null || this.getClass() != o.getClass() )

因为 java 说 this.getClass()o.getClass() 相等。当我调试代码时(在 Intellij IDEA 2022.1 UE 中)我看到了这个:

this.getClass() = MyClass@13706

但是

o.getClass = com.a.b.jpa.models.MyClass@8f7462

但他们是一样的class!几乎每本 Java 书籍、教程、博客、Intellij IDEA 等都以这种方式演示 .equals()。我在 Ubuntu 20.04.4 LTS java-14-openjdk-amd64java-17-openjdk 中尝试过这个-amd64 结果相同。

我做错了什么?

myObject2 是代理 class 的实例,由 Hibernate 在运行时使用 Byte Buddy 生成。生成的代理拦截所有方法调用,这就是为什么getClass() returns 不同的结果。

作为 getClass() 的替代方法,使用 instanceof 可能是另一种方法:

if ( !(this instanceof MyClass && o instanceof MyClass) )
{
   return false;
}

但是请记住 instanceof 有它的 drawbacks。违反了对称原理。

您一开始就不应该比较这些对象,因为新对象应该不同于 Hibernate 管理的具有持久状态的对象。

我将此标准 equalshashcode 实现用于 JPA 实体(如 here 所述):

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


@Override
public int hashCode() {
    return getClass().hashCode();
}

您没有使用最佳实践。 将您的 equals 更改为如下所示 (首先进行空检查,然后进行相同的检查,然后 class 检查):

 public boolean equals(Object obj) {
   if (obj == null) { return false; }
   if (obj == this) { return true; }
   if (obj.getClass() != getClass()) {
     return false;
   }
   
   ... do the actual comparison here
 }

注:以上代码是Apache EqualsBuilder

的意译

快乐的歌他的回复中正确表述:

myObject2 is an instance of a proxy class, generated at runtime by Hibernate using Byte Buddy. The generated proxy intercepts all method invocations, that's why getClass() returns different results.

我真的不想使用 instanceof 因为这被认为是不好的做法所以我开始四处寻找并偶然发现了一个有类似问题的 post。他们的解决方案是将 final 关键字添加到他们的 class 声明中。我认为这微不足道,但试了一下 - 成功了! 添加 final 关键字导致

if ( o == null || this.getClass() != o.getClass() )

if ( o == null || !this.getClass().equals( o.getClass() ) )

才能正常工作。我的 class 代码现在是:

@Entity( name = "MyClass" )
@Table( name = "my_class" )
final public class MyClass extends MySuperClass
{
  ...
  @Override
  public boolean equals( Object o )
  {
    if ( this == o )
    {
      return true;
    }
    if ( o == null || !this.getClass().equals( o.getClass() ) )
    {
      return false;
    }
    if ( !super.equals( o ) )
    {
      return false;
    }
    MyClass that = ( MyClass ) o;
    return this.var1.equals( that.var1 ) && this.var2.equals( that.var2 );
  }
  ...
}

谢谢大家的帮助!非常感谢 happy songs 为我指明了正确的方向!