How to add a single Key value to existing Java 8 stream?

Viewed 1706

Here is what I do to populate my static map

public static final Map<String, FooBar> mapEnum = 

Arrays.stream(FooBarEnum.values())
            .collect(Collectors.toMap(e-> StringUtils.upperCase(e.name), e -> e));

I want to add another single key-value to this map.

mapEnum.put("xx", FooBar.A);

Here is the enum

public enum FooBar {
   A("a"), B("b"), C("c");
}

My static map will look like this after map is constructed

{"a":FooBar.A, "b": FooBar.B, "c": FooBar.C, "xx": Foobar.A}

Is it possible to include the explicit put call into Collectors.toMap()?

4 Answers

If you're open to using a third party library you can create a static ImmutableMap inline with a Stream using Eclipse Collections.

public static final ImmutableMap<String, FooBar> MAP_ENUM =
        Arrays.stream(FooBar.values())
                .collect(Collectors2.toMap(FooBar::getName, fooBar -> fooBar))
                .withKeyValue("xx", FooBar.A)
                .toImmutable();

public enum FooBar {
    A("a"), B("b"), C("c");

    private String name;

    FooBar(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

You can also simplify the code slightly by using native Eclipse Collections APIs.

public static final ImmutableMap<String, FooBar> MAP_ENUM =
        ArrayAdapter.adapt(FooBar.values())
                .groupByUniqueKey(FooBar::getName)
                .withKeyValue("xx", FooBar.A)
                .toImmutable();

Note: I am a committer for Eclipse Collections

I actually don't see the need to use Java Streams for that. You simply can use the static block to initialize mapEnum and put additional values in it:

public static final Map<String, FooBar> mapEnum;

static {
    mapEnum = Arrays.stream(FooBar.values())
            .collect(Collectors.toMap(FooBar::getName, Function.identity()));
    mapEnum.put("xx", FooBar.A);
    // ...
}

Collectors.toMap(): There are no guarantees on the type, mutability, serializability, or thread-safety of the {@code Map} returned.

To ensure the mutability of the Map returned by Collectors.toMap(), so you can use Map.put() afterwards better use this:

Arrays.stream(FooBar.values())
        .collect(Collectors.toMap(Function.identity(), Function.identity(), (a, b) -> a, HashMap::new));

If you really want to use java streams you can use this:

public static final Map<String, FooBar> mapEnum = Stream.concat(
        Stream.of(FooBar.values()).map(e -> Map.entry(e.getName(), e)),
        Stream.of(Map.entry("xx", FooBar.A))
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Or if you also want to add all names to the enum value itself you can change your class like this:

public static enum FooBar {
    A("a", "xx"), B("b"), C("c");

    private String[] names;

    FooBar(String... names) {
        this.names = names;
    }

    public String[] getNames() {
        return names;
    }
}

And use this to create the map:

public static final Map<String, FooBar> mapEnum = Stream.of(FooBar.values())
        .flatMap(e -> Arrays.stream(e.getNames()).map(n -> Map.entry(n, e)))
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Prior to Java 9 use new AbstractMap.SimpleEntry<>() instead of Map.entry(). If you need the map to be sorted use LinkedHashMap::new with Collectors.toMap().

you can use Collectors::collectAndThen to modify the resulted map

Arrays.stream(FooBarEnum.values())
        .collect(Collectors.collectAndThen(
                    Collectors.toMap(e-> StringUtils.upperCase(e.name), 
                        Function.identity()), FooBarEnum::addCustom));

the following method is in enum

static Map<String, FooBar> addCustom(Map<String, FooBarEnum> map) {
    map.put("xx", FooBar.A);
    return map;
}

You cannot directly pass it to Collectors.toMap(). You can see all the overrides available in the javadocs: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toMap-java.util.function.Function-java.util.function.Function- .

However, you can make sure your stream has all the pairs needed to construct the map, before you call toMap by using Stream.concat. You concat the pairs from the enum, and the manual pairs you want to add.

My standalone code has to define the Pair class, but since you used StringUtils, I imagine you have a library that already includes Pair so you don't need to define it.

Code:

import java.util.*;
import java.util.stream.*;

public class Main {

    private static enum  FooBar {
        A("a"), B("b"), C("c");
        private String name;
        FooBar(String name) {
            this.name = name;
        }
    }

    public static class Pair {
        String a;
        FooBar b;
        Pair(String a, FooBar b) {
            this.a = a;
            this.b = b;
        }
    }

    public static void main(String [] args) {
        System.out.println(
            Stream.concat( 
                Arrays.stream(FooBar.values()).map(e -> new Pair(e.name.toUpperCase(), e)),
                Stream.of(new Pair("xx", FooBar.A))
            )
            .collect(Collectors.toMap(pair -> pair.a, pair -> pair.b))
        );
    }

}

Output:

{xx=A, A=A, B=B, C=C}
Related