Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initialize a map using a lambda?

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?

like image 698
Mike Nakis Avatar asked Sep 30 '15 14:09

Mike Nakis


People also ask

What is map in Lambda Java?

A Map is a collection object that maps keys to values in Java. The data can be stored in key/value pairs and each key is unique. These key/value pairs are also called map entries.


2 Answers

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 ) );
like image 55
Mike Nakis Avatar answered Sep 28 '22 04:09

Mike Nakis


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.

like image 34
Tunaki Avatar answered Sep 28 '22 05:09

Tunaki