Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a simple way in Java to get the difference between two collections using a custom equals function without overriding the equals?

I'm open to use a lib. I just want something simple to diff two collections on a different criteria than the normal equals function.

Right now I use something like :

collection1.stream()
           .filter(element -> !collection2.stream()
                                          .anyMatch(element2 -> element2.equalsWithoutSomeField(element)))
           .collect(Collectors.toSet());

and I would like something like :

Collections.diff(collection1, collection2, Foo::equalsWithoutSomeField);

(edit) More context:

Should of mentioned that I'm looking for something that exists already and not to code it myself. I might code a small utils from your ideas if nothing exists.

Also, Real duplicates aren't possible in my case: the collections are Sets. However, duplicates according to the custom equals are possible and should not be removed by this operation. It seems to be a limitation in a lot of possible solutions.

like image 470
FredBoutin Avatar asked Mar 16 '18 15:03

FredBoutin


1 Answers

We use similar methods in our project to shorten repetitive collection filtering. We started with some basic building blocks:

static <T> boolean anyMatch(Collection<T> set, Predicate<T> match) {
    for (T object : set)
        if (match.test(object))
            return true;
    return false;
}

Based on this, we can easily implement methods like noneMatch and more complicated ones like isSubset or your diff:

static <E> Collection<E> disjunctiveUnion(Collection<E> c1, Collection<E> c2, BiPredicate<E, E> match)
{
    ArrayList<E> diff = new ArrayList<>();
    diff.addAll(c1);
    diff.addAll(c2);
    diff.removeIf(e -> anyMatch(c1, e1 -> match.test(e, e1)) 
                       && anyMatch(c2, e2 -> match.test(e, e2)));
    return diff;
}

Note that there are for sure some possibilities for perfomance tuning. But keeping it separated into small methods help understanding and using them with ease. Used in code they read quite nice.

You would then use it as you already said:

CollectionUtils.disjunctiveUnion(collection1, collection2, Foo::equalsWithoutSomeField);

Taking Jose Da Silva's suggestion into account, you could even use Comparator to build your criteria on the fly:

Comparator<E> special = Comparator.comparing(Foo::thisField)
                                  .thenComparing(Foo::thatField);
BiPredicate specialMatch = (e1, e2) -> special.compare(e1, e2) == 0;
like image 168
Malte Hartwig Avatar answered Oct 08 '22 03:10

Malte Hartwig