简体   繁体   中英

equals on objects that contain different types of collections

I have an object that looks like this (I've using a bit of the Guava library in order to simply my equals and hashcode methods):

public class MyClass {
    private Collection<Integer> integers;
    private Collection<String> strings;

    // Getters and Setters...

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        MyClass that = (MyClass) o;

        return Objects.equal(integers, that.integers) &&
               Objects.equal(strings, that.strings);
    }

    @Override
    public int hashCode() {
        return Objects.hashCode(integers, strings);
    }
}

The issue I'm running into is with code that essentially does this (this would be a typical test case):

// Produce a new instance of MyClass that users ArrayLists.
MyClass expected = new MyClass();
expected.setIntegers(Arrays.asList(1, 2, 3));
expected.setStrings(Arrays.asList("a", "b"));

// I don't normally construct the actual object here, 
// but I've included the code so you get an understanding of what's happening

// Produce a new instance of MyClass that uses HashSets.
Set<Integer> someNumbers = new HashSet<Integer>();
someNumbers.add(1);
someNumbers.add(2);
someNumbers.add(3);
Set<String> someStrings = new HashSet<String>();
someStrings.add("a");
someStrings.add("b");
MyClass actual = new MyClass();
actual.setIntegers(someNumbers);
actual.setIntegers(someStrings);

assertEquals(expected, actual);

The problem I'm running into is that even though the contents of the collections are all the same and the compile-time type of those members is "Collection", the run-time types are used to evaluate equality so this assertion fails. Looking at the source code for AbstractSet.equals and AbstractList.equals, they check to see if the other object is a Set or a List, respectively, before they evaluate the contents.

I suppose that makes some sense because order matters in a list and doesn't in a set, so, even if the contents are the same, you can't compare them.

That said, in this case, I don't care what the underlying collection is - I just want to know if the contents are the same and order makes no difference. Is there any easy way to do this?

In the case the underlying collection does not matter to you, you could switch to one pre-defined ordered collection that allows repeats, make sure the two new collections are sorted, and then check for equality.

One simple way to do it is convert your elements to arrays using toArray() , sort it with Arrays.sort() , and check for equality using Arrays.equals()

Just do not do it! There's no possible sane re-definition of equals for Collection as its subclasses List and Set define it in an incompatible way. So the only possibility is to stick with existing equals inherited from Object , ie, testing reference equality.


Actually, there's a solution for your problem... although it's more of another problem than a solution. If you want to consider integers equals whenever they're equals as List (or Set , you have to choose), you can write a corresponding method by simply copying the loop from AbstractList (or AbstractSet ).


In case you care about repeated elements, but not about repetitions, you may be able to sort of copy as amit proposed. However, sorting needs Comparable elements or a Comparator , so it needn't apply. A very trivial solution is Guava's Multiset .

However, things like the one you're asking for rarely occur in a good design. Usually it's better to

  • drop the setters
  • add a setter-like method copying the content into a desired concrete collection

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM