序列化后在 ArrayList 上调用 equals

发布于 2024-08-14 03:36:56 字数 4084 浏览 6 评论 0原文

我遇到了一个与通过 RMI 传输的对象的 equals 相关的奇怪问题。 这几天来这一直困扰着我,我想知道是否有人可以帮助阐明这个问题。

我有一个 Garage 类(如果相关,它也是一个 JPA 实体),我通过 RMI 将其推送到名为 X 的 java 进程(因此该对象正在被序列化)。 Garage 对象存储称为 Car(也是 JPA 实体)的对象列表,这些对象也是可序列化的。

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

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

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

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

@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();
    }   
}

I am hitting a strange problem in relation to equals on an object transported over RMI.
This has been wrecking my head for a few days now and I was wondering if anyone can help shed some light on the Problem.

I have a Garage Class (that is also a JPA entity in case its relevant) that I push to a java process called X over RMI (So this object is being serialized). The Garage object stores a list of objects called Car (also JPA entities) that are also Serializable.

The equals method on Garage is basically calling equals on its list of cars (an ArrayList)

When I call equals in the java process it does not for some reason call equals on the list like i expect I would expect it to call equals on all the Cars in the list to check if the lists are equal it does not do this.

The strange thing is when unit testing it does call equals on all the members of the Cars ArrayList. I even serialized the objects as part of my unit test and this worked too. Any ideas? I hope I am getting the problem across, feel free to request any info to clarify anything.

Edit: I am nearly certain its ArrayList being weird as when I manually do equals in my object instead of calling equals on the list of cars I did a foreach loop on the list of cars and called equals on each Car (like I expected ArrayList equals to do anyway and it worked as expected)

@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();
    }   
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

趴在窗边数星星i 2024-08-21 03:36:56
  • 确保反序列化后您的 List 不为空。
  • 在您的 equals 方法中放置一个断点,看看是否没有发生任何错误。
  • 确保您在 Car 上的 equals 实现正确
  • 检查是否没有 transient 字段

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

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

  • Make sure your List isn't empty after deseriazliation.
  • Put a breakpoint in your equals method and see if anything wrong isn't happening
  • Make sure your implementation of equals on Car is correct
  • Check if there are no transient fields

  • Check if what you expect to be an ArrayList isn't actually PersistentBag. Because its equals won't do what you want. If it is PersistentBag, you can either transfer it to an ArrayList before sending over the wire (thus preventing a potential LazyInitializationException), or call equals on each element rather than on the List itself. Hibernate uses PersistentBag to wrap your collections in order to provide lazy loading

P.S. If you are usingi a JPA provider other than Hibernate, perhaps it has a similar collections wrappers. Indicate what's your persistence provider.

琉璃梦幻 2024-08-21 03:36:56

扩展Bozho:

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

有关此内容的更多信息,请参见 java.io。 Serialized APISun 关于序列化的文章

To expand Bozho:

  • Check if the classes at the both sides have the same serialVersionUID (i.e. are of the exact same compiled versions). If necessary hardcode it as private static final long class variable.

More about this in the java.io.Serializable API and Sun's article about Serialization.

〃温暖了心ぐ 2024-08-21 03:36:56

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

You mentioned that you are using JPA... make sure that the object contains a full ArrayList prior to serialization, perhaps you are lazy loading the list and it is empty after you serialize and deserialize the list? The only thing I don't understand (if this is the case) is why you are aren't getting errors for trying to lazy instantiate the list when not in session (as I suspect is the case on the deserialization side).

枕梦 2024-08-21 03:36:56

ArrayList 使用 AbstractListequals() 实现。定义如下:

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

此实现首先检查指定的对象是否是此列表。如果是,则返回
真的;如果不是,它检查指定的对象是否是一个列表。如果不是,则返回 false;如果
因此,它迭代两个列表,比较相应的元素对。如果有的话
比较返回 false,该方法返回 false。如果任一迭代器耗尽
另一个元素之前的元素返回 false(因为列表的长度不相等);
否则当迭代完成时返回 true。

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

ArrayList uses AbstractLists implementation of equals(). That is defined like this:

Compares the specified object with this list for equality. Returns true if and only
if the specified object is also a list, both lists have the same size, and all
corresponding pairs of elements in the two lists are equal. (Two elements e1 and e2 are
equal if (e1==null ? e2==null : e1.equals(e2)).) In other words, two lists are defined to > be equal if they contain the same elements in the same order.

This implementation first checks if the specified object is this list. If so, it returns
true; if not, it checks if the specified object is a list. If not, it returns false; if
so, it iterates over both lists, comparing corresponding pairs of elements. If any
comparison returns false, this method returns false. If either iterator runs out of
elements before the other it returns false (as the lists are of unequal length);
otherwise it returns true when the iterations complete.

If your Cars are not being compared, maybe the comparison is already failing in the early parts of list comparison? Is it possible the lists you're comparing don't have the same number of elements?

红颜悴 2024-08-21 03:36:56

如果您安装了 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-如果您重写 equals,则必须重写 hashCode,我不知道您是否已删除故意这样做或者如果您没有实施它。 equals() 和 hashCode() 通过联合契约绑定在一起,该契约指定如果使用 equals() 方法将两个对象视为相等,则它们必须具有相同的 hashcode 值。 (借自 SCJP 书籍)。
否则,您将在 HashMap、HashSet 和其他集合类中遇到这些类的问题。

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

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

if (!(obj instanceof Car)){
            return false;
}

If you have the Java sources installed you can debug through the AbstractList equals implementation and see where it is failing.
Current implementation for Java 1.6 is:

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());
}

Apart from that a couple of comments, even though I don't think they are related to your issue:

1- If you override equals you have to override hashCode, I don't know if you have removed it on purpose or if you don't have it implemented. equals() and hashCode() are bound together by a joint contract that specifies if two objects are considered equal using the equals() method, then they must have identical hashcode values. (Borrowed from SCJP book).
You will have issues with these classe in HashMaps, HashSets and other collection classes otherwise.

2- In your equals implementations, instanceof checks for both null-ness and the class type, you can replace

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

with

if (!(obj instanceof Car)){
            return false;
}
甚是思念 2024-08-21 03:36:56

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

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

It's possible you are getting subclasses for your objects (i know hibernate creates proxy classes for lazy loading support). in your Car class equals method, you do a "getClass()" comparison which will be false if you are comparing a proxy subclass to an actual instance. you could try the instanceof operation instead of getClass() (like in your Garage equals method). You could confirm all of this by including "getClass()" in your toString() method.

also, (again a lazy loading thing), you should never refer to member vars directly in an entity class, you should always use the getters and setters. (so your equals,toString,... methods should use getName(), etc.

病毒体 2024-08-21 03:36:56

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

您应该能够通过在两个 JVM 中添加断点并调用 equals 来确认这一点。

Just a guess: isn't it possible that when you send the Garage instance, the X process is only receiving a stub for it? If so, when you invoke the equals method, it could be in fact executing a remote call to it, actually calling those methods in the original JVM (not in the X process).

You should be able to confirm that by adding breakpoints in both JVMs and invoking equals.

萌辣 2024-08-21 03:36:56

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

/**
 * 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;
}

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

I just ran into the same problem. JUnit's assertEquals() on collections returned by Hibernate will fail when Hibernate replaces your list with its PersistentBag class, because that class improperly implements equals(). Here's code from Hibernate 3.5.1-Final PersistentBag class:

/**
 * 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);
}

From reading their comment, it seems like they do this for performance/efficiency reasons. But it makes unit testing hard, if you have an object that contains a list. My solution will be writing an areEqualNonNullLists(listA, listB) method and put it in the eqauls() method of my object that contains a list.

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

I wonder if there's a more elegant, generic solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文