oggmonster oggmonster - 3 months ago 6
Java Question

Check if only specific properties of 2 objects are equal

Say I have a a class

Person
(assume all of the properties can be set and can also be null):

public class Person {
private String firstName;
private String secondName;
private Address address;

public String getFirstName() { return firstName; }
public String getSecondName() { return secondName; }
public String getAddress() { return address; }
}


If I have two instances of
Person
, and I want to check if they both have the same
firstName
and
secondName
, I cannot simply call
equals()
as that will also check
address
for equality (and any other properties for that matter).

I would have to write a function like this:

boolean areNamesEqual(Person person1, Person person2) {
if(person1 == null || person2 == null) {
return person1 == person2;
}
if(person1.getFirstName() != null ? !person1.getFirstName().equals(person2.getFirstName()) : person2.getFirstName() != null) {
return false;
}
if(person1.getSecondName() != null ? !person1.getSecondName().equals(person2.getSecondName()) : person2.getSecondName() != null) {
return false;
}
return true;
}


Is there any cleaner way to express this? It feels like Java is making jump through quite a few hoops to express this simple idea. I have started looking at Google Guava, and have seen I can use
Objects.equal()
to improve matters:

boolean areNamesEqual(Person person1, Person person2) {
if(person1 == null || person2 == null) {
return person1 == person2;
}
if(Objects.equal(person1.getFirstName(), person2.getFirstName())) {
return false;
}
if(Objects.equal(person1.getSecondName(), person2.getSecondName())) {
return false;
}
return true;
}


But I still have to check for the Person objects themselves being null, and write getFirstName() and getSecondName() twice each. It feels like there must be a better way expressing this.

Code like this would be ideal:

(person1, person2).arePropertiesEqual(firstName, secondName)


With this I don't have check for null anywhere, I don't have to return early, and I don't have to write
firstName
or
secondName
more than once.

Any ideas?

Answer

With Java (at least before Java 8 and the lambdas) and Guava, you won't get far as Java is not a functional language. See the Caveats section in Functional Explained in the Guava Wiki for an extra opinion.

Actually, you can do it, but at the cost of more code, so you should really evaluate whether you need it. Something like:

private <T> boolean arePropertiesEqual(T t1, T t2, Function<T, ?>... functions) {
    if (t1 == null || t2 == null) {
        return t1 == t2; // Shortcut
    }
    for (Function<T, ?> function : functions) {
        if (!Objects.equal(function.apply(t1), function.apply(t2))) {
            return false;
        }
    }
    return true;
}

private static class PersonFirstNameFunction 
        implements Function<Person, String> {
    @Override
    public String apply(Person input) {
        return input.getFirstName();
    }
}

private static class PersonLastNameFunction
        implements Function<Person, String> {
    @Override
    public String apply(Person input) {
        return input.getLastName();
    }
}

private void someMethod(Person p1, Person p2) {
    boolean b = arePropertiesEqual(p1, p2, 
            new PersonFirstNameFunction(), new PersonLastNameFunction());
}

Note: I've neither compiled nor run the code above, it probably has at least warnings due to the use of generic arrays (in the varargs).

It's already shorter with Java 8, looking something like:

private <T> boolean arePropertiesEqual(T t1, T t2, Function<T, ?>... functions) {
    if (t1 == null || t2 == null) {
        return t1 == t2; // Shortcut
    }
    for (Function<T, ?> function : functions) {
        if (!Objects.equals(function.apply(t1), function.apply(t2))) {
            return false;
        }
    }
    return true;
}

private void someMethod(Person p1, Person p2) {
    boolean b = arePropertiesEqual(p1, p2,
            Person::getFirstName, Person::getLastName);
}

Note that here it's using java.util.Objects and java.util.functions.Function, not Guava.


Now, is that really "better" than the imperative code?