Map vs. Bag
--
Discover a better way to count things in Java.
Using Map to count the beads on an abacus
Here’s a simple problem to solve using a Map
. How can we count the number of beads tallied on an abacus by color? First, I will define an enum
for Color
.
Next, I will create a HashMap<Color, Integer>
and use the merge
method to add and subtract values for a specific colors. I will leave one color bead (Color.RED
) out from the counting. Then I will assert the result of querying the Map
for the counts of the colors using Map.get
.
I use the same merge
operation for adding and subtracting. I use negative Integer
values to subtract. I use the method reference Integer::sum
for the BiFunction
parameter the merge
method takes to determine how to merge values. Note, that even though I am using primitive literal int
values here, they are auto-boxed as Integer
objects in the Map
., and the Integer::sum
call will result in more unboxing and boxing of Integer
objects.
In the final assertion in the test, the call to get
with the Color.RED
instance returns null
. I would have to explicitly set the value to 0
for it to come back as 0
when calling get
. One way to address the null
return problem with get
, is to use getOrDefault
instead. This will allow you to return a default value in the case that the key does not exist in the Map
.
The merge
and getOrDefault
methods were both added in Java 8, and are useful methods for developers to understand on Map
.
Using a primitive Map instead of a Map
In Eclipse Collections, there is support for primitive Maps. There is an ObjectIntMap
type and corresponding implementation that can be used to count the beads. I will use a MutableObjectIntMap
so I can mutate the map and create the implementation using the ObjectIntMaps
factory class.
A MutableObjectIntMap
has an addToValue
method that can be used to put a new value in the map, or add to an existing value in a map. The method getIfAbsent
can be used to look up the values. The 0
value is an int
, not an Integer
object, and none of the calls to addToValue
result in any boxing.
Note: Eclipse Collections HashBag
class leverages a MutableObjectIntMap
.
Using Bag to count the beads on an abacus
So how would I solve this problem using a MutableBag
from Eclipse Collections? A MutableBag
is a Collection
, not a Map
. It has add
and remove
methods which I can use to add single items. MutableBag
also has addOccurrences
and removeOccurrences
methods which allow adding a specific number of items at once.
The individually named methods on Bag
are more intention revealing than the single method named merge
for this use case. Notice that in the case of Color.RED
, the Bag will return 0
from the call to occurrencesOf
. Bag
does not suffer from the null
problem that Map.get
does. If a value does not exist in the Bag
, the occurrencesOf
method always returns 0
.
More Information
There are a few more blogs which provide more details on the Bag
type and supporting methods like countBy
in Eclipse Collections. The following blog was written by Nikhil Nanivadekar. It describes many of the implementation details of the Bag
type in Eclipse Collections, that can result in both memory and performance benefits.
I also wrote a separate blog describing the countBy
method in Eclipse Collections which provides Bag
as its return type.
I hope you enjoyed reading this blog and learned something useful about Map
and Bag
types in the process!
I am a Project Lead and Committer for the Eclipse Collections OSS project at the Eclipse Foundation. Eclipse Collections is open for contributions. If you like the library, you can let us know by starring it on GitHub.
Other Java Articles you may like