Search code examples
javadictionaryjava-8java-streamconcurrenthashmap

Java 8 ConcurrentHashMap initialisation


I am looking for a "CLEAN & Simple" method of initialising a ConcurrentHashMap.

With Java 8 I have this:-

private static final Map<String, String> myStreamedMap = Stream.of(
        new AbstractMap.SimpleImmutableEntry<>("Key1", "Value1"), 
        new AbstractMap.SimpleImmutableEntry<>("Key2", "Value2"), 
        new AbstractMap.SimpleImmutableEntry<>("Key3", "Value3"), 
        new AbstractMap.SimpleImmutableEntry<>("Key4", "Value4")).
        collect(Collectors.toMap((entry) -> entry.getKey(), (entry) -> entry.getValue()));

Which provides the desired end result, however I feel

"new AbstractMap.SimpleImmutableEntry<>"

Makes it hard to see whats going on.

Is there any way I can "hide" this and keep to a single line?

UPDATE

Came up with this (Obvious) solution

private static final Map<String, String> myStreamedMapWith = Stream.of(
        with("Key1", "Value1"), 
        with("Key2", "Value2"), 
        with("Key3", "Value3"), 
        with("Key4", "Value4")).
        collect(Collectors.toMap((entry) -> entry.getKey(), (entry) -> entry.getValue()));

private static AbstractMap.SimpleImmutableEntry<String, String> with(final String key, final String value) {
    return new AbstractMap.SimpleImmutableEntry<>(key, value);
}

Solution

  • Until Java 9 gets released, there is no a convenient built-in map initialization method, so I would recommend looking at a third-party library (like Google's Guava):

    new ConcurrentHashMap<>(com.google.common.collect.ImmutableMap.of("Key1", "Value1"));
    

    Anyway, the main problem here is that you are creating an instance of the HashMap.

    from the Collectors.toMap sources:

    return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
    

    If you don't want to use any external libraries, a good way to go is using the Builder pattern. Here is a simple example:

    class MapBuilder<K, V> {
    
        private List<Map.Entry<K, V>> entries = new ArrayList<>();
    
        public MapBuilder<K, V> with(K key, V value) {
            entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
    
            return this;
        }
    
        public Map<K, V> build(Supplier<Map<K, V>> mapSupplier) {
            return entries.stream().collect(Collectors.toMap(
                    Map.Entry::getKey,
                    Map.Entry::getValue,
                    (k1, k2) -> k1,
                    mapSupplier
                    )
            );
        }
    
    }
    

    And its demonstration:

    new MapBuilder().with("Key1", "Value1")
                    .with("Key2", "Value2")
                    .build(ConcurrentHashMap::new);
    

    To be fully independent of implementation (the AbstractMap.SimpleImmutableEntry), I suggest introducing a constructor that takes a BiFunction<KEY, VALUE, Map.Entry<KEY, VALUE>> entry initialiser as an argument:

    class MapBuilder<K, V> {
    
        private List<Map.Entry<K, V>> entries;
        private BiFunction<K, V, Map.Entry<K, V>> function;
    
        public MapBuilder() {
            entries = new ArrayList<>();
        }
    
        public MapBuilder(BiFunction<K, V, Map.Entry<K, V>> function) {
            this();
            this.function = function;
        }
    
        public MapBuilder<K, V> with(K key, V value) {
            entries.add(function.apply(key, value));
    
            return this;
        }
    
        public Map<K, V> build(Supplier<Map<K, V>> mapSupplier) { ... }
    
    }
    

    The call is gonna be changed to:

    new MapBuilder<>(AbstractMap.SimpleImmutableEntry::new);
    

    At this point, we can decide what implementation should be chosen for both an entry and a map separately.