Возможно ли, что TreeSet равно HashSet, но не HashSet равно TreeSet

У меня сегодня было интервью, и человек, берущий мое интервью, озадачил меня своим заявлением, спрашивая, возможно ли, что TreeSet равно HashSet, но не HashSet равно TreeSet. Я сказал нет, но, по его словам, ответ положительный.

Как это вообще возможно?


person Community    schedule 19.06.2020    source источник


Ответы (5)


Ваш интервьюер прав, они не проводят отношения эквивалентности для некоторых конкретных случаев. Возможно, что TreeSet может быть равно HashSet, а не наоборот. Вот пример:

TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
HashSet<String> hashSet = new HashSet<>();
treeSet.addAll(List.of("A", "b"));
hashSet.addAll(List.of("A", "B"));
System.out.println(hashSet.equals(treeSet)); // false
System.out.println(treeSet.equals(hashSet)); // true

Причина этого в том, что TreeSet использует компаратор для определения дубликата элемента, а HashSet использует equals.

Цитата TreeSet:

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

person Aniket Sahrawat    schedule 19.06.2020

Это невозможно без нарушения договора равных или установленного. Определение равенства в Java требует симметрии, т.е. a.equals(b) должен быть таким же, как b.equals(a).

Фактически, сама документация Set говорит

Возвращает истину, если указанный объект также является набором, два набора имеют одинаковый размер, и каждый член указанного набора содержится в этом наборе (или, что эквивалентно, каждый член этого набора содержится в указанном наборе). Это определение гарантирует, что метод equals правильно работает в разных реализациях интерфейса set.

person kewne    schedule 19.06.2020
comment
Определение равенства действительно требует этого, но это никоим образом не применяется (и даже не может быть обеспечено) на техническом уровне. Я мог бы даже создать класс, который дает случайные результаты для equals (). - person ; 19.06.2020
comment
@Taemyr в документации также говорится, что «объект содержится в наборе, если есть элемент набора, для которого object.equals(element)», что однозначно определяет критерии равенства. Вот почему SortedSet требует, чтобы компаратор был совместим с равными. Возможно, не очевидно, что для этого вообще не нужно вызывать равных. Например, вы можете реализовать набор, содержащий все строки, просто используя проверки instanceof. - person kewne; 21.06.2020

НЕТ, это невозможно без нарушения общего договора equals класса Object, который требует симметрии, т.е. е. x.equals(y) тогда и только тогда, когда y.equals(x).

НО, классы TreeSet и HashSet реализуют равно контракту Set интерфейса по-разному. Этот контракт требует, среди прочего, чтобы каждый член указанного набора содержался в этом наборе. Чтобы определить, входит ли элемент в набор, вызывается метод contains, который для TreeSet использует Comparator, а для HashSet использует hashCode.

И наконец:

ДА, в некоторых случаях это возможно.

person Community    schedule 24.07.2020

Это цитата из книги Java Generics and Collections:

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

Итак, ответ: да, это может произойти, но только если вы не соблюдаете свою сторону контракта с Java. Здесь вы можете сказать, что Java нарушила симметричное свойство равенства, но если это произойдет, убедитесь, что вы первый разорвали контракт с некоторыми другими интерфейсами. Java уже задокументировала это поведение.

Как правило, вы должны читать документацию по интерфейсам Comparator и Comparable, чтобы правильно использовать их в отсортированных коллекциях.

На этот вопрос каким-то образом дан ответ в документе Эффективное третье издание Java, пункт 14 на страницах 66-68.

Это цитата из книги при определении контракта на реализацию интерфейса Comparable (обратите внимание, что это только часть всего контракта):

• Настоятельно рекомендуется, но не обязательно, чтобы (x.compareTo (y) == 0) == (x.equals (y)). Вообще говоря, любой класс, реализующий интерфейс Comparable и нарушающий это условие, должен четко указывать на этот факт. Рекомендуемый язык - «Примечание: этот класс имеет естественный порядок, несовместимый с equals».

В нем говорится: Настоятельно рекомендуется, но не обязательно, это означает, что вам разрешено иметь классы, для которых x.compareTo(y)==0 не означает _5 _. (Но если это реализовано таким образом, вы не можете использовать их в качестве элемент в отсортированных коллекциях, это как раз тот случай с BigDecimal)

Стоит упомянуть параграф книги, описывающий эту часть контракта интерфейса Comparable:

Это сильное предложение, а не истинное требование, оно просто утверждает, что проверка равенства, наложенная методом compareTo, обычно должна возвращать те же результаты, что и метод equals. Если это положение соблюдается, порядок, установленный методом compareTo, считается совместимым с equals. Если он нарушен, порядок считается несовместимым с равным. Класс, чей метод compareTo устанавливает порядок, несовместимый с equals, по-прежнему будет работать, но отсортированные коллекции, содержащие элементы класса, могут не подчиняться общему контракту соответствующих интерфейсов сбора (Collection, Set или Map). Это связано с тем, что общие контракты для этих интерфейсов определены в терминах метода equals, но в отсортированных коллекциях вместо equals используется проверка на равенство, налагаемая compareTo. Если это произойдет, это не катастрофа, но об этом нужно знать.

На самом деле у нас есть несколько классов в самой Java, которые не следовали этой рекомендации. BigDecimal - один из них, и об этом упоминается в книге.

Например, рассмотрим класс BigDecimal, чей метод compareTo несовместим с equals. Если вы создаете пустой экземпляр HashSet, а затем добавляете новые BigDecimal (1.0) и новые BigDecimal (1.00), набор будет содержать два элемента, потому что два экземпляра BigDecimal, добавленные в набор, не равны при сравнении с использованием метода equals. Однако если вы выполните ту же процедуру с использованием TreeSet вместо HashSet, набор будет содержать только один элемент, поскольку два экземпляра BigDecimal равны при сравнении с использованием метода compareTo. (Подробности см. В документации BigDecimal.)

Однако такое поведение задокументировано в BigDecimal Документация. Давайте посмотрим на эту часть документации:

Примечание: следует проявлять осторожность, если объекты BigDecimal используются в качестве ключей в SortedMap или элементы в SortedSet, поскольку естественный порядок BigDecimal несовместим с equals. См. Comparable, SortedMap или SortedSet для получения дополнительной информации.

Поэтому, хотя вы можете написать код, подобный приведенному ниже, вам не следует этого делать, потому что класс BigDecimal запрещает такое использование:

Set<BigDecimal> treeSet = new TreeSet<>();
Set<BigDecimal> hashSet = new HashSet<>();
treeSet.add(new BigDecimal("1.00"));
treeSet.add(new BigDecimal("2.0"));
hashSet.add(new BigDecimal("1.00"));
hashSet.add(new BigDecimal("2.00"));
System.out.println(hashSet.equals(treeSet)); // false
System.out.println(treeSet.equals(hashSet)); // true

Обратите внимание, что Comparable будет использоваться как естественный порядок элементов, когда вы не передадите какой-либо компаратор в TreeSet или TreeMap, то же самое может произойти, когда вы передадите Comparator в этот конструктор класса. Это упоминается в Comparator документации:

Упорядочение, наложенное компаратором c на набор элементов S, называется согласованным с равенством тогда и только тогда, когда c.compare (e1, e2) == 0 имеет то же логическое значение, что и e1.equals (e2) для каждого e1 и e2 в S.

Следует проявлять осторожность при использовании компаратора, способного устанавливать порядок, несовместимый с равенством, для упорядочивания отсортированного набора (или отсортированной карты). Предположим, что отсортированный набор (или отсортированная карта) с явным компаратором c используется с элементами (или ключами), взятыми из набора S. Если порядок, наложенный c на S, несовместим с equals, отсортированный набор (или отсортированная карта) будет вести себя странно. В частности, отсортированный набор (или отсортированная карта) будет нарушать общий договор для набора (или карты), который определяется в терминах равенства.

Итак, учитывая этот документ Comparator, следующий пример, приведенный @Aniket Sahrawat, не поддерживается для работы:

TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
HashSet<String> hashSet = new HashSet<>();
treeSet.addAll(List.of("A", "b"));
hashSet.addAll(List.of("A", "B"));
System.out.println(hashSet.equals(treeSet)); // false
System.out.println(treeSet.equals(hashSet)); // true

Вкратце ответ: да, это может произойти, но только если вы нарушите задокументированный контракт одного из вышеупомянутых интерфейсов (SortedSet, Comparable, Comparator).

person Tashkhisi    schedule 22.08.2020

Уже есть хорошие ответы, но я хотел бы подойти к этому с более общей точки зрения.

В математике, логике и, соответственно, в компьютерных науках равно - это Симметричное двоичное отношение, что означает, что если A is equal to B, то B is equal to A.

Итак, если TreeSet X равно HashSet Y, тогда HashSet Y должно быть равно TreeSet X, и это должно быть истинно всегда.

Если, однако, свойство симметричности Equality нарушено (т.е. Equality не реализовано правильно), тогда x.equals(y) может не означать y.equals(x).


Документация для Object # equals в Java явно заявляет, что:

Метод equals реализует отношение эквивалентности для ненулевых ссылок на объекты.

следовательно, он реализует симметричное свойство, и если это не так , то он нарушает равенство в целом и нарушает метод Object # equals, особенно в Java.

person Giorgi Tsiklauri    schedule 22.08.2020