Map of Primitives in Java

 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 storage of primitive keys or values. That’s why we’ll introduce a number of external third-party libraries which provide primitive map implementations.

2. Eclipse Collections

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

More specifically, there are mutable, immutable, and unmodifiable implementations of a primitive map.

2.1. Mutable and Immutable Maps

Let’s create an empty map where both key and value 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, PrimitiveIterable is the base interface for all the primitive containers, including ones for Map. Moreover, each primitive type has its own base interface deriving from PrimitiveIterable.

For example, the ShortIterable base interface is for the short primitive type and LongIterable is for the long primitive type. Similarly, for int, byte, float, double, boolean, and char types, the base interfaces are IntIterable, ByteIterable, FloatIterable, DoubleIterable, BooleanIterable, and CharIterable, respectively.

All these base interfaces have their own tree of XYMap implementations:

First, each interface has two kinds of implementations, mutable and immutable. We just saw this with IntIntMap, MutableIntIntMap, and ImmutableIntIntMap.

Second, all these primitive map implementations have all the combinations between their primitive and the rest of the primitive types. So, for int, we have FloatIntMap – and its mutable and immutable implementations – but we also have IntFloatMap.

Finally, as we saw just a moment ago, we have interfaces for mixing primitive and reference types. For int, we get ObjectIntMap<K> for mixing an Object key with a primitive type and the inverse, IntObjectMap<K>, for mixing a primitive key with an Object value. K, of course, is the reference type for the required object.

3. Trove

Trove is another high-performance library which provides primitive collections for Java.

3.1. Quick Start

Firstly, let’s create a primitive map accepting double type keys and int values:

TDoubleIntMap doubleIntMap = new TDoubleIntHashMap();

Then, let’s add some entries:

doubleIntMap.put(1.2, 22);
doubleIntMap.put(4.5, 16);

3.2. Adjustable Entries

An interesting feature in Trove is the ability to adjust entry values.

Now, when we have a map with keys and values, we can adjust a few entries:

doubleIntMap.adjustValue(1.2, 1);
doubleIntMap.adjustValue(4.5, 4);
doubleIntMap.adjustValue(0.3, 7);

Here, the first two operations will increase the 1.2 and 4.5 keys with 1 and 4 units. However, the last adjustValue() will return false with no effect. This is because we simply don’t have any entry with a 0.3 key.

3.3. Complete Constructors

We’ve all experienced the pain of constructing a Map in Java, need to call the constructor and then separately supply the elements.

With Trove, if we know the keys and values in advance, we can provide the values as a constructor argument. First, let’s define the keys array:

double[] doubles = new double[] {1.2, 4.5, 0.3};

Next, the values:

int[] ints = new int[] {1, 4, 0};

Finally, we pass them to the constructor:

TDoubleIntMap doubleIntMap = new TDoubleIntHashMap(doubles, ints);

3.4. Cheat Sheet

Along with all the standard methods available in the JDK’s Map implementations, Trove provides a few more useful methods:

  • putIfAbsent(key, value) – adds the key, value entry only if the map doesn’t contain the key

  • adjustOrPutValue(key, adjust_amount, put_amount) – where the put_amount is the initial value, adjust_amount is the amount of the increment

  • adjustValue(key, amount) – is similar to adjustOrPutValue() with one exception: It won’t set up the initial value. So, if we don’t have any association with the given key, adjustValue() won’t change anything and returns false

  • increment(key) – internally calls the adjustValue(key, 1)

  • retainEntries(TDoubleIntProcedure) – retains only the entries that satisfy the procedure

The gnu.trove.map package contains all the interfaces to work with primitive maps. Moreover, we can use all the combinations of the key, value pairs like:

  • A primitive key and primitive value – for example, TDoubleIntMap interface is a primitive map of double key and int value

  • A primitive key and Object value – TByteObjectMap<T> can have a byte key and reference type of T

  • And, finally, an Object key and primitive value – TObjectByteMap<T> is a primitive map of T type Object keys and byte values

Under the hood, all the primitive maps have three primitive containers to hold the data:

  • _values[] – keeps the primitive values

  • _set[] – holds the key primitives

  • _states[] – maintains the cell state – free, occupied, deleted

4. Colt

Colt is an open-source library for scientific and technical computing in Java.

4.1. A Simple Equivalent

Colt shines as a simple primitive-to-primitive equivalent to Java’s Map interface.

First, let’s create an int to double association map:

AbstractIntDoubleMap map = new OpenIntDoubleHashMap();

Then, we can add value:

map.put(1, 4.5);

And retrieve it:

double value = map.get(1);

Additionally, Colt primitive maps provide standard methods like containsKey(value), containsValue(key), and keyOf(value).

4.2. Open Addressing vs Chaining

The cern.colt.map package contains automatically growing and shrinking maps for double and int primitive data types. Under the hood, they use open addressing with double hashing. Open addressing means that all entries are stored in the hash table without using additional data structures.

Java, on the other hand, uses chaining. This is good for resolving collisions but has more memory footprint due to its requiring numerous LinkedLists.

For double to int associations, the core Java’s HashMap equivalent is OpenDoubleIntHashMap. Similarly, OpenIntDoubleHashMap is for int to double, and OpenIntIntHashMap for int to int. And finally, OpenLongObjectHashMap class is for long to Object types.

On top of that, each map implementation has an abstract base class like AbstractDoubleIntMap, AbstractIntDoubleMap, AbstractIntIntMap, and AbstractLongObjectMap.

5. Fastutil

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

5.1. Quick Example

Similar to Eclipse Collections and Trove, Fastutil also provides primitive to primitive and primitive to Object typed association maps.

Let’s create int to boolean primitive map instance:

Int2BooleanMap int2BooleanMap = new Int2BooleanOpenHashMap();

Further, let’s add some entries in it:

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

Moreover, we can retrieve some of the values by its key:

boolean value = int2BooleanMap.get(1);

Finally, here’s how we create a sorted int to boolean empty map:

Int2BooleanSortedMap int2BooleanSorted = Int2BooleanSortedMaps.EMPTY_MAP;

5.2. Primitive Maps

Let’s choose the int to boolean key-value association as an example. The it.unimi.dsi.fastutil.ints package contains a variety of interfaces like Int2BooleanMap and Int2BooleanSortedMap.

Curiously, we also have Int2BooleanOpenHashMap for open-addressing, like Colt.

Moreover, we can create maps with a custom hashing strategy with Int2BooleanOpenCustomHashMap. This is nice since primitives can’t extend equals and hashCode on their own.

6. Conclusion

In this article, we learned how to create primitive maps in Java using Eclipse Collections, Trove, Colt, andFastutil.

Finally, the complete code for this article is available over on GitHub.

Leave a Reply

Your email address will not be published.