为什么我似乎能够将两个彼此相等的对象添加到 TreeSet
Why do I seem able to add two objects that are equal() to each other to a TreeSet
将对象添加到 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);
}
将这些字符串包装在包装器 class 中,其中 equals()
和 hashCode()
仅基于包装的 class,但测试失败:
@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()
陈述了我们的预期:
Adds the specified element to this set if it is not already present. More formally, adds the specified element e
to this set if the set contains no element e2
such that (e==null ? e2==null : e.equals(e2))
. If this set already contains the element, the call leaves the set unchanged and returns false
.
鉴于我断言这两个对象是 equal()
,我希望这会通过。我正在假设我遗漏了一些非常明显的东西,除非 TreeSet
没有 实际上使用 Object.equals()
,而是使用了足够接近的东西在绝大多数情况下都是一样的。
这是使用 JDK 1.8.0.60 观察到的 - 我还没有机会测试其他 JDK,但我假设某处有一些 "Operator Error".. .
您的 equals(仅考虑 value
)与您的 Comparator 不一致,后者考虑了 value
和 creationTime
。
我假设您有两个具有相同值的对象,因此它们等于 true 但创建时间不同,因此它们是 compareTo != 0。
问题是给定的用于对集合进行排序的比较器与 WrappedValue
的 equals
方法不兼容。您期望 SortedSet
表现得像 Set
,但在这种情况下它不会这样做。
来自 SortedSet
:
Note that the ordering maintained by a sorted set [...] must be consistent with equals
if the sorted set is to correctly implement the Set
interface. [...] This is so because the Set
interface is defined in terms of the equals
operation, but a sorted set performs all element comparisons using its compareTo
(or compare
) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal. The behavior of a sorted set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set
interface.
换句话说,SortedSet
仅使用您提供的比较器来确定两个元素是否相等。本例中的比较器是
Comparator.comparing(WrappedValue::getValue).thenComparing(WrappedValue::getCreationTime)
先比较值再比较创建时间。但是由于 WrappedValue
的构造函数使用 System.nanoTime()
初始化了一个(有效的)唯一创建时间,因此此比较器不会认为两个 WrappedValue
相等。因此,就排序集而言
WrappedValue firstInstance = new WrappedValue("Hello");
WrappedValue secondInstance = new WrappedValue("Hello");
是两个不同的对象。实际上,如果您稍微修改构造函数以添加 long creationTime
参数,并为两个实例提供相同的时间,您会注意到 "expected" 结果(即排序集的大小仅为1 添加两个实例后)。
所以这里有3种解法:
- 修复
equals
和 hashCode
方法,让它们比较值和时间。
- 只给一个comparator比较值
- 接受
SortedSet
在这种特殊情况下表现得不像 Set
的事实。
将对象添加到 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);
}
将这些字符串包装在包装器 class 中,其中 equals()
和 hashCode()
仅基于包装的 class,但测试失败:
@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()
陈述了我们的预期:
Adds the specified element to this set if it is not already present. More formally, adds the specified element
e
to this set if the set contains no elemente2
such that(e==null ? e2==null : e.equals(e2))
. If this set already contains the element, the call leaves the set unchanged and returnsfalse
.
鉴于我断言这两个对象是 equal()
,我希望这会通过。我正在假设我遗漏了一些非常明显的东西,除非 TreeSet
没有 实际上使用 Object.equals()
,而是使用了足够接近的东西在绝大多数情况下都是一样的。
这是使用 JDK 1.8.0.60 观察到的 - 我还没有机会测试其他 JDK,但我假设某处有一些 "Operator Error".. .
您的 equals(仅考虑 value
)与您的 Comparator 不一致,后者考虑了 value
和 creationTime
。
我假设您有两个具有相同值的对象,因此它们等于 true 但创建时间不同,因此它们是 compareTo != 0。
问题是给定的用于对集合进行排序的比较器与 WrappedValue
的 equals
方法不兼容。您期望 SortedSet
表现得像 Set
,但在这种情况下它不会这样做。
来自 SortedSet
:
Note that the ordering maintained by a sorted set [...] must be consistent with
equals
if the sorted set is to correctly implement theSet
interface. [...] This is so because theSet
interface is defined in terms of theequals
operation, but a sorted set performs all element comparisons using itscompareTo
(orcompare
) method, so two elements that are deemed equal by this method are, from the standpoint of the sorted set, equal. The behavior of a sorted set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of theSet
interface.
换句话说,SortedSet
仅使用您提供的比较器来确定两个元素是否相等。本例中的比较器是
Comparator.comparing(WrappedValue::getValue).thenComparing(WrappedValue::getCreationTime)
先比较值再比较创建时间。但是由于 WrappedValue
的构造函数使用 System.nanoTime()
初始化了一个(有效的)唯一创建时间,因此此比较器不会认为两个 WrappedValue
相等。因此,就排序集而言
WrappedValue firstInstance = new WrappedValue("Hello");
WrappedValue secondInstance = new WrappedValue("Hello");
是两个不同的对象。实际上,如果您稍微修改构造函数以添加 long creationTime
参数,并为两个实例提供相同的时间,您会注意到 "expected" 结果(即排序集的大小仅为1 添加两个实例后)。
所以这里有3种解法:
- 修复
equals
和hashCode
方法,让它们比较值和时间。 - 只给一个comparator比较值
- 接受
SortedSet
在这种特殊情况下表现得不像Set
的事实。