Java SortedSet + Comparator, вопрос согласованности с equals()

Я хотел бы иметь SortedSet коллекций (в данном случае сами наборы, но не обязательно в целом), которые сортируются по размеру коллекции. Кажется, это нарушает запрет на то, чтобы Comparator был совместим с equals() - т. Е. Две коллекции могут быть неравными (имея разные элементы), но сравниваться с одним и тем же значением (потому что они имеют одинаковое количество элементов).

Теоретически я мог бы также добавить в компаратор способы сортировки наборов одинакового размера, но использование сортировки не воспользуется этим преимуществом, и на самом деле нет полезного + интуитивно понятного способа сравнить коллекции одинакового размера (по крайней мере , в моем конкретном случае), так что это кажется пустой тратой времени.

Этот случай несоответствия кажется проблемой?


person Carl    schedule 02.10.2009    source источник


Ответы (5)


Как написал ChssPly76 в комментарии, вы можете использовать hashCode для определения вызова compareTo в случае, когда две коллекции имеют одинаковый размер, но не равны. Это работает нормально, за исключением редкого случая, когда у вас есть две коллекции одинакового размера, которые не равны, но имеют одинаковый хэш-код. Правда, шансы на это довольно малы, но вполне допустимы. Если вы хотите быть очень осторожным, вместо hashCode используйте System.identityHashCode. Это должно дать вам уникальный номер для каждой коллекции, и у вас не должно быть коллизий.

В конце концов, это дает вам функциональность сортировки коллекций в наборе по размеру с произвольным порядком в случае двух коллекций с одинаковым размером. Если это все, что вам нужно, это не намного медленнее, чем обычное сравнение. Если вам нужно, чтобы порядок был согласован между разными экземплярами JVM, это не сработает, и вам придется сделать это каким-то другим способом.

псевдокод:

if (a.equals(b)) {
    return 0;
} else if (a.size() > b.size()) {
    return 1;
} else if (b.size() > a.size()) {
    return -1;
} else {
    return System.identityHashCode(a) > System.identityHashCode(b) ? 1 : -1;
}
person joe p    schedule 02.10.2009
comment
нет в вопросе JVM, так как порядок наборов одинакового размера не имеет значения. Я надеялся избежать такого решения, но, похоже, выхода нет. - person Carl; 03.10.2009
comment
Я не думаю, что это правильно. Технически две коллекции одинакового размера, содержащие разные элементы, могут иметь одинаковый хэш-код идентификации, и в этом случае это сравнение будет асимметричным, поскольку a.compareTo(b) и b.compareTo(a) возвращают -1. Мы можем избежать этой конкретной проблемы, используя Integer.compare(System.identityHashCode(a), System.identityHashCode(b)), но это все равно оставляет нам сценарий, в котором (a.compareTo(b) == 0) != a.equals(b). - person shmosel; 16.12.2015

Интерфейс SortedSet расширяет ядро ​​Set и, следовательно, должен соответствовать контракту указано в спецификации Set.

Единственный возможный способ добиться этого — сделать так, чтобы поведение метода equal() вашего элемента соответствовало вашему Comparator — причина этого в том, что ядро ​​Set работает на основе равенства, тогда как SortedSet работает на основе сравнения.

Например, add()< Метод /a>, определенный в базовом интерфейсе Set, указывает, что вы не можете добавить элемент в набор, если уже есть элемент, чей метод equal() вернет true с этим новым элементом в качестве аргумента. Ну, SortedSet не использует equal(), он использует compareTo(). Поэтому, если ваш compareTo() возвращает false, ваш элемент БУДЕТ добавлен, даже если equals() должен был вернуть true, тем самым нарушая контракт Set.

Однако ни одна из этих проблем не является практической проблемой сама по себе. SortedSet поведение всегда последовательно, даже если compare() против equals() нет.

person ChssPly76    schedule 02.10.2009
comment
Меня больше беспокоит противоположное направление в этом случае - я хочу добавить элементы в набор, когда equals() имеет значение false, даже если compareTo возвращает 0. Существуют ли какие-либо реализации, которые делают это? - person Carl; 03.10.2009
comment
Установить реализации? Нет. Не возвращайте ноль из compareTo(), если equals() возвращает false, и вы решите все свои проблемы. Очевидно, вам нужно будет вернуть согласованный результат, но это не должно быть слишком сложно (в худшем случае вы можете сравнить хэш-коды Set). Производительность может немного пострадать, но (насколько это распространено на самом деле?), но у вас будет согласованная семантика как для Set, так и для SortedSet. - person ChssPly76; 03.10.2009

Кажется, это нарушает запрет на то, чтобы Comparator был совместим с equals() - т. Е. Две коллекции могут быть неравными (имея разные элементы), но сравниваться с одним и тем же значением (потому что они имеют одинаковое количество элементов).

Нет требований, заявленных (в Javadoc) или подразумеваемых, чтобы Comparator соответствовал реализации объекта boolean equals(Object).

Обратите внимание, что Comparable и Comparator — это разные интерфейсы с разными целями. Comparable используется для определения «естественного» порядка для класса. В этом контексте было бы плохой идеей, если бы equals и compateTo были несовместимы. Напротив, Comparator используется, когда вы хотите использовать порядок, отличный от естественного порядка класса.

РЕДАКТИРОВАТЬ: Вот полный абзац из Javadoc для SortedSet.

Обратите внимание, что порядок, поддерживаемый отсортированным набором (независимо от того, предоставлен явный компаратор или нет), должен соответствовать равенству, если отсортированный набор должен правильно реализовать интерфейс Set. (См. интерфейс Comparable или интерфейс Comparator для точного определения соответствия с равными.) Это так, потому что интерфейс Set определен в терминах операции равенства, но отсортированный набор выполняет все сравнения элементов, используя свой метод compareTo (или сравнение). , так что два элемента, которые считаются равными с помощью этого метода, равны с точки зрения отсортированного множества. Поведение отсортированного набора четко определено, даже если его порядок несовместим с равными; он просто не подчиняется общему контракту интерфейса Set.

Я выделил последнюю фразу. Дело в том, что такой SortedSet будет работать так, как вы, скорее всего, ожидаете, но поведение некоторых операций не будет точно соответствовать спецификации Set... потому что спецификация определяет их поведение в терминах метода equals.

Так что на самом деле существует существует заявленное требование согласованности (моя ошибка), но последствия его игнорирования не так ужасны, как вы могли бы подумать. Конечно, вам решать, стоит ли вам это делать. По моей оценке, все должно быть в порядке, если вы тщательно прокомментируете код и убедитесь, что SortedSet не «утекает».

Однако мне не ясно, будет ли компаратор для коллекций, который смотрит только на «размер» коллекций, работать ... с семантической точки зрения. Я имею в виду, вы действительно хотите сказать, что все коллекции с (скажем) 2 элементами равны? Это будет означать, что ваш набор может содержать только одну коллекцию любого заданного размера...

person Stephen C    schedule 03.10.2009
comment
В javadoc указано: порядок, поддерживаемый отсортированным набором (независимо от того, предоставлен явный компаратор или нет), должен быть совместим с равными, а совместимый с равными определяется как (compare(e1,e2)==0)==(equals(e1 ,е2)). Я не уверен, как устранить разницу между вашим вступительным заявлением и заявлением в javadoc. Вы можете уточнить? - person Carl; 03.10.2009

Нет причин, по которым Comparator должен возвращать те же результаты, что и equals(). На самом деле API Comparator был представлен потому, что equals() просто недостаточно: если вы хотите отсортировать коллекцию, вы должны знать, какие два элемента меньше или больше.

person Aaron Digulla    schedule 02.10.2009
comment
Этот ответ кажется несовместимым с javadoc для SortedSet: java.sun.com/j2se/1.5.0/docs/api/java/util/SortedSet.html Можете ли вы уточнить? - person Carl; 02.10.2009
comment
@Carl: Comparator != Comparable ... см. мой ответ - person Stephen C; 03.10.2009

Немного странно, что SortedSet как часть стандартного API нарушает контракт, определенный в интерфейсе Set, и использует Comparator для определения равенства вместо метода equals, но так оно и есть.

Если ваша реальная проблема состоит в том, чтобы отсортировать набор коллекций в соответствии с размером содержащихся коллекций, вам лучше использовать список, который вы можете отсортировать с помощью Collections.sort(List, Comparator>);

person jarnbjo    schedule 02.10.2009