如何在Java中正确实现树的equals()、hashCode()?
我有一个树结构,我需要重写equals/hashCode方法,因为我在单元测试中使用了对预期结果的检查 树型结构的问题在于,它们递归地相互引用。特别是,父母为子女服务,反之亦然 如果方法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
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