This is a quote from the book Java Generics and Collections:
In principle, all that a client should need to know is how to keep to
its side of the contract; if it fails to do that, all bets are off and
there should be no need to say exactly what the supplier will do.
So the answer is : Yes it can happen but only when you don't keep to your side of the contract with Java. Here you can say Java has violated the symmetric property of equality but if that happen be sure that you are the one who has broken the contract of some other interfaces first. Java has already documented this behaviour.
Generally you should read documentation of Comparator
and Comparable
interfaces to use them correctly in sorted collections.
This question is somehow answered in Effective Java Third Edition Item 14 on pages 66-68.
This is a quote from the book when defining contract for implementing Comparable
interface(note that this is only part of the whole contract):
• It is strongly recommended, but not required, that (x.compareTo(y)
== 0)
== (x.equals(y)). Generally speaking, any class that implements the Comparable interface and violates this condition should clearly
indicate this fact. The recommended language is “Note: This class has
a natural ordering that is inconsistent with equals.”
It says It is strongly recommended, but not required, it means you are allowed to have classes for which
x.compareTo(y)==0
does not mean x.equal(y)==true
.(But if it is implemented that way you can't use them as an element in sorted collections, this is exactly the case with BigDecimal
)
The paragraph of the book describing this part of the contract of Comparable
interface is worth mentioning:
It is a strong suggestion rather than a true requirement, simply
states that the equality test imposed by the compareTo method should
generally return the same results as the equals method. If this
provision is obeyed, the ordering imposed by the compareTo method is
said to be consistent with equals. If it’s violated, the ordering is
said to be inconsistent with equals. A class whose compareTo method
imposes an order that is inconsistent with equals will still work, but
sorted collections containing elements of the class may not obey the
general contract of the appropriate collec- tion interfaces
(Collection, Set, or Map). This is because the general contracts for
these interfaces are defined in terms of the equals method, but sorted
collec- tions use the equality test imposed by compareTo in place of
equals. It is not a catastrophe if this happens, but it’s something to
be aware of.
Actually we have some classes in Java itself that did not follow this recommendation. BigDecimal
is one of them and this is mentioned in the book.
For example, consider the BigDecimal class, whose compareTo method is
inconsistent with equals. If you create an empty HashSet instance and
then add new BigDecimal("1.0") and new BigDecimal("1.00"), the set
will contain two elements because the two BigDecimal instances added
to the set are unequal when compared using the equals method. If,
however, you perform the same procedure using a TreeSet instead of a
HashSet, the set will contain only one element because the two
BigDecimal instances are equal when compared using the compareTo
method. (See the BigDecimal documentation for details.)
However this behaviour is documented in BigDecimal
Documentation. Let's have a look at that part of the documentation:
Note: care should be exercised if BigDecimal objects are used as keys
in a SortedMap or elements in a SortedSet since BigDecimal's natural
ordering is inconsistent with equals. See Comparable, SortedMap or
SortedSet for more information.
So although you can write code like below you should not do it because the BigDecimal
class has prohibited this usage:
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
Note that Comparable
will be used as natural ordering of the elements when you don't pass any comparator to TreeSet
or TreeMap
, the same thing can happen when you pass Comparator
to those class constructor. This is mentioned in the Comparator
documentation:
The ordering imposed by a comparator c on a set of elements S is said
to be consistent with equals if and only if c.compare(e1, e2)==0 has
the same boolean value as e1.equals(e2) for every e1 and e2 in S.
Caution should be exercised when using a comparator capable of
imposing an ordering inconsistent with equals to order a sorted set
(or sorted map). Suppose a sorted set (or sorted map) with an explicit
comparator c is used with elements (or keys) drawn from a set S. If
the ordering imposed by c on S is inconsistent with equals, the sorted
set (or sorted map) will behave "strangely." In particular the sorted
set (or sorted map) will violate the general contract for set (or
map), which is defined in terms of equals.
So considering this documention of Comparator
, following example given by @Aniket Sahrawat is not supported to work:
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
In a nutshell the answer is: Yes it can happen but only when you break the documented contract of one of the aforementioned interfaces(SortedSet
, Comparable
, Comparator
).