логическое несоответствие между интерфейсами Set и SortedSet в Java

Я заметил логическое несоответствие между интерфейсами Set и SortedSet в Java.

SortedSet распознает разные объекты (методом equal()) как равные, если они совпадают при сравнении, но это логически неверно. Сравнение объектов должно отвечать только за порядок объектов.

Например: у меня может быть много товаров, и я хочу отсортировать их по цене. При этом SortedSet не может содержать разные товары с одинаковой ценой: [“соль”, 0,5$], [“молоко”, 1$], [“хлеб”, 1$], [“бананы”, 2$ ] В приведенном выше примере молоко будет заменено хлебом. В этом случае контракт унаследованного интерфейса Set будет нарушен, так как неравные объекты заменяют друг друга. Я прочитал JavaDoc SortedSet и знаю, что это поведение хорошо задокументировано, но я думаю, что это логическая ошибка.

Каково ваше мнение, может у вас уже были подобные проблемы с Set и SortedSet?


person thinker    schedule 26.01.2013    source источник
comment
Почему вы думаете, что использовать equals() для равенства неправильно? И что вы подразумеваете под «неравными объектами сменяют друг друга»?   -  person user207421    schedule 26.01.2013
comment
Иногда я хочу сортировать продукты по имени, а иногда по дате. Если бы я реализовал сравнение по цене прямо в методе Product.equals(), то не смог бы сортировать товары по дате. Кроме того, логически продукты не равны, если они имеют одинаковую цену.   -  person thinker    schedule 26.01.2013
comment
Затем вам нужно использовать два разных компаратора.   -  person user207421    schedule 27.01.2013


Ответы (3)


Это не логическая ошибка, а свойство по замыслу. Обратите внимание, что если ваш алгоритм сравнения совместим с равными, a.compareTo(b) == 0 будет эквивалентен a.equals(b).

В javadoc об этом очень подробно говорится:

Поведение отсортированного набора четко определено, даже если его порядок несовместим с равными; он просто не подчиняется общему контракту интерфейса Set.

Это полезно?

Это на самом деле дает гибкость, когда вы хотите достичь определенного поведения. Допустим, например, что вы хотите поместить строки в свой SortedSet и отсортировать их без учета регистра, вы можете использовать:

Set<String> set = new TreeSet<> (String.CASE_INSENSITIVE_ORDER);

Это приведет к ожидаемому результату, но это:

set.add("ABC");
set.add("abc");

добавит только одну строку в ваш набор, потому что они будут считаться равными этому конкретному компаратору.

person assylias    schedule 26.01.2013
comment
Спасибо за ваш ответ. Иногда я хочу отсортировать продукты по имени, а иногда по дате. Если бы я реализовал метод сравнения непосредственно в классе продуктов и определил бы продукты равными по цене, я бы не смог сортировать продукты по дате. Я думаю, что «упорядочивание» и «справедливость» — разные аспекты, и их следует разделять. - person thinker; 26.01.2013
comment
Проблема с этим утверждением на самом деле в том, что не существует эффективного способа реализации структуры данных, которая отделяет равенство от сравнения. - person Louis Wasserman; 26.01.2013
comment
Спасибо за понимание, свою версию TreeSet я реализовал на основе TreeList из коллекций apache. Он отлично работает, я тестировал его с коллекцией Apache SetTest. Я опубликую его со следующей версией моей библиотеки с открытым исходным кодом. Новый TreeSet распознает элементы как равные не с помощью компаратора, а с помощью метода equals() и обратно совместим с обычным набором. Таким образом, новая реализация TreeSet будет содержать те же элементы, что и HashSet, но в упорядоченном виде. - person thinker; 27.01.2013

Вы, вероятно, имеете в виду метод equals(), который на самом деле доступен в Object, поэтому каждый отдельный тип объекта в Java либо переопределяет его, либо наследует от Object.

person Dan D.    schedule 26.01.2013

Вообще говоря, в Java два объекта a и b совпадают, если a.equals(b) == true. И набор, каждый набор, даже SortedSet, не имеет дубликатов, поэтому, если вы переопределите equals для сравнения свойства цены, два объекта будут одинаковыми, если они имеют одинаковую цену.

Я предполагаю, что вы не переопределяете метод equals, а предоставляете от Comparator до SortedSet, чтобы сделать заказ. В этом случае, как сказано в другом ответе, если a.compareTo(b) == 0 будет эквивалентно a.equals(b).

Если вы хотите упорядочить этот набор, вам нужна другая структура, например список, для упорядочивания. Что-то типа:

List<Product> list = new ArrayList<Product>(myUnorderedProductsSet);
Collections.sort(list, comparatorByPrice);

Обратите внимание, что это не будет дублировать или копировать ваши компоненты продукта, просто будет другая структура со ссылками на одни и те же объекты в другом порядке. Так что это не что-то дорогое или избежать.

person Pedro R.    schedule 26.01.2013
comment
Если вы хотите упорядочить этот набор, вам нужна другая структура => Не уверен, что понимаю вас: SortedSet по определению... отсортирован. Поэтому нет необходимости явно сортировать его снова. - person assylias; 26.01.2013
comment
Два объекта равны, если obj1.equals(obj2)==true, а не если объекты сравниваются друг с другом. Например, иногда я хочу сортировать продукты по имени, а иногда по дате. Если бы я реализовал метод сравнения прямо в классе товаров и сравнил бы товары по цене, то не смог бы сортировать товары по дате. Я думаю, что «упорядочивание» и «справедливость» — разные аспекты, и их следует разделять. - person thinker; 26.01.2013
comment
@assylias Да, набор отсортирован, но в нем нет повторяющихся элементов, учитывая критерии сортировки, которые вы выбираете только для того, чтобы быть набором. Вам нужна другая структура, которая не поддерживает повторяющиеся элементы. Например, Список. И тогда вы можете сохранить повторяющиеся элементы. - person Pedro R.; 26.01.2013
comment
@thinker Думаю, я понимаю твою точку зрения, но Java так не работает. - person Pedro R.; 26.01.2013
comment
Спасибо за понимание, свою версию TreeSet я реализовал на основе TreeList из коллекций apache. Он отлично работает, я тестировал его с коллекцией Apache SetTest. Я опубликую его со следующей версией моей библиотеки с открытым исходным кодом. Новый TreeSet распознает элементы как равные не с помощью компаратора, а с помощью метода equals() и обратно совместим с обычным набором. Таким образом, новая реализация TreeSet будет содержать те же элементы, что и HashSet, но в упорядоченном виде. - person thinker; 27.01.2013