class Differ<T> {

        private final T existingObject;
        private final T newObject;
        private final Map<String, Diff> diffs;

        public Differ(final T existingObject, final T newObject) {
            this.existingObject = requireNonNull(existingObject, "existingObject must not be null");
            this.newObject = requireNonNull(newObject, "newObject must not be null");
            this.diffs = new HashMap<>();
        }

        /**
         * Apply value of {@code newObject}'s field to value of {@code existingObject}'s field, if both values are different.
         *
         * @param fieldName Name of the field
         * @param getter    The getter to access current values with
         * @param setter    The setter on {@code existingObject}
         * @param <V>       Type of the values being compared
         * @return {@code true} when changed, otherwise {@code false}
         */
        public <V> boolean applyIfChanged(final String fieldName, final Function<T, V> getter, final Consumer<V> setter) {
            final V existingValue = getter.apply(existingObject);
            final V newValue = getter.apply(newObject);

            if (!Objects.equals(existingValue, newValue)) {
                diffs.put(fieldName, new Diff(existingValue, newValue));
                setter.accept(newValue);
                return true;
            }

            return false;
        }

        /**
         * Apply value of {@code newObject}'s field to value of {@code existingObject}'s field, if {@code newObject}'s
         * value is not {@code null}, and both values are different.
         *
         * @param fieldName Name of the field
         * @param getter    The getter to access current values with
         * @param setter    The setter on {@code existingObject}
         * @param <V>       Type of the values being compared
         * @return {@code true} when changed, otherwise {@code false}
         */
        public <V> boolean applyIfNonNullAndChanged(final String fieldName, final Function<T, V> getter, final Consumer<V> setter) {
            final V existingValue = getter.apply(existingObject);
            final V newValue = getter.apply(newObject);

            if (newValue != null && !Objects.equals(existingValue, newValue)) {
                diffs.put(fieldName, new Diff(existingValue, newValue));
                setter.accept(newValue);
                return true;
            }

            return false;
        }

        /**
         * Apply value of {@code newObject}'s field to value of {@code existingObject}'s field, if both values are
         * different, but not {@code null} or empty. In other words, {@code null} and empty are considered equal.
         *
         * @param fieldName Name of the field
         * @param getter    The getter to access current values with
         * @param setter    The setter on {@code existingObject}
         * @param <V>       Type of the items inside the {@link Collection} being compared
         * @param <C>       Type of the {@link Collection} being compared
         * @return
         */
        public <V, C extends Collection<V>> boolean applyIfNonEmptyAndChanged(final String fieldName, final Function<T, C> getter, final Consumer<C> setter) {<FILL_FUNCTION_BODY>}

        public Map<String, Diff> getDiffs() {
            return unmodifiableMap(diffs);
        }

    }

            final C existingValue = getter.apply(existingObject);
            final C newValue = getter.apply(newObject);

            if (CollectionUtils.isEmpty(existingValue) && CollectionUtils.isEmpty(newValue)) {
                return false;
            }

            if (!Objects.equals(existingValue, newValue)) {
                diffs.put(fieldName, new Diff(existingValue, newValue));
                setter.accept(newValue);
                return true;
            }

            return false;
        
<no_super_class>