我正在尝试从破解编码面试回答以下问题。下面的代码是GitHub上项目的一部分,here。
给定一个二叉搜索树,设计一个算法,创建每个深度的所有节点的链表(即,如果你有一个深度为D的树,你将有D个链表)。
作为一名优秀的小开发人员,我写了一个单元测试来检查这一点。
@Test
public void testGetValuesAtEachLevel() {
Integer[] treeValues = {
1, 2, 3, 4, 5,
6, 7, 8, 9, 10,
11, 12, 13, 14, 15
};
tree = new GenericBinaryTree<>(treeValues);
Integer[][] expectedArray = {
{ 1 },
{ 2, 3 },
{ 4, 5, 6, 7 },
{ 8, 9, 10, 11, 12, 13, 14, 15 }
};
List<List<Node<Integer>>> expectedList = new ArrayList<>(4);
for (Integer[] level : expectedArray) {
List<Node<Integer>> list = new LinkedList<>();
for (Integer value : level) {
list.add(new Node<>(value));
}
expectedList.add(list);
}
assertEquals(expectedList, tree.getValuesAtEachLevel());
}
这是代码。
List<List<Node<T>>> getValuesAtEachLevel() {
List<List<Node<T>>> results = new ArrayList<>();
List<Node<T>> firstLevel = new LinkedList<>();
firstLevel.add(getRoot());
results.add(firstLevel);
loadAtLevel(results, 1);
return results;
}
private void loadAtLevel(List<List<Node<T>>> list, int level) {
List<Node<T>> levelList = new LinkedList<Node<T>>();
for (Node<T> node : list.get(level - 1)) {
if (node.left() != null) levelList.add(node.left());
if (node.right() != null) levelList.add(node.right());
}
if (levelList.isEmpty()) return;
level++;
list.add(levelList);
loadAtLevel(list, level);
}
想象一下,当单元测试因以下错误而失败时我会感到惊讶:
java.lang.AssertionError: expected:
java.util.ArrayList<[[1], [2, 3], [4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14, 15]]> but was:
java.util.ArrayList<[[1], [2, 3], [4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14, 15]]>
字符串表示是相同的,所以我无法弄清楚这里发生了什么。事实上,如果我将断言更改为此,则测试通过:
assertEquals(expectedList.toString(), tree.getValuesAtEachLevel().toString());
我意识到我即将学习有关接口和对象的非常有价值的东西。我在这做错了什么?
答案 0 :(得分:5)
正如@Jon Skeet在评论中指出的那样,您需要覆盖equals
课程的hashCode
和Node
,以进行比较(equals
)在其实例上。
示例(使用intelliJ默认自动生成)可以是 -
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Node<?> node = (Node<?>) o;
if (left != null ? !left.equals(node.left) : node.left != null) return false;
if (right != null ? !right.equals(node.right) : node.right != null) return false;
if (parent != null ? !parent.equals(node.parent) : node.parent != null) return false;
return value != null ? value.equals(node.value) : node.value == null;
}
@Override
public int hashCode() {
int result = left != null ? left.hashCode() : 0;
result = 31 * result + (right != null ? right.hashCode() : 0);
result = 31 * result + (parent != null ? parent.hashCode() : 0);
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
另一方面为什么
assertEquals(expectedList.toString(),tree.getValuesAtEachLevel().toString());
的工作原理。
这是因为你最终在这里断言的是比较两个String
n @Override
定义here
equals
个实例
答案 1 :(得分:4)
很可能是因为
new Node().equals(new Node()) == false
覆盖equals()
hashCode()
和Node
答案 2 :(得分:1)
断言两个对象的字符串表示是等于的,这些对象是相同的是两个不同的东西
对象相等性依赖于boolean equals(Object o)
类中的Object
方法
如果以int hashcode()
方法的对称方式覆盖而不是在任何情况下(默认情况下)返回唯一值,equals(Object o)
可以提高性能,但对于断言,它不会改变结果。当您覆盖equals()
时,一个好的做法是覆盖两者。
在您的情况下,您有多种处理方式。我建议你3种常见方式:
覆盖equals()
和hashcode()
它们的属性允许以某种方式定义它们,它实际上定义了对象的身份。覆盖equals()
和hashcode()
以在单元测试中成功断言不足以覆盖该方法。
此外,equals()
覆盖可能适合此测试,不适合其他测试。
equals()
和hashcode()
方法的实现必须符合标识对象的方式以及如何在应用程序中使用此类,而不仅仅是单元测试断言。
这是另一个提出问题的帖子:Should one override equals method for asserting the object equality in a unit test?
在循环中执行断言。
例如,循环实际结果并使用您为预期结果创建的2D数组逐节点地执行断言:
int i=0;
for (List<Node<Integer>>> listOfNodes : tree.getValuesAtEachLevel()) {
int j=0;
for (Node node : listOfNodes ) {
list.add(new Node<>(value));
// I called the method getValue() because I don't know the getter
Assert.assertEquals(expectedArray[i][j], node.getValue();
// You could also display a JUnit failure message if you want
j++;
}
i++;
}
如果你必须在其他几个场景中为列表列表做断言,那么继续进行的方式可能是笨拙和耗时的,并且执行断言的方式是非常不同的。但是通过合适的代码重构,我们可以在许多场景中以可重用的方式提取util断言方法。
Unitils库提供此功能和其他功能。
http://www.unitils.org/tutorial-reflectionassert.html
使用Unitils,你可以这样做:
ReflectionAssert.assertReflectionEquals(expectedList, tree.getValuesAtEachLevel());
不支持equals(Object o)
和hashcode()
如果断言失败,Unitils的另一个优点是相当友好的消息。