序列化后在 ArrayList 上调用 equals
我遇到了一个与通过 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
List
不为空。equals
方法中放置一个断点,看看是否没有发生任何错误。Car
上的equals
实现正确检查是否没有
transient
字段检查是否是您期望的
ArrayList
实际上并不是PersistentBag
。因为它的equals
不会做你想要的。如果它是 PersistentBag,您可以在通过线路发送之前将其传输到 ArrayList(从而防止潜在的 LazyInitializationException),或者调用 equals在每个元素上而不是在List
本身上。 Hibernate 使用PersistentBag
包装您的集合以提供延迟加载功能如果您使用 Hibernate 以外的 JPA 提供程序,也许它有类似的集合包装器。指出您的持久性提供者是什么。
List
isn't empty after deseriazliation.equals
method and see if anything wrong isn't happeningequals
onCar
is correctCheck if there are no
transient
fieldsCheck if what you expect to be an
ArrayList
isn't actuallyPersistentBag
. Because itsequals
won't do what you want. If it isPersistentBag
, you can either transfer it to anArrayList
before sending over the wire (thus preventing a potentialLazyInitializationException
), or call equals on each element rather than on theList
itself. Hibernate usesPersistentBag
to wrap your collections in order to provide lazy loadingP.S. If you are usingi a JPA provider other than Hibernate, perhaps it has a similar collections wrappers. Indicate what's your persistence provider.
扩展Bozho:
serialVersionUID
(即具有完全相同的编译版本)。如有必要,将其硬编码为private static final long
类变量。有关此内容的更多信息,请参见
java.io。 Serialized
API 和 Sun 关于序列化的文章 。To expand Bozho:
serialVersionUID
(i.e. are of the exact same compiled versions). If necessary hardcode it asprivate static final long
class variable.More about this in the
java.io.Serializable
API and Sun's article about Serialization.您提到您正在使用 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).
ArrayList
使用AbstractList
的equals()
实现。定义如下:如果您的
Car
没有被比较,也许比较在列表比较的早期部分就已经失败了?您正在比较的列表是否可能没有相同数量的元素?ArrayList
usesAbstractList
s implementation ofequals()
. That is defined like this:If your
Car
s 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?如果您安装了 Java 源代码,您可以通过 AbstractList equals 实现进行调试并查看哪里失败。
Java 1.6 的当前实现是:
除此之外,还有一些注释,尽管我认为它们与您的问题无关:
1-如果您重写 equals,则必须重写 hashCode,我不知道您是否已删除故意这样做或者如果您没有实施它。 equals() 和 hashCode() 通过联合契约绑定在一起,该契约指定如果使用 equals() 方法将两个对象视为相等,则它们必须具有相同的 hashcode 值。 (借自 SCJP 书籍)。
否则,您将在 HashMap、HashSet 和其他集合类中遇到这些类的问题。
2-在您的 equals 实现中,instanceof 检查 null 性和类类型,您可以替换
为
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:
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
with
您可能会获得对象的子类(我知道 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.
只是猜测:当您发送
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 theequals
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
.我刚刚遇到了同样的问题。当 Hibernate 将列表替换为其 PersistentBag 类时,JUnit 对 Hibernate 返回的集合的assertEquals() 将失败,因为该类未正确实现 equals()。以下是来自 Hibernate 3.5.1-Final PersistedBag 类的代码:
从阅读他们的评论来看,他们这样做似乎是出于性能/效率的原因。但如果您有一个包含列表的对象,它会使单元测试变得困难。我的解决方案将编写一个 areEqualNonNullLists(listA, listB) 方法并将其放入包含列表的对象的 eqauls() 方法中。
我想知道是否有更优雅、更通用的解决方案。
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:
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.
I wonder if there's a more elegant, generic solution.