3

Say I have a HashMap and I want to insert the same value to a list of keys. How can I do this with Java 8 without iterating through all the keys and inserting the value? This is more of a Java Streams question.

Here is the straight forward way of doing it. This is a sample code that I wrote to demonstrate what I wanted to achieve.

public void foo(List<String> keys, Integer value) {
    Map<String, Integer> myMap = new HashMap<>(); 
    for (String key : keys) {
        myMap.put(key, value);
    }
}

Is there a simpler way of doing the above using Java 8 streams? How can I avoid the for loop using Java 8 streams. Thanks!

[Edit-1] A better code snippet below.

public void foo() {
    Map<String, Integer> myMap = new HashMap<>(); 
    List<String> keys = getKeysFromAnotherFunction();
    Integer value = getValueToBeInserted(); // Difficult to show my actual use case. Imagine that some value is getting computed which has to be inserted for the keys.
    for (String key : keys) {
        myMap.put(key, value);
    }  

    List<String> keys2 = getNextSetOfKeys();
    Integer newValue = getValueToBeInserted(); 
    for (String key : keys2) {
        myMap.put(key, newValue);
    } 
}
Naman
  • 27,789
  • 26
  • 218
  • 353
SyncMaster
  • 9,754
  • 34
  • 94
  • 137
  • Is `myMap` actually defined outside the `foo` method? – ernest_k Apr 23 '19 at 16:06
  • Yes. that is right. This is not the actual code. It is a small code snippet that I quickly came up to better explain what I wanted to achieve. – SyncMaster Apr 23 '19 at 16:06
  • 1
    It could hardly be simpler than that. You need a loop. There's no shortcut. – JB Nizet Apr 23 '19 at 16:10
  • 2
    There's no way to do this without iterating (unless "iterating" excludes `keys.forEach(e -> myMap.put(e, value);`) – ernest_k Apr 23 '19 at 16:12
  • Possible duplicate of [Java: How to convert List to Map](https://stackoverflow.com/questions/4138364/java-how-to-convert-list-to-map) – Samuel Philipp Apr 23 '19 at 16:26
  • This is not about converting list to a map as I am aware of that. I want to add a same value to a bunch of keys of a Map. – SyncMaster Apr 23 '19 at 16:46
  • 1
    Your idea is pretty straight forward. No need to use streams (they can incur signifcant overhead) but you might want to consider generalizing for "foo" method to take a list of keys and a value. And also consider using myMap.putIfAbsent() if you don't want to accidentally replace an existing value. – WJS Apr 23 '19 at 20:43
  • Unfortunately, the following only works for `ConcurrentHashMap`: `ConcurrentHashMap myMap = new ConcurrentHashMap<>(); myMap.keySet(getValueToBeInserted()).addAll(getKeysFromAnotherFunction());` – Holger Apr 24 '19 at 13:51

2 Answers2

6

Using collector, something like:

Map<String, Integer> myMap = keys.stream()
            .collect(Collectors.toMap(key -> key,
                    val -> value, (a, b) -> b));
Naman
  • 27,789
  • 26
  • 218
  • 353
  • 1
    This is not "a simpler way" (a stated requirement) and does not use a myMap that is defined outside of the method (another stated requirement). – DodgyCodeException Apr 23 '19 at 16:29
  • 1
    Thanks! I was looking for something like this. – SyncMaster Apr 23 '19 at 16:46
  • 1
    @DodgyCodeException I would say "simpler" could be opinion based then. Anyway not sure what is the `myMap` outside the method context you've commented about. The variable `myMap` seems local to the method in the code in question. – Naman Apr 23 '19 at 18:17
1

I think that your question is about factoring out some piece of code more than converting traditional for loops into stream constructs.

Suppose you have the following generic utility method:

public static <K, V, M extends Map<K, V>> M fillMap(
        Supplier<List<K>> keysFactory,
        Supplier<V> singleValueFactory,
        Supplier<M> mapFactory) {

    M map = mapFactory.get();
    List<K> keys = keysFactory.get();
    V singleValue = singleValueFactory.get();

    keys.forEach(k -> map.put(k, singleValue));

    return map;
}

Then, you could use the above method as follows:

Map<String, Integer> myMap = fillMap(() -> getKeysFromAnotherFunction(),
                                     () -> getValueToBeInserted(),
                                     HashMap::new); // create HashMap

myMap = fillMap(() -> getNextSetOfKeys(),
                () -> getValueToBeInserted(),
                () -> myMap); // use previously created map

There are variants for the code above, i.e., the method could receive a Map<K, V> instance instead of a Supplier<Map<K, V>>, or it might even be overloaded to support both variants.

fps
  • 33,623
  • 8
  • 55
  • 110