Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Collecting values that could be null

I have the following code:

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);
                }
            })
            .filter(Objects::nonNull)
            .collect(Collectors.toMap(Pair::getKey, Pair::getValue));
}

Now, pairValue can be null. In order to avoid the NPE as described here, while "collect"-ing, I wish to ensure that I send only those values that are non-null. If null, I want to send "".

So, I tried replacing the last line with this:

.collect(Collectors.toMap(Pair::getKey,Optional.ofNullable(Pair::getValue).orElse(""));

And other modifications thereof:

.collect(Collectors.toMap(pair -> pair.getKey(), Optional.ofNullable(pair -> pair.getValue()).orElse(""));

Does not compile. I'm not sure what is needed here. Any help?

like image 631
user2116243 Avatar asked Feb 15 '18 04:02

user2116243


1 Answers

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));
}
like image 126
Holger Avatar answered Oct 08 '22 14:10

Holger