序列化后在ArrayList上调用equals

时间:2009-12-08 11:46:54

标签: java serialization jpa arraylist rmi

关于通过RMI传输的对象上的equals,我遇到了一个奇怪的问题。 这已经破坏了我的头几天了,我想知道是否有人可以帮助解决这个问题。

我有一个Garage类(也是一个相关的JPA实体),我推送到一个名为X over RMI的java进程(所以这个对象被序列化)。 Garage对象存储一个名为Car(也是JPA实体)的对象列表,这些对象也是Serializable。

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

当我在java进程中调用equals时,它不会出于某种原因在列表上调用equals,就像我期望它会在列表中的所有Cars上调用equals来检查列表是否相等它是不是此

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

编辑:我几乎可以肯定它的ArrayList很奇怪,因为当我在我的对象中手动执行等于而不是在汽车列表上调用equals时我在汽车列表上执行了foreach循环并调用了在每辆车上等于(就像我预期的那样,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();
    }   
}

8 个答案:

答案 0 :(得分:6)

  • 确保您的List在解除隔离后不为空。
  • equals方法中设置一个断点,看看是否有任何错误
  • 确保equalsCar的实施正确无误
  • 检查是否没有transient字段

  • 检查您希望ArrayList的内容实际上不是PersistentBag。因为它的equals不会做你想要的。如果是PersistentBag,您可以在通过网络发送之前将其转移到ArrayList(从而防止潜在的LazyInitializationException),或者在每个元素上调用等于而不是List 1}}本身。 Hibernate使用PersistentBag来包装您的集合以提供延迟加载

P.S。如果您正在使用除Hibernate之外的JPA提供程序,那么它可能具有类似的集合包装器。指出你的持久性提供者是什么。

答案 1 :(得分:1)

扩展Bozho:

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

java.io.Serializable APISun's article about Serialization中的更多相关信息。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

ArrayList使用AbstractList的{​​{1}}实施。这是这样定义的:

  

将指定对象与此列表进行比较以获得相等性。当且仅返回true   如果指定的对象也是一个列表,则两个列表都具有相同的大小   两个列表中相应的元素对是相等的。 (两个元素e1和e2是   等于if(e1 == null?e2 == null:e1.equals(e2))。)换句话说,两个列表被定义为&gt;如果它们包含相同顺序的相同元素,则相等。

     

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

如果没有比较你的equals(),那么在列表比较的早期部分中,比较可能已经失败了吗?您正在比较的列表是否可能没有相同数量的元素?

答案 4 :(得分: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-如果你覆盖equals你必须覆盖hashCode,我不知道你是故意删除它还是没有实现它。 equals()和hashCode()通过联合契约绑定在一起,该联合契约指定使用equals()方法是否认为两个对象相等,然后它们必须具有相同的哈希码值。 (借用SCJP书)。 否则,您将在HashMaps,HashSets和其他集合类中遇到这些classe的问题。

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

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

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

答案 5 :(得分:1)

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

另外,(再次是一个延迟加载的东西),你不应该直接在实体类中引用成员变量,你应该总是使用getter和setter。 (所以你的equals,toString,...方法应该使用getName()等。

答案 6 :(得分:0)

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

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

答案 7 :(得分: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;
}

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