Why are empty collections of different type equal?

What is mechanism below that makes equal different types?

import static org.testng.Assert.assertEquals;
@Test
public void whyThisIsEqual() {
    assertEquals(new HashSet<>(), new ArrayList<>());
}

The assertEquals(Collection<?> actual, Collection<?> expected) documentation says:

Asserts that two collections contain the same elements in the same order. If they do not, an AssertionError is thrown.

Thus the content of the collections will be compared which, in case both the collections are empty, are equal.


They are not...

System.out.println(new HashSet<>().equals(new ArrayList<>())); // false

This is specific to testng assertEquals

Looking at the documentation of that method it says:

Asserts that two collections contain the same elements in the same order.

And this is ridiculous to me, a Set does not have an order, per-se.

Set<String> set = new HashSet<>();
set.add("hello");
set.add("from");
set.add("jug");

System.out.println(set); // [from, hello, jug]

IntStream.range(0, 1000).mapToObj(x -> x + "").forEachOrdered(set::add);
IntStream.range(0, 1000).mapToObj(x -> x + "").forEachOrdered(set::remove);

System.out.println(set); // [jug, hello, from]

So comparing these against a Collection at some particular point in time would yield interesting results.

Even worse, java-9 Set::of methods implement a randomization internally, so the order (or not the order) will be different from run to run.


Testng calls through to a method implemented this way.

  public static void assertEquals(Collection<?> actual, Collection<?> expected, String message) {
    if (actual == expected) {
      return;
    }

    if (actual == null || expected == null) {
      if (message != null) {
        fail(message);
      } else {
        fail("Collections not equal: expected: " + expected + " and actual: " + actual);
      }
    }

    assertEquals(
        actual.size(),
        expected.size(),
        (message == null ? "" : message + ": ") + "lists don't have the same size");

    Iterator<?> actIt = actual.iterator();
    Iterator<?> expIt = expected.iterator();
    int i = -1;
    while (actIt.hasNext() && expIt.hasNext()) {
      i++;
      Object e = expIt.next();
      Object a = actIt.next();
      String explanation = "Lists differ at element [" + i + "]: " + e + " != " + a;
      String errorMessage = message == null ? explanation : message + ": " + explanation;
      assertEqualsImpl(a, e, errorMessage);
    }
  }

This is trying to be helpful but is poor for a number of reasons.

Two equals collections can appear to be different.

Set<Integer> a = new HashSet<>();
a.add(82);
a.add(100);
System.err.println(a);
Set<Integer> b = new HashSet<>();
for (int i = 82; i <= 100; i++)
    b.add(i);
for (int i = 83; i <= 99; i++)
    b.remove(i);
System.err.println(b);
System.err.println("a.equals(b) && b.equals(a) is " + (a.equals(b) && b.equals(a)));
assertEquals(a, b, "a <=> b");

and

Set<Integer> a = new HashSet<>();
a.add(100);
a.add(82);
System.err.println(a);
Set<Integer> b = new HashSet<>(32);
b.add(100);
b.add(82);
System.err.println(b);
System.err.println("a.equals(b) && b.equals(a) is " + (a.equals(b) && b.equals(a)));
assertEquals(a, b, "a <=> b");

prints

[82, 100]
[100, 82]
a.equals(b) && b.equals(a) is true
Exception in thread "main" java.lang.AssertionError: a <=> b: Lists differ at element [0]: 100 != 82
    at ....

Two collections can be the same or different depending on how they are compared.

assertEquals(a, (Iterable) b); // passes

assertEquals(a, (Object) b); // passes

assertEquals(Arrays.asList(a), Arrays.asList(b)); // passes