1. Overview

In this tutorial, we'll learn how to construct a map with primitive keys and values.

As we know, the core Java Maps don't allow the storage of primitive keys or values. That's why we'll introduce some external third-party libraries that provide primitive map implementations.

2. Eclipse Collections

Eclipse Collections is a high-performance collection framework for Java. It provides improved implementations as well as some additional data structures, including several primitive collections.

2.1. Mutable and Immutable Maps

Let's create an empty map where both keys and values are primitive ints. For that, we'll use the IntIntMaps factory class:

MutableIntIntMap mutableIntIntMap = IntIntMaps.mutable.empty();

The IntIntMaps factory class is the most convenient way to create primitive maps. It allows us to create both mutable and immutable instances of the desired type of map. In our example, we created the mutable instance of IntIntMap. Similarly, we can create an immutable instance by simply replacing the IntIntMaps.mutable static factory call with IntIntMaps.immutable:

ImmutableIntIntMap immutableIntIntMap = IntIntMaps.immutable.empty();

So, let's add a key, value pair to our mutable map:

mutableIntIntMap.addToValue(1, 1);

Likewise, we can create mixed maps with reference and primitive type key-value pairs. Let's create a map with String keys and double values:

MutableObjectDoubleMap dObject = ObjectDoubleMaps.mutable.empty();

Here, we used the ObjectDoubleMaps factory class to create a mutable instance for MutableObjectDoubleMap.

Now let's add some entries:

dObject.addToValue("price", 150.5);
dObject.addToValue("quality", 4.4);
dObject.addToValue("stability", 0.8);

2.2. A Primitive API Tree

In Eclipse Collections, there's a base interface called PrimitiveIterable. This is the base interface for each of the primitive containers of the library. All are named PrimitiveTypeIterable, where PrimitiveType can be Int, Long, Short, Byte, Char, Float, Double, or Boolean.

All these base interfaces, in turn, have their tree of XY**Map implementations, which is divided on whether the map is mutable or immutable. As an example, for IntIntMap, we have MutableIntIntMap and ImmutableIntIntMap.

Finally, as we saw above, we have interfaces to cover all kinds of combinations of types for keys and values for both primitive and object values. So, for example, we can have IntObjectMap for a primitive key with an Object value or ObjectIntMap for its opposite case.

3. HPPC

HPPC is a library geared towards high performance and memory efficiency. This means that the library has less abstraction than others. However, this has the benefit of exposing the internals to useful low-level manipulation. It provides both maps and sets.

3.1. A Simple Example

Let's start by creating a map that has an int key and a long value. Using this is pretty familiar:

IntLongHashMap intLongHashMap = new IntLongHashMap();
intLongHashMap.put(25, 1L);
intLongHashMap.put(150, Long.MAX_VALUE);
intLongHashMap.put(1, 0L);
        
intLongHashMap.get(150);

HPPC provides maps for all combinations of keys and values:

  • Primitive key and primitive value
  • Primitive key and object-type value
  • Object-type key and primitive value
  • Both Object-type key and value

Object-type maps support generics:

IntObjectOpenHashMap<BigDecimal>
ObjectIntOpenHashMap<LocalDate>

The first map has a primitive int key and a BigDecimal value. The second map has LocalDate for its keys and int for its values

3.2. Hash Maps vs Scatter Maps

Due to the way key hashing and distribution functions are traditionally implemented, we could have collisions when hashing the keys. Depending on how keys are distributed, this can lead to performance problems on huge maps. By default, HPPC implements a solution that avoids this problem.

However, there is still a place for maps that have a simpler distribution function. This is useful if the maps are used as lookup tables or for counting, or if they don't require lots of write operations once loaded. HHPC provides Scatter Maps to boost performance even more.

All the scatter-map classes maintain the same naming convention as maps, but instead use the word Scatter:

  • IntScatterSet
  • IntIntScatterMap
  • IntObjectScatterMap

4. Fastutil

Fastutil is a fast and compact framework that provides type-specific collections including primitive type maps.

4.1. Quick Example

Similar to Eclipse Collections and HPPC. Fastutil also provides primitive-to-primitive and primitive-to-Object typed association maps.

Let's create an int to boolean map:

Int2BooleanMap int2BooleanMap = new Int2BooleanOpenHashMap();

And now, let's add some entries:

int2BooleanMap.put(1, true);
int2BooleanMap.put(7, false);
int2BooleanMap.put(4, true);

Then, we can retrieve values from it:

boolean value = int2BooleanMap.get(1);

4.2. In-Place Iteration

Standard JVM collections that implement the Iterable interface usually create a fresh temporary iterator object at each iteration step. With huge collections, this can create a garbage collection issue.

Fastutil provides an alternative that greatly mitigates this:

Int2FloatMap map = new Int2FloatMap();
//Add keys here
for(Int2FloatMap.Entry e : Fastutil.fastIterable(map)) {
    //e will be reused on each iteration, so it will be only one object
}

Fastutil also provides the fastForeach method. This will take a Consumer functional interface and perform a lambda-expression for each loop:

Int2FloatMap map = new Int2FloatMap();
//Add keys here
Int2FloatMaps.fastForEach(map , e ->  {
    // e is also reused across iterations
});

This is very similar to the standard Java foreach construct:

Int2FloatMap map = new Int2FloatMap();
//Add keys here
map.forEach((key,value) -> {
    // use each key/value entry   
});

5. Conclusion

In this article, we learned how to create primitive maps in Java using Eclipse Collections, HPPC, and Fastutil.

As always, the example code for this article is available over on GitHub.