How to initialize a map using a lambda?

时间秒杀一切 提交于 2019-12-30 04:24:11

问题


I want to declare a fully populated map field in a single statement, (which may contain several nested statements,) like this:

private static final Map<Integer,Boolean> map = 
    something-returning-an-unmodifiable-fully-populated-HashMap;

Anonymous initializers won't do, for the same reason that invoking a function which returns a new populated map won't do: they require two top-level statements: one for the variable declaration, and one for the method or initializer.

The double curly bracket ({{ and }}) idiom will work, but it creates a whole new class which extends HashMap<>, and I do not like the overhead represented by this.

Do the lambdas of Java 8 perhaps offer a better way of accomplishing this?


回答1:


If you want to initialize a Map in a single statement, you can use Collectors.toMap.

Imagine you want to build a Map<Integer, Boolean> mapping an integer to the result of calling some function f:

private static final Map<Integer,Boolean> MAP = 
        Collections.unmodifiableMap(IntStream.range(0, 1000)
                                             .boxed()
                                             .collect(Collectors.toMap(i -> i, i -> f(i))));

private static final boolean f(int i) {
    return Math.random() * 100 > i;
}

If you want to initialize it with "static" known values, like the example in your answer, you can abuse the Stream API like this:

private static final Map<Integer, Boolean> MAP = 
       Stream.of(new Object[] { 1, false }, new Object[] { 2, true })
             .collect(Collectors.toMap(s -> (int) s[0], s -> (boolean) s[1]));

Note that this is a real abuse and I personally would never use it: if you want to construct a Map with known static values, there is nothing to gain from using Streams and you would be better off to use a static initializer.




回答2:


If you really want to do initialize the map in single statement, you can write your custom builder and use it in your project. Something like this:

public class MapBuilder<K, V> {
    private final Map<K, V> map;

    private MapBuilder(Map<K, V> map) {
        this.map = map;
    }

    public MapBuilder<K, V> put(K key, V value) {
        if(map == null)
            throw new IllegalStateException();
        map.put(key, value);
        return this;
    }

    public MapBuilder<K, V> put(Map<? extends K, ? extends V> other) {
        if(map == null)
            throw new IllegalStateException();
        map.putAll(other);
        return this;
    }

    public Map<K, V> build() {
        Map<K, V> m = map;
        map = null;
        return Collections.unmodifiableMap(m);
    }

    public static <K, V> MapBuilder<K, V> unordered() {
        return new MapBuilder<>(new HashMap<>());
    }

    public static <K, V> MapBuilder<K, V> ordered() {
        return new MapBuilder<>(new LinkedHashMap<>());
    }

    public static <K extends Comparable<K>, V> MapBuilder<K, V> sorted() {
        return new MapBuilder<>(new TreeMap<>());
    }

    public static <K, V> MapBuilder<K, V> sorted(Comparator<K> comparator) {
        return new MapBuilder<>(new TreeMap<>(comparator));
    }
}

Usage example:

Map<Integer, Boolean> map = MapBuilder.<Integer, Boolean>unordered()
    .put(1, true).put(2, false).build();

This works in Java-7 as well.

As a side note, we will likely to see something like Map.of(1, true, 2, false) in Java-9. See JDK-8048330 for details.




回答3:


Google Guava's Maps class provides some nice utility methods for this. Additionally, there is the ImmutableMap class and its static methods. Have a look at:

    ImmutableMap.of(key1, value1, key2, value2, ...);
    Maps.uniqueIndex(values, keyExtractor);
    Maps.toMap(keys, valueMapper);



回答4:


Here is how to implement a field initializer in Java 8 in a single statement using a lambda.

private static final Map<Integer,Boolean> map =
        ((Supplier<Map<Integer,Boolean>>)() -> {
            Map<Integer,Boolean> mutableMap = new HashMap<>();
            mutableMap.put( 1, false );
            mutableMap.put( 2, true );
            return Collections.unmodifiableMap( mutableMap );
        }).get();

Java 9 solution:

private static final Map<Integer,Boolean> map = Map.of( 1, false, 2, true );

and if you have more than 10 entries, Map.of() won't work, so you need this:

private static final Map<Integer,Boolean> map = Map.ofEntries( 
        Map.entry( 1, false ), 
        Map.entry( 2, true ), 
        Map.entry( 3, false ), 
        Map.entry( 4, true ), 
        Map.entry( 5, false ), 
        Map.entry( 6, true ), 
        Map.entry( 7, false ), 
        Map.entry( 8, true ), 
        Map.entry( 9, false ), 
        Map.entry( 10, true ), 
        Map.entry( 11, false ) );


来源:https://stackoverflow.com/questions/32868665/how-to-initialize-a-map-using-a-lambda

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!