У меня проблема с манипуляциями HashSet и TreeSet.
Вот простой тест JUnit 4, объясняющий мою проблему:
import java.util.HashSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Test;
public class TreeSetTest<T> {
@Test
public void test() {
final HashSet<Object> set1 = new HashSet<>();
final TreeSet<Object> set2 = new TreeSet<>((a, b) -> a.toString().compareTo(b.toString()));
Assert.assertEquals(0, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set1.add(new AtomicReference<>("A"));
set1.add(new AtomicReference<>("B"));
set1.add(new AtomicReference<>("C"));
Assert.assertEquals(3, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set2.add(new AtomicReference<>("A"));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK Nothing has been removed
Assert.assertEquals(1, set2.size());
set2.add(new AtomicReference<>("B"));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK Nothing has been removed
Assert.assertEquals(2, set2.size());
set2.add(new AtomicReference<>("C"));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // Error Everything has been removed and size is now 0
Assert.assertEquals(3, set2.size());
}
}
При удалении всех элементов set2
из set1
я ожидаю использовать компаратор равенства set1
, который имеет место до тех пор, пока set2
имеет размер меньше, чем set1
но если размер set2
больше или равен размеру set1
, сравнение производится из set2
.
Это очень плохо для меня, потому что делает программу непредсказуемой.
Я думаю, это можно рассматривать как ошибку в реализации Java, но моя проблема:
Как я могу гарантировать ожидаемое поведение, не переписывая все?
Изменить 1 после комментария @FedericoPeraltaSchaffner:
AtomicReference был только для того, чтобы предоставить простой пример. На самом деле, я использую класс, который является окончательным из библиотеки, поэтому я не могу его легко улучшить.
Но даже если учесть, что правильный класс реализует hashCode
и equals
, моя проблема остается. Рассмотрим это сейчас:
package fr.ncenerar.so;
import java.util.HashSet;
import java.util.TreeSet;
import org.junit.Assert;
import org.junit.Test;
public class TreeSetTest<T> {
public static class MyObj {
private final int value1;
private final int value2;
public MyObj(final int v1, final int v2) {
super();
this.value1 = v1;
this.value2 = v2;
}
public int getValue1() {
return this.value1;
}
public int getValue2() {
return this.value2;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.value1;
result = prime * result + this.value2;
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final MyObj other = (MyObj) obj;
if (this.value1 != other.value1) {
return false;
}
if (this.value2 != other.value2) {
return false;
}
return true;
}
}
@Test
public void test() {
final HashSet<MyObj> set1 = new HashSet<>();
final TreeSet<MyObj> set2 = new TreeSet<>((a, b) -> a.getValue1() - b.getValue1());
Assert.assertEquals(0, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set1.add(new MyObj(0, 0));
set1.add(new MyObj(1, 1));
set1.add(new MyObj(2, 2));
Assert.assertEquals(3, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set2.add(new MyObj(0, 1));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK Nothing has been removed
Assert.assertEquals(1, set2.size());
set2.add(new MyObj(1, 2));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK Nothing has been removed
Assert.assertEquals(2, set2.size());
set2.add(new MyObj(2, 3));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // Error Everything has been removed
Assert.assertEquals(3, set2.size());
}
}
Проблема все еще существует, и реализация MyObj
верна. Проблема заключается в том, что я использую объекты с двух разных сторон. В одном наборе я хочу сохранить один экземпляр каждого объекта на основе их равенства (как в equals
методе объекта), а в другом наборе я хочу подмножество первого набора, в котором для каждого value1
я хотите сохранить только первый вставленный элемент.
Использование TreeSet
показалось правильным.
Редактировать 2:
Плохо, я пропустил ту часть TreeSet
документации:
Обратите внимание, что порядок поддерживается набором (независимо от того,
явный компаратор предоставляется) должен соответствовать равным, если он
это правильно реализовать интерфейс Set. (См. Сравнительный или
Компаратор для точного определения последовательных выражений.) Это
так, потому что интерфейс Set определяется через равные
операция, но экземпляр TreeSet выполняет все сравнения элементов
используя его метод CompareTo (или сравнение), поэтому два элемента, которые
считаются равными по этому методу, с точки зрения множества равны.
Поведение набора четко определено, даже если его порядок
несовместимый с равными; он просто не соблюдает общий договор
Настроить интерфейс.
Если я правильно понимаю, я могу использовать TreeSet
для своих целей, но не могу ожидать, что он будет вести себя так, как я хочу.
Спасибо всем за помощь.