К основному контенту

Может ли TreeSet содержать дубликаты?

Не спешите давать ответ, а лучше запустите код, приведенный ниже.

Возможно вы подумаете, что это надуманный пример, но это лишь немного упрощенная версия того кода, с которым мне пришлось столкнуться на практике. Для начала о проблеме, которую был призван решить этот код. В списке объектов надо было оставить объекты с различными именами, причем неважно какой объект из всего набора с таким же именем останется. Сразу же уточню, что на практике в проекте используется Java 7 и класс объектов, добавляемых в TreeSet, не вложен в какой-либо другой, а приведенный здесь вариант использован для краткости.

Может возникнуть вопрос почему выбрано именно такое решение. Давайте разберемся, чтобы не повторять ошибку. Первое, что приходит в голову, когда из списка надо удалить дубликаты, - использовать Set. В данной ситуации HashSet не подходит, потому что он использует equals метод для сравнения и отсеивания дубликатов. Но наш программист не сдаётся и вспоминает, что есть же еще TreeSet, в документации по которому явно написано, что для сравнения (в том числе при вставке) используется метод compare переданного в конструктор компаратора и в TreeSet не добавляются объекты равные с уже добавленными по определению компаратора, хотя и не равные по определению метода equals, что вообще-то нарушает контракт интерфейса Set (sic!). Это же то, что надо, - решает наш программист и приступает к написанию компаратора. Для начала пишется интересующее нас сравнение и тут все ок. Так, а что возвращать для объектов, у которых имена разные? Ну, нам же порядок неважен, так что пускай будет 1, - думает программист (sic!). Осталось превратить Set обратно в список и всё, решение готово. А спустя некоторое время выясняется, что дубликаты как были так и остались, а значит надо разобраться что же пошло не так. Предлагаю сделать это вместе.

Для начала скажем, что в данном случае метод addAll просто вызывает метод add в цикле. А метод add выглядит следующим образом: где переменная m имеет тип TreeMap (для некоторых это сюрприз :) ).

Не будем спешить и почитаем документацию по TreeMap, в которой написано, что данный класс является реализацией интерфейса NavigableMap, в основе которой красно-чёрное дерево (вспоминаем, что это одно из самобалансирующихся двоичных деревьев поиска). Теперь смотрим реализацию метода put и видим, что аргумент метода сравнивается с уже имеющимися объектами, начиная с корня дерева и дальше по направлению, зависящему от знака сравнения (вправо, если + и влево, если -). После каждой вставки дерево перебалансируется, в результате чего корень может измениться, что и происходит в рассматриваемом примере. В нашем случае корень (первый добавленный объект) в итоге оказывается слева и добавляемый 4-й объект, идентичный ему, с ним уже не сравнивается (из-за особенностей реализации компаратора) и успешно добавляется. В итоге TreeSet содержит дубликат.

Вывод простой: знайте то, что используете.


Комментарии

мне кажется, что в данном конкретном случае проблема в компараторе...

Из за неверного компаратора и дерево построиться не может.

строку 23 заменить на
return item2.name.compareTo(item1.name);
надо бы...
Виталий написал(а)…
Конечно, компаратор криво написан, но немаловажно и то, как он применяется. Если бы при вставке он применялся к каждому уже добавленному объекту (что, конечно, неэффективно), то дубликатов бы в коллекции не было.

Популярные сообщения из этого блога

Одно приложение, несколько баз данных

Рецепт от Spring Boot Некоторое время назад мне довелось писать агрегатор информации, разбросанной по нескольким базам данных с разными схемами. Для реализации был выбран Spring Boot. Ну, потому что модный и судя по примерам существенно упрощает жизнь за счет умной автоконфигурации. В этой статье я опишу, что же необходимо сконфигурировать и как, в случае, если вы отошли от стандартного сценария. Первым делом, необходимо прописать настройки доступа к каждой из баз. Например, вот так: Следующим шагом создадим отдельный класс конфигурации (для удобства), в котором определим dataSources: Обратите внимание, как просто получить настройки с помощью @ConfigurationProperties. Правда, пришлось ввести вспомогательный класс BaseDataSourceProperties — наследник DataSourceProperties, в котором область видимости метода getDriverClassName расширена до public. И осталось совсем немного — сконфигурировать JPA-репозитории. Насчет немного я, конечно, пошутил :) В этой части предстоит больше

Обработка изображений с ImageMagick

ImageMagick ( http://www.imagemagick.org ) — набор утилит для создания, редактирования, конвертирования и просмотра растровых изображений. Графический режим необходим только для просмотра. Для остальных действий над изображениями достаточно консоли. То есть налицо два отличия от привычных редакторов растровых изображений (вроде GIMP или Krita): использование набора утилит вместо одной программы для операций над изображениями не требуется GUI. Очевидно, что таким инструментом вряд ли будут пользоваться художники, фотографы или дизайнеры. Чтобы разобраться для кого предназначен этот набор, предлагаю ознакомиться с предоставляемыми возможностями. Что умеет ImageMagick? Чтобы ответить на поставленный вопрос я перечислю входящие в набор утилиты, напишу какой функционал предоставляет каждая из них и, конечно же, приведу примеры использования. identify — информационная утилита, воспользовавшись которой можно узнать формат изображения и множество других его свойств (например, высоту,