JDK-8181579 : Inconsistency between API documentation and TreeSet.add(...) behaviour
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:collections
  • Affected Version: 8,9
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2017-05-29
  • Updated: 2021-12-06
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Windows 10 x64

A DESCRIPTION OF THE PROBLEM :
A DESCRIPTION OF THE PROBLEM :
API documentation for TreeSet.add(E e) is not consistent with class behaviour.

The documentation states that: "Adds the specified element to this set if it is not already present. More formally, adds the specified element e to this set if the set contains no element e2 such that (e==null ? e2==null : e.equals(e2))."

However, the implementation does not actually use equals, it uses compareTo; an equivalent statement of the actual behaviour would be as follows: "Adds the specified element to this set if it is not already present. More formally, adds the specified element e to this set if the set contains no element e2 such that (e==null ? e2==null : e.compareTo(e2)==0)."

These two are NOT equivalent, since there is no strict requirement on compareTo to be consistent with equals (see documentation for interface Comparable).

Although this behaviour means that TreeSet and other set implementations (e.g. HashSet) have different mechanisms for detecting equality of members, a fix for this might not be considered backwards compatible (despite being consistent with existing documentation), so my request is simply for a change to the documentation as suggested above.

There may be other methods (addAll?) which display the same mismatch between advertised and actual behaviour, but I've not checked them.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Create a class which implements Comparable, and override Object.equals such that e1.compareTo(e2)==0 and e1.equals(e2)==true are not synonymous.  For a quick test, make equals always return false and compareTo always return zero.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expected behaviour (to be consistent with the documentation) is that insertion of multiple such objects into either a TreeSet or a HashSet will succeed, since both claim reliance on equals to determine uniqueness.

ACTUAL -
Iow, inserting multiple such objects into a TreeSet will fail (contrary to the documentation), while inserting them into a HashSet will succeed.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.HashSet;
import java.util.TreeSet;

public class TreeSetTest {

	/*
	 * This inner class deliberately has a compareTo method that is not consistent with equals
	 */
	static class TestObject implements Comparable<TestObject> {
		@Override
		public int compareTo(TestObject arg0) {
			// No two of these objects can be ordered
			return 0;
		}
		
		@Override
		public boolean equals(Object arg0) {
			// No two of these objects are ever equal to each other
			return false;
		}
	}
	
	public static void printSuccess(boolean success) {
		if (success) System.out.println(" Success");
		else System.out.println(" Failure");
	}
	
	public static void main(String[] args) {
		TreeSet<TestObject> testTreeSet = new TreeSet<TestObject>();
		HashSet<TestObject> testHashSet = new HashSet<TestObject>();
		
		System.out.println("Adding to the HashSet:");
		printSuccess(testHashSet.add(new TestObject()));
		printSuccess(testHashSet.add(new TestObject()));
		printSuccess(testHashSet.add(new TestObject()));
		
		System.out.println("Copying to the TreeSet:");
		for (TestObject to : testHashSet) {
			printSuccess(testTreeSet.add(to));
		}
	}
}

---------- END SOURCE ----------


Comments
Agreed, we need to revisit the specs of these methods for that segment of the hierarchy. While there isn't a direct dependency, this is related to JDK-8180409 and JDK-6394757. The problem is that operations that take two collections might disagree on their notions of equality, so which collection is iterated and which one's contains() is called can give different results. The specs and implementations for those all need to be reviewed and possibly fixed as well.
06-06-2017

probably we don't want to change any behavior. Spec clarifications should consider the entire sorted set/map hierarchy, so also: {Sorted,Navigable,ConcurrentSkipList}{Set,Map}
05-06-2017

The Java API documentation at https://docs.oracle.com/javase/8/docs/api/java/util/TreeSet.html states: " Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface. " The documentation of TreeSet does recommend that natural ordering must be consistent with equals though it is not mandated.
05-06-2017