3

I have two similar questions here:

  1. Java Compare Two List's object values?
  2. Java ArrayList - how can I tell if two lists are equal, order not mattering?

But I have a boresome situation, making this problem a little complicated. The object I need to compare come from an ancient third-party jar, which has no equals() function. And I'm concerned of modifying its codes.

One solution I came up with is building two other lists, and their values are tostring of the original lists. And the problem turns no difference with question 2.

But I still wonder if there is a better way?

Community
  • 1
  • 1
Mobility
  • 3,117
  • 18
  • 31
  • 2
    I'd probably write a wrapper containing `equals()` for the elements and use those in the lists. – Kayaman Mar 08 '17 at 10:34
  • You could make another class responsible for calculating equality. With a method such as: `public static boolean equalsAncientObj(Ancient a1, Ancient a2);`. This requires that useful enough state be exposed publicly. Then you can write a "list comparer" which explicitly uses this function for checking equality. – Birchlabs Mar 08 '17 at 10:43

5 Answers5

4

Try using Apache Commons isEqualCollection(Collection a, Collection b, Equator equator), you will still need to implement Equator in the same way you would write a equals override.

You could try this one too, this is the same algorithm from Apache Commons but with some performance improvements.

fn.
  • 2,788
  • 2
  • 27
  • 29
0

Point 1: See Kayaman's comment (wrapper class providing equals function).

Point 2: This wrapper class might additionally provide a lessThan function imposing an absolute order on the elements. You then could sort both lists and afterwards compare them element by element. This is more efficient than trying to identify each element of one list in the other one and additionally handles problems with duplicates elegantly.

Implementation examples for e. g. quicksort can be found all over the internet, e. g. here.

Aconcagua
  • 24,880
  • 4
  • 34
  • 59
0

First, you need a well-behaved canonical representation of the object. Create a class called MyCustomObject (hopefully you can think of a better name), that contains the most relevant properties from your original object and implements equals(), hashCode() and toString(). Write a constructor or factory method that creates MyCustomObject instances from your original object. Now convert your lists to Maps:

List<OriginalObject> list = // wherever you get your list from
Map<MyCustomObject> map = list.stream()
                              .collect(
                                 Collectors.toMap(
                                    MyCustomObject::new,
                                    (oo)->oo)
                               );

Do this with both lists, and compare the keySet()s of the two maps.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • Surely it should map MyCustomObject instances to the number of occurrences of that instance, shouldn't it? (And then compare the whole maps, not just the keysets.) – Klitos Kyriacou Mar 08 '17 at 10:55
0

To illustrate Kayaman's answer, you could do something like :

public class EqualsWrapper {
    private final Object delegate;

    public EqualsWrapper(Object toWrap) {
        delegate = toWrap;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((delegate == null) ? 0 : delegate.toString().hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        EqualsWrapper other = (EqualsWrapper) obj;
        return other.toString().equals(obj.toString());
    }
}

public static boolean areListsEqual(List list1, List list2){
    Set<EqualsWrapper> l_set1 = list1.stream().map(e -> new EqualsWrapper(e)).collect(Collectors.toSet());
    Set<EqualsWrapper> l_set2 = list2.stream().map(e -> new EqualsWrapper(e)).collect(Collectors.toSet());

    return l_set1.equals(l_set2);
}

However copying the whole lists might not be something optimal if you have very big lists and a high chance they are not equal. You could also follow that stance :

public static boolean areListsEqual(List list1, List list2){
    return list1.stream().map(e -> new EqualsWrapper(e)).allMatch(e -> list2.stream().map(e -> new EqualsWrapper(e)).anyMatch(e2 -> e2.equals(e)));
}

Where you'll break faster if there is not a match but risk to lose information on potential duplicates.

Jeremy Grand
  • 2,300
  • 12
  • 18
-1

My approach also will require implementing equals method but you don't want to do update the equals method whenever you update the object with the new fields.

Try it out, I believe that, the approach can be an acceptable one.

Ganesa Vijayakumar
  • 2,422
  • 5
  • 28
  • 41