关于通过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();
}
}
答案 0 :(得分:6)
List
在解除隔离后不为空。equals
方法中设置一个断点,看看是否有任何错误equals
上Car
的实施正确无误检查是否没有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
API和Sun'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;
}
我想知道是否有更优雅,通用的解决方案。