При добавлении объектов в java.util.TreeSet
вы надеетесь, что два равных объекта будут существовать только один раз, когда оба будут добавлены, и следующий тест проходит, как и ожидалось:Почему я могу добавить два объекта, равных друг другу, в 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()
стат что бы мы ожидали:
Добавляет указанный элемент к этому набору, если он еще не присутствует. Более формально добавляет указанный элемент
e
к этому набору, если в наборе нет элементаe2
, такого как(e==null ? e2==null : e.equals(e2))
. Если этот набор уже содержит элемент, вызов оставляет значение неизменным и возвращаетfalse
.
Учитывая мое утверждение, что два объекта: equal()
, я бы ожидал, что это пройдет. Я работаю над тем, что мне не хватает чего-то мыслящего очевидного, если только TreeSet
не фактически использует Object.equals()
, но использует в большинстве случаев что-то такое же, что и в большинстве случаев.
Это наблюдалось с использованием JDK 1.8.0.60 - у меня не было возможности протестировать другие JDKs еще, но я предполагаю, что есть некоторые «Оператор Error» где-то ...
Мой фактический прецедент предназначен для кеша, который сортируется по необязательному полю ZonedDateTime, но возвращается к порядку вставки с помощью 'System.nanoTime()'. – Edd
Если равны и ваш компаратор смотрит на разные поля, почему они будут вести себя совместимым способом? –