Этот ответ охватывает только первый пример в вопросе.Я думаю, что на оставшуюся часть вопроса и на различные правки лучше ответить как часть отдельных, сфокусированных вопросов.
В первом примере создается 12 экземпляров Dummy
, создается карта, сопоставляющая каждый экземпляр сInteger
в диапазоне [0, 3], а затем добавляет 12 Dummy
экземпляров к TreeSet
.Это TreeSet
предоставляется с компаратором, который использует карту Dummy-to-Integer.В результате TreeSet
содержит только четыре экземпляра Dummy
.Пример завершается следующим утверждением:
Результат явно неудовлетворительный (содержит меньше элементов, чем исходная коллекция).Это связано с тем, что такой компаратор не согласуется с равенством, т. Е. Иногда возвращает 0 для неравных элементов.
Последнее предложение неверно.Результат содержит меньше элементов, чем было вставлено, поскольку компаратор считает, что многие экземпляры являются дубликатами, и поэтому они не вставляются в набор.Метод equals
вообще не входит в обсуждение.Таким образом, концепция «в соответствии с равными» не имеет отношения к этой дискуссии.TreeSet
никогда не звонит equals
.Компаратор - единственное, что определяет членство в TreeSet
.
. Это кажется неудовлетворительным результатом, но только потому, что мы "знаем", что существует 12 различных Dummy
экземпляров.Однако TreeSet
не «знает», что они различны.Он знает только, как сравнить Dummy
экземпляров с помощью компаратора.Когда он это делает, он обнаруживает, что несколько являются дубликатами.То есть компаратор иногда возвращает 0, даже если он вызывается с Dummy
экземплярами, которые мы считаем отличимыми.Вот почему только четыре Dummy
экземпляра заканчиваются на TreeSet
.
. Я не совсем уверен, каков желаемый результат, но кажется, что результат TreeSet
должен содержать все 12 экземпляров, упорядоченныхзначения в карте Dummy-to-Integer.Мое предложение состояло в том, чтобы использовать Ordering.arbitrary()
в Guava, который предоставляет компаратор, который различает отличные, но в остальном равные элементы, но делает это таким образом, чтобы удовлетворить общий контракт Comparator.Если вы создадите TreeSet
следующим образом:
SortedSet<Dummy> sorted = new TreeSet<>(Comparator.<Dummy>comparingInt(map::get)
.thenComparing(Ordering.arbitrary()));
, результатом будет то, что TreeSet
содержит все 12 Dummy
экземпляров, отсортированных по значению Integer
на карте и с Dummy
экземпляры, которые отображаются на одно и то же значение в произвольном порядке.
В комментариях вы указали, что документ Ordering.arbitrary
"однозначно предостерегает против использования его в SortedSet
".Это не совсем верно;в этом документе написано:
Поскольку упорядочение основано на идентичности, оно не «соответствует Object.equals (Object)», как определено Comparator.Будьте осторожны при построении SortedSet или SortedMap из него, так как результирующая коллекция не будет вести себя точно в соответствии со спецификацией.
Фраза «не ведет себя точно в соответствии со спецификацией» действительно означает, что она будет вести себя «странно»"как описано в документе класса Comparator
:
Упорядочение, наложенное компаратором c на набор элементов S, называется в соответствии с равными тогда и только тогда, когда c.compare(e1, e2)==0
имеет то же логическое значение, что и e1.equals(e2)
для каждого e1 и e2 в S.
Следует соблюдать осторожность при использовании компаратора, способного наложить порядок, несовместимый с равнымзаказать отсортированный набор (или отсортированную карту).Предположим, что отсортированный набор (или отсортированная карта) с явным компаратором c используется с элементами (или ключами), взятыми из набора S. Если порядок, наложенный c на S, не соответствует равенствам, отсортированный набор (или отсортированная карта) будетвести себя "странно".В частности, отсортированный набор (или отсортированная карта) будет нарушать общий контракт для набора (или карты), который определяется в терминах equals
.
Например, предположим, что один добавляет два элемента a и b так, что (a.equals(b) && c.compare(a, b) != 0)
в пустой TreeSet с компаратором c.Вторая операция добавления вернет true (и размер набора деревьев увеличится), потому что a и b не эквивалентны с точки зрения набора деревьев, даже если это противоречит спецификации метода Set.add.
Вы, кажется, указали, что это "странное" поведение было недопустимым, в том смысле, что Dummy
элементы, которые equals
не должны появляться в TreeSet
.Но класс Dummy
не переопределяет equals
, поэтому кажется, что здесь скрывается дополнительное требование.
В последующих редакциях вопроса есть несколько дополнительных вопросов, но, как я уже упоминал выше, Я думаю, что их лучше рассматривать как отдельный вопрос (ы).
ОБНОВЛЕНИЕ 2018-12-22
После перечитывания вопроса, редактирования и комментариев, я думаю, что 'мы наконец выяснили, что вы ищете.Требуется компаратор по любому объекту, который обеспечивает первичное упорядочение на основе некоторой int-значной функции, которая может привести к дублированию значений для неравных объектов (как определено методом equals
объектов).Следовательно, требуется вторичное упорядочение, которое обеспечивает общее упорядочение по всем неравным объектам, но которое возвращает ноль для объектов, которые equals
.Это подразумевает, что компаратор должен быть согласован с равными.
Guava's Ordering.arbitrary
приближается к тому, что он обеспечивает произвольное общее упорядочение по любым объектам, но он возвращает ноль только для объектов, которые идентичны (то есть ==
), но не для объектов, которые equals
.Таким образом, он несовместим с уравнениями.
Значит, вам нужен компаратор, который обеспечивает произвольное упорядочение по неравным объектам.Вот функция, которая создает ее:
static Comparator<Object> arbitraryUnequal() {
Map<Object, Integer> map = new HashMap<>();
return (o1, o2) -> Integer.compare(map.computeIfAbsent(o1, x -> map.size()),
map.computeIfAbsent(o2, x -> map.size()));
}
По сути, она присваивает порядковый номер каждому вновь увиденному неравному объекту и сохраняет эти числа в карте, хранящейся в компараторе.Он использует размер карты в качестве счетчика.Поскольку объекты никогда не удаляются с этой карты, размер и, следовательно, порядковый номер всегда увеличиваются.
(Если вы намерены использовать этот компаратор одновременно, например, в параллельной сортировке, HashMap
должно бытьзамените на ConcurrentHashMap
, а трюк размера следует изменить, чтобы использовать AtomicInteger
, который увеличивается при добавлении новых записей.)
Обратите внимание, что карта в этом компараторе создает записи для каждого неравного объекта, которыйкогда-либо видел.Если он прикреплен к TreeSet
, объекты будут сохраняться на карте компаратора даже после того, как они были удалены из TreeSet
.Это необходимо для того, чтобы при добавлении или удалении объектов со временем они сохраняли упорядоченность.Ordering.arbitrary
в Guava использует слабые ссылки, позволяющие собирать объекты, если они больше не используются.Мы не можем этого сделать, потому что нам нужно сохранить порядок неидентичных, но равных объектов.
Вы бы использовали это так:
SortedSet<Dummy> sorted = new TreeSet<>(Comparator.<Dummy>comparingInt(map::get)
.thenComparing(arbitraryUnequal()));
У вас былотакже спросил, что означает «хорошо определенный» в следующем:
Поведение набора четко определено, даже если его порядок не соответствует равным
Предположим, вымы должны были использовать TreeSet
, используя компаратор, который не совместим с равными, например, тот, который использует Ordering.arbitrary
Гуавы, показанный выше.TreeSet
все еще будет работать, как ожидалось, в соответствии с самим собойТо есть он будет поддерживать объекты в полном порядке, он не будет содержать никаких двух объектов, для которых компаратор возвращает ноль, и все его методы будут работать, как указано.Тем не менее, возможно, что существует объект, для которого contains
возвращает значение true (поскольку это вычисляется с использованием компаратора), но для которого equals
имеет значение false, если вызывается с объектом, фактически находящимся в наборе.
Например, BigDecimal
равно Comparable
, но его метод сравнения не совпадает с:
> BigDecimal z = new BigDecimal("0.0")
> BigDecimal zz = new BigDecimal("0.00")
> z.compareTo(zz)
0
> z.equals(zz)
false
> TreeSet<BigDecimal> ts = new TreeSet<>()
> ts.add(z)
> HashSet<BigDecimal> hs = new HashSet<>(ts)
> hs.equals(ts)
true
> ts.contains(zz)
true
> hs.contains(zz)
false
Это то, что означает спецификация, когда говорится, что вещи могут вести себя "странно".У нас есть два набора, которые равны.Тем не менее, они сообщают о различных результатах для contains
одного и того же объекта, а TreeSet
сообщает, что он содержит объект, даже если этот объект не равен объекту в наборе.