如何在Java中正确实现树的equals()、hashCode()?

如何在Java中正确实现树的equals()、hashCode()?,java,tree,equals,hashcode,Java,Tree,Equals,Hashcode,我有一个树结构,我需要重写equals/hashCode方法,因为我在单元测试中使用了对预期结果的检查 树型结构的问题在于,它们递归地相互引用。特别是,父母为子女服务,反之亦然 如果方法equals/hashCode中使用了所有字段,那么将出现循环。问题是如何正确地覆盖然后,以不违反合同 我将给出一个如何实现它的示例 public class App { public static void main(String[] args) { Book book1 = new B

我有一个树结构,我需要重写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);
    }
}
公共类应用程序{
公共静态void main(字符串[]args){
Book book1=新书(1L,“麦田里的守望者”);
Book book2=新书(2L,“富爸爸穷爸爸”);
BookTree bookTree1=新的BookTree(book1);
BookTree bookTreeChild1=新的BookTree(book2);
bookTree1.addChild(bookTreeChild1);
BookTree bookTree2=新的BookTree(book1);
BookTree bookTreeChild2=新的BookTree(book2);
bookTree2.addChild(bookTreeChild2);
如果(!bookTree1.equals(bookTree2)){
抛出新的RuntimeException(“无效覆盖等于”);
}
}
}
课堂用书{
私人长id;
私有字符串名称;
公共图书(长id,字符串名称){
this.id=id;
this.name=名称;
}
公共长getId(){
返回id;
}
公共无效集合id(长id){
this.id=id;
}
公共字符串getName(){
返回名称;
}
公共void集合名(字符串名){
this.name=名称;
}
@凌驾
公共布尔等于(对象){
如果(this==对象)返回true;
如果(object==null | | getClass()!=object.getClass())返回false;
Book=(Book)对象;
返回Objects.equals(id,book.id)&&
Objects.equals(name,book.name);
}
@凌驾
公共int hashCode(){
返回Objects.hash(id,name);
}
}
类树{
private List children=new ArrayList();
私有树父级=null;
私有T数据;
公共树(T数据){
这个数据=数据;
}
公共树(T数据,树父级){
这个数据=数据;
parent.addChild(this);
}
公共列表getChildren(){
返回儿童;
}
公共void addChild(树子级){
child.setParent(this);
this.children.add(child);
}
公共void addChild(T数据){
Tree newChild=新树(数据);
这是addChild(newChild);
}
公共无效删除儿童(){
this.children=new ArrayList();
}
公共子项(列出子项){
用于(树t:儿童){
t、 setParent(本);
}
this.children.addAll(children);
}
私有void setParent(树父级){
this.parent=parent;
}
公共树getParent(){
返回父母;
}
公共T getData(){
返回此.data;
}
公共无效设置数据(T数据){
这个数据=数据;
}
公共布尔值isRoot(){
返回(this.parent==null);
}
公共布尔isLeaf(){
返回此.children.size()==0;
}
public void removeParent(){
this.parent=null;
}
@凌驾
公共布尔等于(对象){
如果(this==对象)返回true;
如果(object==null | | getClass()!=object.getClass())返回false;
Tree=(Tree)对象;
返回对象.equals(子对象,树.children)&&
Objects.equals(数据,tree.data);
}
@凌驾
公共int hashCode(){
返回Objects.hash(子对象、数据);
}
}
类BookTree扩展了BookTree{
公共图书目录树(图书数据){
超级(数据);
}
公共BookTree(书本数据、树父级){
超级(数据、父级);
}
}
从我的实现中可以看到,我只使用了两个字段:“数据”和“子项”。 因此,我的问题是我是否正确地实现了equals/hashCode方法? 如果是错误的,那么请说明如何

因此,我的问题是我是否正确地实现了equals/hashCode方法

首先:“什么是正确的?”。。。人们可能想知道为什么树首先应该实现
equals()
hashCode()
。尤其是
hashCode()
是一个棘手的问题:该方法的要点是(主要是)可以将相应的对象存储在HashMap/HashSet中。但这会引起一个大问题:当
hashCode()
随时间返回不同的值时,这两个类都不喜欢它。这正是您的代码将要做的:每次更改树(添加/删除节点),
hashCode()
将给出不同的结果

所以我们可以看看标准LIB的功能:在那里我们发现。。。这两种方法都不能实现!另一方面,当我们展望(TreeSet的基类)时,我们发现这两个方法都实现了,并且包含了成员。所以这两种方法似乎都是有效的

回到问题上来:这实际上取决于您希望这两种方法如何工作。当两棵树的内容完全相同时,它们是否相等(意思是:子树的顺序重要吗)

长话短说:假设您希望确保所有数据都是平等的,并且所有子项都是平等的,并且顺序相同,那么您的实现似乎是正确的

是的,只检查这两个属性的限制很有意义:当您包含父链接时,您会立即进入一个无法中断的递归

最后:你用JUnit标记了这个问题。这意味着您考虑为生产代码编写测试。那么这些测试应该能回答你的问题。意思:一种方法是你坐下来定义这两种方法的契约。然后你创建了一些测试c