【发布时间】:2016-09-24 22:26:39
【问题描述】:
向java.util.TreeSet 添加对象时,您希望两个相等的对象在都添加后只存在一次,并且以下测试按预期通过:
@Test
void canAddValueToTreeSetTwice_andSetWillContainOneValue() {
SortedSet<String> sortedSet = new TreeSet<>(Comparator.naturalOrder());
// String created in a silly way to hopefully create two equal Strings that aren't interned
String firstInstance = new String(new char[] {'H', 'e', 'l', 'l', 'o'});
String secondInstance = new String(new char[] {'H', 'e', 'l', 'l', 'o'});
assertThat(firstInstance).isEqualTo(secondInstance);
assertThat(sortedSet.add(firstInstance)).isTrue();
assertThat(sortedSet.add(secondInstance)).isFalse();
assertThat(sortedSet.size()).isEqualTo(1);
}
将这些字符串包装在包装类中,其中 equals() 和 hashCode() 仅基于包装类,但测试失败:
@Test
void canAddWrappedValueToTreeSetTwice_andSetWillContainTwoValues() {
SortedSet<WrappedValue> sortedSet = new TreeSet<>(Comparator.comparing(WrappedValue::getValue).thenComparing(WrappedValue::getCreationTime));
WrappedValue firstInstance = new WrappedValue("Hello");
WrappedValue secondInstance = new WrappedValue("Hello");
assertThat(firstInstance).isEqualTo(secondInstance); // Passes
assertThat(sortedSet.add(firstInstance)).isTrue(); // Passes
assertThat(sortedSet.add(secondInstance)).isFalse(); // Actual: True
assertThat(sortedSet.size()).isEqualTo(1); // Actual: 2
}
private class WrappedValue {
private final String value;
private final long creationTime;
private WrappedValue(String value) {
this.value = value;
this.creationTime = System.nanoTime();
}
private String getValue() {
return value;
}
private long getCreationTime() {
return creationTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof WrappedValue)) return false;
WrappedValue that = (WrappedValue) o;
return Objects.equals(this.value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
The JavaDoc for TreeSet.add() 说明了我们的预期:
如果指定元素尚不存在,则将其添加到此集合中。更正式地说,如果集合不包含元素
e2,则将指定元素e添加到此集合中,这样(e==null ? e2==null : e.equals(e2))。如果此集合已包含该元素,则调用保持集合不变并返回false。
鉴于我断言这两个对象是equal(),我希望这会通过。我正在假设我遗漏了一些非常明显的东西,除非TreeSet 不实际上使用Object.equals(),但在绝大多数情况下使用的东西几乎相同.
这是使用 JDK 1.8.0.60 观察到的 - 我还没有机会测试其他 JDK,但我假设某处存在一些“操作员错误”......
【问题讨论】:
-
我的实际用例是一个按可选 ZonedDateTime 字段排序的缓存,但使用
System.nanoTime()回退到插入顺序。 -
如果 equals 和您的比较器查看不同的字段,为什么它们会以兼容的方式运行?
标签: java equals treeset sortedset