User27
User27

Reputation: 541

Is there a clean (and null safe) way to multiply the values of a map in Java?

I have a Map<String, Double>, and want to multiply all the values in the map by 2, say, but keep the nulls as nulls.

I can obviously use a for loop to do this, but was wondering if there was a cleaner way to do so?

Map<String, Double> someMap = someMapFunction();
Map<String, Double> adjustedMap = new Hashmap<>();
if (someMap != null) {
    for (Map.Entry<String,Double> pair : someMap.entryset()) {
        if (pair.getValue() == null) {
            adjustedMap.put(pair.getKey(), pair.getValue());
        } else {
            adjustedMap.put(pair.getKey(), pair.getValue()*2)
        }

    }
}

Also sometimes the map returned by someMapFunction is an immutable map, so this can't be done in place using Map.replaceAll. I couldn't come up with a stream solution that was cleaner.

Upvotes: 48

Views: 9167

Answers (12)

David Soroko
David Soroko

Reputation: 9086

If you are OK with Optional values the following may work for you:

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import static java.util.stream.Collectors.toMap;


public static Map<String, Optional<Double>> g(Map<String, Double> map, Function<Double, Double> f) {
    return map.entrySet().stream().collect(
            toMap(
                    e -> e.getKey(),
                    e -> e.getValue() == null ? Optional.empty() : Optional.of(f.apply(e.getValue()))
            ));
}

and then:

public static void main(String[] args) throws Exception {
    Map<String, Double> map = new HashMap<>();
    map.put("a", 2.0);
    map.put("b", null);
    map.put("c", 3.0);


    System.out.println(g(map, x -> x * 2));
    System.out.println(g(map, x -> Math.sin(x)));
}

prints:

{a=Optional[4.0], b=Optional.empty, c=Optional[6.0]}
{a=Optional[0.9092974268256817], b=Optional.empty, c=Optional[0.1411200080598672]}

This is fairly clean with creation of the new map delegated to Collectors and the added benefit of the return type Map<String, Optional<Double>> clearly indicating the possibility nulls and encouraging users to handle them.

Upvotes: 1

Leviand
Leviand

Reputation: 2805

You can achieve that by converting into a stream, with something like:

someMap.entrySet()
        .forEach(entry -> {
            if (entry.getValue() != null) {
                adjustedMap.put(entry.getKey(), someMap.get(entry.getKey()) * 2);
            } else {
                adjustedMap.put(entry.getKey(), null);
            }
        });

which can be shortened to:

someMap.forEach((key, value) -> {
    if (value != null) {
        adjustedMap.put(key, value * 2);
    } else {
        adjustedMap.put(key, null);
    }
});

So, if you have a map with:

Map<String, Double> someMap = new HashMap<>();
someMap.put("test1", 1d);
someMap.put("test2", 2d);
someMap.put("test3", 3d);
someMap.put("testNull", null);
someMap.put("test4", 4d);

You will get this output:

{test4=8.0, test2=4.0, test3=6.0, testNull=null, test1=2.0}

Upvotes: 5

Marco13
Marco13

Reputation: 54639

There already are many answers. Some of them seem a bit dubious to me. In any case, most of them inline the null-check in one form or the other.

An approach that takes one step up the abstraction ladder is the following:

You want to apply an unary operator to the values of the map. So you can implement a method that applies an unary operator to the values of the map. (So far, so good). Now, you want a "special" unary operator that is null-safe. Then, you can wrap a null-safe unary operator around the original one.

This is shown here, with three different operators (one of them being Math::sin, for that matter) :

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.UnaryOperator;

public class MapValueOps
{
    public static void main(String[] args)
    {
        Map<String, Double> map = new LinkedHashMap<String, Double>();
        map.put("A", 1.2);
        map.put("B", 2.3);
        map.put("C", null);
        map.put("D", 4.5);

        Map<String, Double> resultA = apply(map, nullSafe(d -> d * 2));
        System.out.println(resultA);

        Map<String, Double> resultB = apply(map, nullSafe(d -> d + 2));
        System.out.println(resultB);

        Map<String, Double> resultC = apply(map, nullSafe(Math::sin));
        System.out.println(resultC);

    }

    private static <T> UnaryOperator<T> nullSafe(UnaryOperator<T> op)
    {
        return t -> (t == null ? t : op.apply(t));
    }

    private static <K> Map<K, Double> apply(
        Map<K, Double> map, UnaryOperator<Double> op)
    {
        Map<K, Double> result = new LinkedHashMap<K, Double>();
        for (Entry<K, Double> entry : map.entrySet())
        {
            result.put(entry.getKey(), op.apply(entry.getValue()));
        }
        return result;
    }
}

I think this is clean, because it nicely separates the concerns of applying the operator and performing the null-check. And it is null-safe, because ... the method name says so.

(One could argue to pull the call to wrap the operator into a nullSafe one into the apply method, but that's not the point here)

Edit:

Depending on the intended application pattern, one could do something similar and apply the transformation in place, without creating a new map, by calling Map#replaceAll

Upvotes: 6

user987339
user987339

Reputation: 10707

You can do this with this code:

Map<String, Double> map = new HashMap<>();
map.put("1", 3.0);
map.put("3", null);
map.put("2", 5.0);

Map<String, Double> res = 
map.entrySet()
   .stream()
   .collect(
           HashMap::new, 
           (m,v)->m.put(v.getKey(), v.getValue() != null ? v.getValue() * 2 : null),
           HashMap::putAll
           );

System.out.println(res);

and the output will be:

{1=6.0, 2=10.0, 3=null}

It will allow you to keep null values in the map.

Upvotes: 3

fps
fps

Reputation: 34460

Yet another way:

Map<String, Double> someMap = someMapFunction();

int capacity = (int) (someMap.size() * 4.0 / 3.0 + 1);
Map<String, Double> adjustedMap = new HashMap<>(capacity);

if (someMap != null) someMap.forEach((k, v) -> adjustedMap.put(k, v == null ? v : v * 2));

Note that I'm building the new map with the default load factor (0.75 = 3.0 / 4.0) and an initial capacity that is always greater than size * load_factor. This ensures that adjustedMap is never resized/rehashed.

Upvotes: 2

Eugene
Eugene

Reputation: 120848

How about this?

Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.entrySet().forEach(x -> {
      if (x.getValue() != null) {
            x.setValue(x.getValue() * 2);
      }
});

Upvotes: 4

Eran
Eran

Reputation: 393791

My first instinct was to suggest a Stream of the input Map's entrySet which maps the values to new values and terminates with collectors.toMap().

Unfortunately, Collectors.toMap throws NullPointerException when the value mapper function returns null. Therefore it doesn't work with the null values of your input Map.

As an alternative, since you can't mutate your input Map, I suggest that you create a copy of it and then call replaceAll:

Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.replaceAll ((k,v) -> v != null ? 2*v : null);

Upvotes: 64

c0der
c0der

Reputation: 18792

To keep null values you can use something as simple as :

someMap.keySet()
        .stream()
        .forEach(key -> adjustedMap.put(key, (someMap.get(key)) == null ? null : someMap.get(key) * 2));

Edit in response to Petr Janeček comment: you could apply the proposed on a copy of someMap:

adjustedMap.putAll(someMap);
adjustedMap.keySet()
       .stream()
       .forEach(key -> adjustedMap.put(key, (adjustedMap.get(key)) == null ? null : adjustedMap.get(key) * 2));

Upvotes: 2

Pandey Amit
Pandey Amit

Reputation: 703

Use like this.

  Map<String, Double> adjustedMap = map.entrySet().stream().filter(x -> x.getValue() != null)
            .collect(Collectors.toMap(x -> x.getKey(), x -> 2*x.getValue()));
  //printing 
  adjustedMap.entrySet().stream().forEach(System.out::println);

Upvotes: -1

Petr Janeček
Petr Janeček

Reputation: 38424

As an alternative to streaming and/or copying solutions, the Maps.transformValues() utility method exists in Google Guava:

Map<String, Double> adjustedMap = Maps.transformValues(someMap, value -> (value != null) ? (2 * value) : null);

This returns a lazy view of the original map that does not do any work on its own, but applies the given function when needed. This can be both a pro (if you're unlikely to ever need all the values, this will save you some computing time) and a con (if you'll need the same value many times, or if you need to further change someMap without adjustedMap seeing the changes) depending on your usage.

Upvotes: 25

Oğuzhan Ayg&#252;n
Oğuzhan Ayg&#252;n

Reputation: 137

Try something like this with java 8 steam api

Map<String, Double> newMap = oldMap.entrySet().stream()
    .collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue() == null ? null: x.getValue()*2));

Upvotes: -1

CodeMatrix
CodeMatrix

Reputation: 2154

It can be done like that

someMap.entrySet().stream()
            .filter(stringDoubleEntry -> stringDoubleEntry.getValue() != null) //filter null values out
            .forEach(stringDoubleEntry -> stringDoubleEntry.setValue(stringDoubleEntry.getValue() * 2)); //multiply values which are not null

In case you need a second map where just values in which are not null just use the forEach to put them into your new map.

Upvotes: 4

Related Questions