Search code examples
javacollectionsarraylistcomparatortreeset

Sort a java collection of objects by one value and remain unique by another value


I need to sort a java collection of objects by an Integer value "level". I also need to determine if this collection already contains an object by "title".

I believe the best choice of a collection is a TreeSet to have an ordered set of unique values.

I have an object with the "level" and "title attributes. It implements comparable like so:

It overrides the Equals method (for checking if the object is already contained in the TreeSet by "title".

The code looks like:

@Override
public boolean equals(Object arg0) {

    Artifact obj = (Artifact) arg0;

    if (this.getTitle().equals(obj.getTitle())) { 
        return true;
    }

    return false;
}

@Override
public int compareTo(Artifact aThat) {  

    final int BEFORE = -1;
    final int EQUAL = 0;
    final int AFTER = 1;

    if (this == aThat) return EQUAL;

    if (this.level < aThat.level) return BEFORE;
    if (this.level > aThat.level) return AFTER;

    assert this.equals(aThat) : "compareTo inconsistent with equals.";
    return EQUAL;
}

When I attempt to add values to the list from an arraylist with possibly duplicate values. The contains seems to not work and objects are added to the TreeSet regardless. Here is the code:

TreeSet<Artifact> subsetOfArtifacts = new TreeSet<Artifact>();

ArrayList<Artifact> allArtifacts = getArtifacts(); 
Iterator<Artifact> allArtifactsIter = allArtifacts.iterator();

while (allArtifactsIter.hasNext()) {
    Artifact artifact = (Artifact) allArtifactsIter.next();
    if (!subsetOfArtifacts.contains(artifact)) {
        subsetOfArtifacts.add(artifact);
    }
 }

I want to ideally have a list of all unique artifacts sorted by level. How do I accomplish this?


Solution

  • If you override equals() you should override hashCode() too! Otherwise, behaviour with collections, especially Sets, is undefined. You should add this method to your class:

    @Override
    public int hashCode() {
        return title.hashCode();
    }
    

    Next, you should use a HashSet instead and use Set sortedSet = new TreeSet(set); for the sorting. Once you do that, it should all work OK.

    The reason is that HashTables rely on the fact that if two objects are equal() then their hashCode() is also equal. Here's an excerpt from the javadoc for hashCode()

    The general contract of hashCode is:

    • Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
    • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
    • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hashtables.