0

在通过 RMI 传输的对象上,我遇到了一个与 equals 相关的奇怪问题。这已经让我头疼了几天,我想知道是否有人可以帮助阐明这个问题。

我有一个车库类(如果它相关,它也是一个 JPA 实体),我将它推送到一个名为 X over RMI 的 java 进程(所以这个对象正在被序列化)。Garage 对象存储了一个名为 Car 的对象列表(也是 JPA 实体),这些对象也是可序列化的。

Garage 上的 equals 方法基本上是在其汽车列表(一个 ArrayList)上调用 equals

当我在 java 进程中调用 equals 时,由于某种原因,它不会像我期望的那样在列表上调用 equals,我希望它会在列表中的所有 Cars 上调用 equals 以检查列表是否相等,但它不会这样做。

奇怪的是,当单元测试时,它确实对 Cars ArrayList 的所有成员调用 equals。我什至将这些对象序列化为我的单元测试的一部分,这也很有效。有任何想法吗?我希望我能解决问题,随时要求任何信息来澄清任何事情。

编辑:我几乎可以肯定它的 ArrayList 很奇怪,因为当我在我的对象中手动执行 equals 而不是在汽车列表上调用 equals 我在汽车列表上做了一个 foreach 循环并在每辆汽车上调用 equals (就像我预期的 ArrayList equals无论如何都要做,它按预期工作)

@Entity
@Table(schema="pdw", name="garage")
public class Garage
    implements Comparable<Garage> , 
    Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String id;

    private String name;


    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(schema="pdw")
    private List<Car> cars = new ArrayList<Car>();

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<Car> getCars() {
        return cars;
    }
    public void setCars(List<Car> cars) {
        this.cars = cars;
    }

    @Override
    public String toString() {

        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        buffer.append("Garage:");
        buffer.append("[id:" + id + "]");
        buffer.append("[Cars:" + cars + "]");
        buffer.append("]");
        return buffer.toString();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof Garage))
            return false;
        Garage other = (Garage) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        if (cars == null) {
            if (other.cars != null)
                return false;
        } else if (!cars.equals(other.cars))
            return false;
        return true;
    }

    @Override
    public int compareTo(Garage other) {
        return this.getName().compareTo(other.getName());
    }
}

@Entity
@Table(schema="pdw", name="car")
public class Car 
    implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private String id;

    private String name;

    @OneToOne(fetch = FetchType.LAZY)
    private Garage garage;

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Garage getGarage() {
        return garage;
    }
    public void setGarage(Garage garage) {
        this.garage = garage;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Car other = (Car) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

    @Override
    public String toString() {

        StringBuffer buffer = new StringBuffer();
        buffer.append("[");
        buffer.append("Car:");
        buffer.append("[id:" + id + "]");
        buffer.append("[name:" + name + "]");
        buffer.append("[garage:" + garage.getName() + "]");
        buffer.append("]");
        return buffer.toString();
    }   
}
4

8 回答 8

6
  • 确保您List在反序列化后不为空。
  • 在您的方法中放置一个断点,equals看看是否没有发生任何错误
  • 确保你的equalson实现Car是正确的
  • 检查是否没有transient字段

  • 检查您期望的ArrayList内容是否实际上不是PersistentBag. 因为它equals不会做你想做的事。如果是PersistentBag,您可以ArrayList在通过线路发送之前将其传输到 an (从而防止潜在的LazyInitializationException),或者在每个元素上而不是在其List本身上调用 equals 。Hibernate 用于PersistentBag包装您的集合以提供延迟加载

PS 如果您使用的是 Hibernate 以外的 JPA 提供程序,可能它具有类似的集合包装器。指出你的持久性提供者是什么。

于 2009-12-08T11:52:23.423 回答
1

扩展博卓:

  • 检查两边的类是否相同serialVersionUID(即编译版本完全相同)。如有必要,将其硬编码为private static final long类变量。

java.io.SerializableAPISun 关于 Serialization 的文章中了解更多信息。

于 2009-12-08T11:56:03.527 回答
1

您提到您正在使用 JPA...确保对象在序列化之前包含完整的 ArrayList,也许您在延迟加载列表并且在序列化和反序列化列表后它是空的?我唯一不明白的事情(如果是这种情况)是为什么你在不在会话时尝试延迟实例化列表时不会出错(我怀疑反序列化方面的情况)。

于 2009-12-08T12:41:49.250 回答
1

ArrayList使用AbstractLists 的实现equals()。这是这样定义的:

比较指定对象与此列表是否相等。当且仅当指定对象也是一个列表时返回 true,两个列表具有相同的大小,并且两个列表中所有对应的元素对都相等。(如果 (e1==null ? e2==null : e1.equals(e2)) 两个元素 e1 和 e2 相等。)换句话说,如果两个列表包含相同的元素,则定义为 > 相等命令。

这个实现首先检查指定的对象是否是这个列表。如果是,则返回 true;如果不是,它检查指定的对象是否是一个列表。如果不是,则返回 false;如果是这样,它会遍历两个列表,比较相应的元素对。如果任何比较返回 false,则此方法返回 false。如果其中一个迭代器在另一个迭代器之前用完元素,则返回 false(因为列表的长度不等);否则在迭代完成时返回 true。

如果您Car的 s 没有被比较,也许比较在列表比较的早期部分已经失败?您正在比较的列表是否可能没有相同数量的元素?

于 2009-12-08T12:47:51.833 回答
1

如果您安装了 Java 源代码,您可以通过 AbstractList equals 实现进行调试并查看它失败的地方。Java 1.6 的当前实现是:

public boolean equals(Object o) {
if (o == this)
    return true;
if (!(o instanceof List))
    return false;

ListIterator<E> e1 = listIterator();
ListIterator e2 = ((List) o).listIterator();
while(e1.hasNext() && e2.hasNext()) {
    E o1 = e1.next();
    Object o2 = e2.next();
    if (!(o1==null ? o2==null : o1.equals(o2)))
    return false;
}
return !(e1.hasNext() || e2.hasNext());
}

除了一些评论,即使我认为它们与您的问题无关:

1-如果你覆盖等于你必须覆盖hashCode,我不知道你是故意删除它还是你没有实现它。equals() 和 hashCode() 由联合合约绑定在一起,该合约指定是否使用 equals() 方法认为两个对象相等,那么它们必须具有相同的 hashcode 值。(借自 SCJP 书)。否则,您将在 HashMaps、HashSets 和其他集合类中遇到这些类的问题。

2- 在您的 equals 实现中,instanceof 检查空值和类类型,您可以替换

    if (obj == null)
            return false;
    if (getClass() != obj.getClass())
            return false;

if (!(obj instanceof Car)){
            return false;
}
于 2009-12-08T13:02:32.453 回答
1

您可能正在为您的对象获取子类(我知道 hibernate 创建代理类以支持延迟加载)。在您的 Car 类 equals 方法中,您执行“getClass()”比较,如果您将代理子类与实际实例进行比较,这将是错误的。您可以尝试使用 instanceof 操作而不是 getClass() (就像在您的 Garage equals 方法中一样)。您可以通过在 toString() 方法中包含“getClass()”来确认所有这些。

另外,(再次延迟加载),您永远不应该直接在实体类中引用成员变量,您应该始终使用 getter 和 setter。(所以你的equals,toString,...方法应该使用getName()等。

于 2009-12-08T14:34:25.530 回答
0

只是一个猜测:当您发送Garage实例时,X 进程是否可能只接收它的存根?如果是这样,当您调用该equals方法时,它实际上可能正在执行对其的远程调用,实际上是在原始 JVM 中(而不是在 X 进程中)调用这些方法。

您应该能够通过在 JVM 中添加断点并调用equals.

于 2009-12-08T12:57:53.003 回答
0

我刚刚遇到了同样的问题。当 Hibernate 用其 PersistentBag 类替换您的列表时,JUnit 对 Hibernate 返回的集合的 assertEquals() 将失败,因为该类不正确地实现了 equals()。这是来自 Hibernate 3.5.1-Final PersistentBag 类的代码:

/**
 * Bag does not respect the collection API and do an
 * JVM instance comparison to do the equals.
 * The semantic is broken not to have to initialize a
 * collection for a simple equals() operation.
 * @see java.lang.Object#equals(java.lang.Object)
 */
public boolean equals(Object obj) {
    return super.equals(obj);
}

从阅读他们的评论来看,他们这样做似乎是出于性能/效率的原因。但是,如果您有一个包含列表的对象,它会使单元测试变得困难。我的解决方案将编写一个 areEqualNonNullLists(listA, listB) 方法并将其放入包含列表的对象的 eqauls() 方法中。

public static boolean areEqualNonNullLists(List thisList, List thatList)
{
    if(thisList.size() != thatList.size()) return false;

    for(int i=0; i<thisList.size(); i++)
    {
        if(!thisList.get(i).equals( thatList.get(i) ) ) return false;
    }
    return true;
}

我想知道是否有更优雅、更通用的解决方案。

于 2011-02-18T05:55:40.743 回答