Yan Khonski
Yan Khonski

Reputation: 13083

TreeSet equals another TreeSet

How to find out if two TreeSet objects are equal? I use open-jdk-10.

ModifiebleObject

class ModifiebleObject implements Comparable<ModifiebleObject>{

    Integer number;
    String text;

    @Override
    public int compareTo(final ModifiebleObject o) {
        return this.number - o.number;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) return true;
        if (!(o instanceof ModifiebleObject)) return false;
        final ModifiebleObject that = (ModifiebleObject) o;
        return Objects.equals(number, that.number) &&
                Objects.equals(text, that.text);
    }

    @Override
    public int hashCode() {
        return Objects.hash(number, text);
    }
}

SomeCode

SortedSet<ModifiebleObject> tree1 = prepare();
SortedSet<ModifiebleObject> tree2 = prepare(); //Returns cloned elements, so object references in tree1 and tree2 are different.

// ModifiebleObject implements Comparable<ModifiebleObject>
// compareTo does not use all the fields, just some of them.
//setSomeValueOutsideOfComparable sets value of the field, which is not used by compareTo
tree2.first().setSomeValueOutsideOfComparable("newValue");

boolean tree1EqualsTree2 = tree1.equals(tree2); //Returns true

Because

TreeSet calls AbstractSet.containsAll -> TreeSet.contains -> TreeMap.containsKey -> TreeMap.getEntry != null

TreeMap.getEntry uses compactor or elements compareTo (elements implement Comparable).

Funny, but JavaDoc lies!

java.utilTreeSet

/**
 * Returns {@code true} if this set contains the specified element.
 * More formally, returns {@code true} if and only if this set
 * contains an element {@code e} such that
 * {@code Objects.equals(o, e)}.
 *
 * @param o object to be checked for containment in this set
 * @return {@code true} if this set contains the specified element
 * @throws ClassCastException if the specified object cannot be compared
 *         with the elements currently in the set
 * @throws NullPointerException if the specified element is null
 *         and this set uses natural ordering, or its comparator
 *         does not permit null elements
 */
public boolean contains(Object o) {
    return m.containsKey(o);
}

More formally, returns {@code true} if and only if this set contains an element {@code e} such that {@code Objects.equals(o, e)}.

But in reality it uses compareTo.


Update

What other collection from jdk or another library I could use that guarantees unique elements and sorted, and equals to another collection uses for each element equals.

Upvotes: 1

Views: 327

Answers (3)

Yan Khonski
Yan Khonski

Reputation: 13083

This is a solution that worked for me.

class TreeSetWithEquals extends TreeSet {

    //Constructors which I will use
    //

    // I consider two sets equal if they have equal elements in the same order!
    @Override
    public boolean equals(Object o) {
        //Check if Object not null, not the same reference as this, and 
        // it is instance of Set
        //And their sizes are equal
        //Iterate through boths sets and apply equals for each method.
    }
}

Why would I do this? Well, in our code we generate equals for other objects in Idea -> which uses Objects.equals(this.field_i, that.field_i). We are lazy to search our code for places and replace Objects.equals(this.field_i, that.field_i) if field_i is a sorted set into a util class to check equality of the sets. So it is easier to use a set which supports sorting, but uses eqauls for each element inside this.equals.

Some people tell me that eqauls, hashCode, compareTo must be consistent. I agree with eqauls, hashCodemust be consistent.

For example.

enum WeaponType {

     KNIFE,
     HAND_GUN,
     TWO_HANDED_GUN,
     GRANADES, //To allow flash, smoke and fragmentation granades
     DEFUSE_KIT
}

class Shooter {

    // make sure we can have different weapons,
    // but only one of type is allowed.
    // Our tree set with such comparator will guarantee this requirement.
    private SortedSet<Weapon> weapons = buyWeapons(andUseWeaponTypeComparator);

To do this I will define a WeaponComparator

Comparator<Weapon> WEAPON_COMPARATOR = Compareator
       .comparing(Weapon::getType, Comparator.naturalOrder()) //enum uses its element order.
}

Now if you want to persist a shooter and send messages in kafka to other micro-services, you will have equals which checks the who Shooter entities.

Upvotes: 0

Akash Shah
Akash Shah

Reputation: 616

If you have Set of Defined Object then you can override hashcode and equals method and below method compare based on hashcode and equals method. You can Use

 org.apache.commons.collections
 SetUtils.isEqualSet(set1, set2);

or

 org.apache.commons.collections
 CollectionUtils.isEqualCollection(a, b)

Upvotes: 2

Michael
Michael

Reputation: 44100

The JavaDoc for TreeSet explicitly says this. It's not some conspiracy.

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.

Your class has ignored the advice of Comparable and you are paying for it.

It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y))

Upvotes: 2

Related Questions