RFR [15] 6394757: rev2: AbstractSet.removeAll semantics are surprisingly dependent on relative sizes

Jason Mehrens jason_mehrens at hotmail.com
Wed May 20 18:23:16 UTC 2020


Alan,

This contains that and that contains this at least avoids calling size which could be expensive or ephemeral at the AbstractSet level.  Using the iterators you can at least do a final check to see there are no more elements to walk.   Biasing towards ordering could pay off for things like naturally ordered SortedSet to SortedSet, LinkedHashSet, or CopyOnWriteArraySet.

Computing the hash during equals is about the best I can come up with:  

public class SetEquals {

    public static void main(String[] args) {
        Comparator<String> cc = String.CASE_INSENSITIVE_ORDER;
        Set<String> s1 = new TreeSet<>(cc);
        Set<String> s2 = new TreeSet<>(cc);
        s1.add("hello");
        s2.add("Hello");
        System.out.println(equals(s1, s2));
        System.out.println(equals(s2, s1));
        System.out.println(s1.hashCode() == s2.hashCode());
    }

    private static boolean equals(Set<?> s1, Set<?> s2) {
        if (s2 == null) {
            return false;
        }

        if (s1 == s2) {
            return true;
        }

        int s1h = 0;
        int s2h = 0;
        Iterator<?> e1 = s1.iterator();
        Iterator<?> e2 = s2.iterator();

        if (s1 instanceof SortedSet && s2 instanceof SortedSet
                && isSameOrder((SortedSet) s1, (SortedSet) s2)) {
            //TODO: raw type warnings.
            Comparator cmp = ((SortedSet<?>) s1).comparator();
            if (cmp == null) {
                cmp = Comparator.naturalOrder();
            }
            while (e1.hasNext() && e2.hasNext()) {
                Object o1 = e1.next();
                Object o2 = e2.next();

                try {
                    if (cmp.compare(o1, o2) != 0
                            && (!s1.contains(o2) || !s2.contains(o1))) {
                        return false;
                    }
                } catch (ClassCastException cce) {
                    return false;
                }

                s1h += Objects.hashCode(o1);
                s2h += Objects.hashCode(o2);

                //Iteration order should be the same.
                if (s1h != s2h) {
                    return false;
                }
            }
        } else {
            while (e1.hasNext() && e2.hasNext()) {
                Object o1 = e1.next();
                Object o2 = e2.next();

                try {
                    if (!Objects.equals(o1, o2)
                            && (!s1.contains(o2) || !s2.contains(o1))) {
                        return false;
                    }
                } catch (ClassCastException cce) {
                    return false;
                }

                s1h += Objects.hashCode(o1);
                s2h += Objects.hashCode(o2);
            }
        }
        return s1h == s2h && !e1.hasNext() && !e2.hasNext();
    }

    private static boolean isSameOrder(SortedSet<?> s1, SortedSet<?> s2) {
        //null matches natural ordering or unwrap to find a singleton.
        //Avoid calling equals on comparator.
        return Collections.reverseOrder(s1.comparator())
                == Collections.reverseOrder(s2.comparator());
    }
}

Jason
________________________________________
From: Alan Snyder <javalists at cbfiddle.com>
Sent: Thursday, May 14, 2020 8:39 AM
To: Jason Mehrens
Cc: core-libs-dev
Subject: Re: RFR [15] 6394757: rev2: AbstractSet.removeAll semantics are surprisingly dependent on relative sizes

> HashSet/TreeSet could do what ConcurrentHashMap/ConcurrentSkipListSet do by using the "this contains that and that contains this" logic.

Yes, at the cost of yet another performance regression.

But how about this problem:

Comparator<String> cc = String.CASE_INSENSITIVE_ORDER;
Set<String> s1 = new TreeSet<>(cc);
Set<String> s2 = new TreeSet<>(cc);
s1.add("hello");
s2.add("Hello");
s1.equals(s2) -> true
s2.equals(s1) -> true
s1.hashCode() == s2.hashCode() -> false



More information about the core-libs-dev mailing list