如何在Java中正确实现Tree的equals(),hashCode()?

时间:2019-05-31 06:50:45

标签: java junit tree equals hashcode

我有一个树形结构,我需要重写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);
    }
}

从我的实现中可以看到,我仅使用两个字段:“数据”和“子项”。 因此,我的问题是我是否正确实现了equals / hashCode方法? 如果有误,请说明如何操作。

1 个答案:

答案 0 :(得分:2)

  

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

首先:“什么是正确的?” ...您可能想知道为什么Tree首先要实现equals()hashCode()hashCode()特别棘手:该方法的重点(主要是),因此您可以将相应的对象存储在HashMap / HashSet中。但这引起了一个很大的危险信号:hashCode()随时间返回不同的值时,这两个类都。这正是您的代码将要执行的操作:每次更改树(添加/删除节点)时,hashCode()都会给出不同的结果。

因此,我们可以看一下标准库的功能:并发现JTree ...不能同时实现这两种方法!另一方面,当我们查看AbstractSet(它是TreeSet的基类)时,我们发现这两种方法都已实现并且包括成员。因此,两种方法似乎都是有效的。

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

长话短说:假设您要确保所有数据相等,并且所有 children 相等,并且顺序相同,那么您的实现似乎正确。

是的,仅检查这两个属性的 restriction 很有意义:当包含父链接时,您会立即进入不可中断的递归。

最后:您用JUnit标记了这个问题。这意味着您考虑为生产代码编写测试。然后这些测试将回答您的问题。意思是:一种方法是您坐下来并定义这两种方法的合同。然后,您创建许多测试用例,以验证这些合同的各个方面。然后您的测试用例会告诉您您的生产代码是否符合合同。

我认为这是关键点:没有通用的规则可以告诉我们/是否为Tree类实现equals()hashCode()。您必须研究您的要求(如果/如何执行)。然后,您从该知识中得出测试,然后将其应用,以验证给定的实现是否满足要求/合同。