You can just collect into a HashMap which allows null values without the need for an Optional:
private static <T> Map<String, Object> getDifference( final T a, final T b, final Map<String, Function<T, Object>> fields) { return fields.entrySet().stream() .map(e -> { final Function<T, Object> getter = e.getValue(); final Object value = getter.apply(b); return Objects.equals(getter.apply(a),value)? null: Pair.of(e.getKey(), value); }) .filter(Objects::nonNull) .collect(HashMap::new, (m,p) -> m.put(p.getKey(),p.getValue()), Map::putAll); }
By the way, it is discouraged to use wildcards in return types, they can make the caller’s life unnecessarily hard for no benefit.
For comparison, here the same operation without Stream:
private static <T> Map<String, Object> getDifference( final T a, final T b, final Map<String, Function<T, Object>> fields) { HashMap<String, Object> result = new HashMap<>(); fields.forEach((key, getter) -> { final Object value = getter.apply(b); if(!Objects.equals(getter.apply(a), value)) result.put(key, value); }); return result; }
Of course, this would also work with optional:
private static <T> Map<String, Optional<Object>> getDifference( final T a, final T b, final Map<String, Function<T, Object>> fields) { HashMap<String, Optional<Object>> result = new HashMap<>(); fields.forEach((key, getter) -> { final Object value = getter.apply(b); if(!Objects.equals(getter.apply(a), value)) result.put(key, Optional.ofNullable(value)); }); return result; }
But if all you want to do, is replace null with an empty string, you don’t need an Optional:
private static <T> Map<String, Object> getDifference( final T a, final T b, final Map<String, Function<T, Object>> fields) { HashMap<String, Object> result = new HashMap<>(); fields.forEach((key,getter) -> { final Object value = getter.apply(b); if(!Objects.equals(getter.apply(a), value)) result.put(key, value==null? "": value); }); return result; }
and well, this substitution would also work out-of-the-box with your original code, if you just do it in the map function instead of the collector:
private static <T> Map<String, ?> getDifference(final T a, final T b, final Map<String, Function<T, Object>> fields) { return fields.entrySet().stream() .map(e -> { final String name = e.getKey(); final Function<T, Object> getter = e.getValue(); final Object pairKey = getter.apply(a); final Object pairValue = getter.apply(b); if (Objects.equals(pairKey, pairValue)) { return null; } else { return Pair.of(name, pairValue==null? "": pairValue); } }) .filter(Objects::nonNull) .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); }
or
private static <T> Map<String, Object> getDifference( final T a, final T b, final Map<String, Function<T, Object>> fields) { return fields.entrySet().stream() .map(e -> { final Function<T, Object> getter = e.getValue(); final Object pairValue = getter.apply(b); return Objects.equals(getter.apply(a), pairValue)? null: Pair.of(e.getKey(), pairValue==null? "": pairValue); }) .filter(Objects::nonNull) .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); }