1
\$\begingroup\$

I'd like to be able to compare two objects with different equality checks. This should work if they're in a Set, List or Map as well.

An example use case is testing of deserialzation of a entity which might contain sensitive information, like passwords, the server side, but not in a REST response.

I'd be interested if you see any design flaws or uncovered issues.

I'd also be grateful for a hint about existing implementations of FLOSS projects.

/**
 * Used by
 * {@link Tools#equals(java.lang.Object, java.lang.Object, EqualsCheck) }.
 *
 * @author richter
 */
@FunctionalInterface
public interface EqualsCheck<T> {

    boolean equals(T a, T b);
}

and the methods:

/**
 * Does what
 * {@link java.util.Objects#equals(java.lang.Object, java.lang.Object) }
 * does, except allows to use methods to check equality as lambda.
 *
 * @param <T> allows to restrict the type of arguments to compare
 * @param a the first item
 * @param b the first item
 * @param equalsCheck the equality check to use
 * @return {@code true} if {@code a} and {@code b} are equal to each other,
 * {@code false} otherwise
 */
public static <T> boolean equals(T a, T b,
        EqualsCheck<T> equalsCheck) {
    return (a == b) || (a != null && equalsCheck.equals(a, b));
}

/**
 * Checks that all elements in {@code a} are contained in {@code b}.
 *
 * @param <T> allows to restrict the type of arguments to compare
 * @param a the first set
 * @param b the second set
 * @param equalsCheck the equality check to use
 * @return {@code true} if all elements in {@code a} are contained in
 * {@code b} and {@code b} contains no other items, {@code false} otherwise
 */
public static <T> boolean equalsSet(Set<T> a, Set<T> b,
        EqualsCheck<T> equalsCheck) {
    if(a == b) {
        return true;
    }
    if(a.stream().anyMatch(a0 -> b.stream().noneMatch(b0 -> equalsCheck.equals(a0, b0)))) {
        return false;
    }
    if(b.stream().anyMatch(b0 -> a.stream().noneMatch(a0 -> equalsCheck.equals(b0, a0)))) {
        return false;
    }
    return true;
}

/**
 * Checks that all elements in {@code a} are contained in {@code b} at the
 * same position.
 *
 * @param <T> allows to restrict the type of arguments to compare
 * @param a the first list
 * @param b the second list
 * @param equalsCheck the equality check to use
 * @return {@code true} if all elements in {@code a} are contained in
 * {@code b} at the same position and {@code b} contains no other elements,
 * {@code false} otherwise
 */
public static <T> boolean equalsList(List<T> a, List<T> b,
        EqualsCheck<T> equalsCheck) {
    if(a == b) {
        return true;
    }
    if(a.size() != b.size()) {
        return false;
    }
    Iterator<T> aItr = a.iterator();
    Iterator<T> bItr = b.iterator();
    while(aItr.hasNext()) {
        T aNxt = aItr.next();
        T bNxt = bItr.next();
        if(!equalsCheck.equals(aNxt, bNxt)) {
            return false;
        }
    }
    return true;
}

/**
 * Checks that all mappings between key and value in {@code a} are contained
 * in {@code b}.
 *
 * @param <K> allows to restrict the type of keys in both maps
 * @param <V> allows to restrict the type of values in both maps
 * @param a the first map
 * @param b the second map
 * @param equalsCheckKey the equality check to use for keys
 * @param equalsCheckValue the equality check to use for values
 * @return {@code true} if the key set of {@code a} and {@code b} is equals
 * as defined by
 * {@link #equalsSet(java.util.Set, java.util.Set, EqualsCheck) }
 * and all keys in {@code a} are mapped to the same values as they are in
 * {@code b}, {@code false} otherwise
 */
public static <K,V> boolean equalsMap(Map<K,V> a, Map<K,V> b,
        EqualsCheck<K> equalsCheckKey,
        EqualsCheck<V> equalsCheckValue) {
    if(a == b) {
        return true;
    }
    if(a.size() != b.size()) {
        return false;
    }
    if(!equalsSet(a.keySet(), b.keySet(), equalsCheckKey)) {
        return false;
    }
    for(K key : a.keySet()) {
        if(!equalsCheckValue.equals(a.get(key), b.get(key))) {
            return false;
        }
    }
    return true;
}
\$\endgroup\$
0
\$\begingroup\$

i think there is no need to use an Iterator for you list compare since you can simply compare the objects inside of the list directly

boolean hasMismatch = IntStream.range(0,a.size()).allMatch(e -> a.get(e).equals(a.get(e)));

or using your functionallity again

boolean hasMismatch = IntStream.range(0,a.size()).allMatch(e -> equalsCheck.equals(a.get(e), b.get(e));

NOTE

richtercloud.project1.jar.controller is an interesting package name - maybe you can find something more suitable

\$\endgroup\$
  • \$\begingroup\$ Mhmm... accessing a List with get(int) can cause O(length^2) for linked list or similar implementation whereas an Iterator cost O(1), maybe O(length) for construction and O(length) for the iteration resulting in O(length) vs. O(length^2). The package name is not part of code review. \$\endgroup\$ – Karl Richter Mar 16 '18 at 13:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.