4

Trying to efficiently check to see if a new copy of the object has any different fields, and if they do, update the local ones and make a note of it. If any of the fields change then I need to persist the object to the database. I don't want to make that call if I don't have to, hence the boolean.

I couldn't think of a better way to do this without using reflection, but I don't want to use reflection here because of the lack of compiler-backed safety (would have string references to field names), and not all the fields are the same type (I have some Java 8 Instant fields in there).

What I really want to avoid is the book-keeping of having to remember to add or subtract to/from the sync method when the fields are modified. Obviously subtracting is not a big deal because the method will break, but adding is scary if someone doesn't remember to update the new field.

public boolean syncWithFieldsFrom(User currentUser) {
    boolean doesUserNeedUpdating = false;
    if (!StringUtils.equals(email, currentUser.email)) {
        email = currentUser.email;
        doesUserNeedUpdating = true;
    }
    if (!StringUtils.equals(firstName, currentUser.firstName)) {
        firstName = currentUser.firstName;
        doesUserNeedUpdating = true;
    }
    if (!StringUtils.equals(lastName, currentUser.lastName)) {
        lastName = currentUser.lastName;
        doesUserNeedUpdating = true;
    }
    if (!StringUtils.equals(fullName, currentUser.fullName)) {
        fullName = currentUser.fullName;
        doesUserNeedUpdating = true;
    }
    return doesUserNeedUpdating;
}
dee-see
  • 23,668
  • 5
  • 58
  • 91
Jazzepi
  • 5,259
  • 11
  • 55
  • 81
  • 1
    An alternative would be using a code generation tool that creates the proper code to compare instances of your class. For example, MapStruct uses this to generate classes that can map object to object at runtime without the burden of using reflection nor serialization, and it performs really well. – Luiggi Mendoza Nov 14 '18 at 02:45
  • Are they always strings? – shmosel Nov 14 '18 at 02:46
  • Change your build script to generate this method during compilation and the problem is no more. – yegodm Nov 14 '18 at 02:50
  • @shmosel they're not all strings. :( – Jazzepi Nov 14 '18 at 02:58

2 Answers2

7

This may be a little overkill, but you can use lambdas to extract the fields and run a loop against them. I'll assume you have getters and setters for simplicity's sake.

private static class Field<T> {
    final Function<User, T> getter;
    final BiConsumer<User, T> setter;

    Field(Function<User, T> getter, BiConsumer<User, T> setter) {
        this.getter = getter;
        this.setter = setter;
    }

    boolean sync(User src, User dst) {
        T srcField = getter.apply(src);
        if (!Objects.equal(srcField, getter.apply(dst))) {
            setter.accept(dst, srcField);
            return true;
        }
        return false;
    }
}

private static final List<Field<?>> FIELDS = Arrays.asList(
        new Field<>(User::getEmail, User::setEmail),
        new Field<>(User::getFirstName, User::setFirstName),
        new Field<>(User::getLastName, User::setLastName),
        new Field<>(User::getFullName, User::setFullName));

public boolean syncWithFieldsFrom(User currentUser) {
    boolean updated = false;
    for (Field<?> f : FIELDS) {
        updated |= f.sync(currentUser, this);
    }
    return updated;
}
shmosel
  • 49,289
  • 6
  • 73
  • 138
  • 1
    It looks like the functions in FIELDS only fetch the field, and then Objects.equals(f.apply(), f.apply(currentUser)) checks their equality in a null-safe manner (awesome). But I don't see anything to update the field value? – Jazzepi Nov 14 '18 at 02:56
  • Oops, I totally missed that part. You can extend the example with BiConsumer, but it's a little more complicated. – shmosel Nov 14 '18 at 03:00
  • This gets me closer than I was before :) I'll tinker with it. – Jazzepi Nov 14 '18 at 03:09
  • I think I need a BiFunction not BiConsumer, if I'm to return anything – Jazzepi Nov 14 '18 at 03:11
  • 2
    @Jazzepi I've updated with setter support. It requires some more boilerplate, but it scales pretty well. – shmosel Nov 14 '18 at 03:23
  • Yeah trying to understand it now :) Looks great though. – Jazzepi Nov 14 '18 at 03:23
  • 1
    I’d call the class `Property` instead of `Field`, as it’s the combination of a getter and setter. And it lowers the chances of getting into conflict with existing classes. – Holger Nov 14 '18 at 07:59
0

For deep cloning (clones the entire object hierarchy): commons-lang SerializationUtils - using serialization - if all classes are in your control and you can force implementing Serializable.

Java Deep Cloning Library - using reflection - in cases when the classes or the 
objects you want to clone are out of your control (a 3rd party library) and you can't 
make them implement Serializable, or in cases you don't want to implement Serializable.

For shallow cloning (clones only the first level properties): commons-beanutils BeanUtils - in most cases.

 Spring BeanUtils - if you are already using spring and hence have this utility on 
 the classpath.

 I deliberately omitted the "do-it-yourself" option - the API's above provide a good 
 control over what to and what not to clone (for example using transient, or String[] 
 ignoreProperties), so reinventing the wheel isn't preferred.

please refer below url. might help you to get some idea.

Java: recommended solution for deep cloning/copying an instance

GauravRai1512
  • 834
  • 6
  • 14