我有一个树状结构,需要重写equals/hashCode方法,因为在单元测试中需要检查预期结果。
树类型结构的问题在于它们会递归地相互引用,尤其是父节点和子节点之间。
如果在equals/hashCode方法中使用了所有字段,就会出现循环引用的情况。问题在于如何正确地重写这些方法,以避免违反协议。
我将举个例子来说明我是如何实现的。
public class App {
public static void main(String[] args) {
Book book1 = new Book(1L, "The catcher in the rye");
Book book2 = new Book(2L, "Rich Dad Poor Dad");
BookTree bookTree1 = new BookTree(book1);
BookTree bookTreeChild1 = new BookTree(book2);
bookTree1.addChild(bookTreeChild1);
BookTree bookTree2 = new BookTree(book1);
BookTree bookTreeChild2 = new BookTree(book2);
bookTree2.addChild(bookTreeChild2);
if (!bookTree1.equals(bookTree2)) {
throw new RuntimeException("Invalid override equals");
}
}
}
class Book {
private Long id;
private String name;
public Book(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Book book = (Book) object;
return Objects.equals(id, book.id) &&
Objects.equals(name, book.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
class Tree<T> {
private List<Tree<T>> children = new ArrayList<>();
private Tree<T> parent = null;
private T data;
public Tree(T data) {
this.data = data;
}
public Tree(T data, Tree<T> parent) {
this.data = data;
parent.addChild(this);
}
public List<Tree<T>> getChildren() {
return children;
}
public void addChild(Tree<T> child) {
child.setParent(this);
this.children.add(child);
}
public void addChild(T data) {
Tree<T> newChild = new Tree<>(data);
this.addChild(newChild);
}
public void removeChildren() {
this.children = new ArrayList<>();
}
public void addChildren(List<Tree<T>> children) {
for(Tree<T> t : children) {
t.setParent(this);
}
this.children.addAll(children);
}
private void setParent(Tree<T> parent) {
this.parent = parent;
}
public Tree<T> getParent() {
return parent;
}
public T getData() {
return this.data;
}
public void setData(T data) {
this.data = data;
}
public boolean isRoot() {
return (this.parent == null);
}
public boolean isLeaf() {
return this.children.size() == 0;
}
public void removeParent() {
this.parent = null;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Tree<?> tree = (Tree<?>) object;
return Objects.equals(children, tree.children) &&
Objects.equals(data, tree.data);
}
@Override
public int hashCode() {
return Objects.hash(children, data);
}
}
class BookTree extends Tree<Book> {
public BookTree(Book data) {
super(data);
}
public BookTree(Book data, Tree<Book> parent) {
super(data, parent);
}
}
从我的实现中可以看出,我仅使用了两个字段:“data”和“children”。 因此,我的问题是我是否正确实现了equals/hashCode方法? 如果不正确,请指出如何改正。
hashCode()
随时间变化而返回不同的值”。在典型的OO设计中,无法避免随时间的变化...如果一个类不喜欢它...那就是一个大问题。我最近发现JTree不喜欢没有实现hashCode()
和equals()
,而且在DefaultMutableTreeNode
中也没有实现。 “Mutable”意味着随时间的变化。JTree.getRowForPath
没有hashCode()
就无法工作。有这么多问题。 - JasonJTree
使用VariableHeightLayoutCache
来存储TreePath
到TreeStateNode
的映射。TreePath.hashCode
委托给树路径中的最后一个节点,即TreeNode
。许多实现使用DefaultMutableTreeNode
,它没有实现hashCode
(使用Object.hashCode
,它基于实例而不是对象属性的值或两个实例的逻辑相等性而变化)。因此,当查询它们的JTree
时,具有相同逻辑值的两个TreePath
将产生不同的结果。 - JasonhashCode
和equals
的问题,但我认为这很相关,因为人们也不会预期在此处需要hashCode
。一般来说,最好重写equals
,因为这是一个基本操作,而默认实现是不够的。必须也重写hashCode
,以满足Java协议,即如果equals
返回true,则hashCode
值相等。所以我认为OP想要实现这些是可以的,但是,OP必须定义什么构成相等。 - Jason