Link

Bags

The bag data type is a collection that allows storing of elements and their cardinality, in no particular order. A Bag<T> can be seen as a special Map<T, Integer> (or Map<T, Long>), where together with the element, it stores the number of occurrences.

Bags are not part of the Java collections framework. In this page will use two popular collections libraries, Google Guava and Eclipse Collections to demonstrate some of the bag data type functionality.

Table of contents

  1. The bag data type
    1. Google Guava
    2. Eclipse Collections
  2. Size of a bag
    1. Google Guava
    2. Eclipse Collections
  3. Retrieve the count of an element that does not exist
    1. Google Guava
    2. Eclipse Collections
  4. Take elements from the bag
    1. Google Guava
    2. Eclipse Collections

The bag data type

Consider a fruit basket containing two apples and an orange. Storing the fruit in a bag data type will look like the following diagram.

Bag Data Type

Taking an apple and an orange will reduce the count for apple and will remove the orange from the bag, as shown in the following diagram.

Take Fruit from Bag

Elements which count is less than 1 are removed from the bag.

A Bag<T> can be seen as a special Map<T, Integer> (or Map<T, Long>), where together with the element, it stores the number of occurrences. The bag is a good option when a Map<T, Integer> is required as the bag type provides most of the boilerplate core required. For example, counting an element that does not exist will yield 0 instead of null.

Google Guava

Google Guava support bags as Multiset types.

dependencies {
  implementation 'com.google.guava:guava:29.0-jre'
}

Following is a simple example of the fruit basket containing two apples and an orange.

package demo;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;

public class App {
  public static void main( final String[] args ) {
    final Multiset<String> fruitBasket = HashMultiset.create();
    fruitBasket.add( "Apple" );
    fruitBasket.add( "Apple" );
    fruitBasket.add( "Orange" );

    final int apples = fruitBasket.count( "Apple" );
    System.out.printf( "We have %d apples in the basket%n", apples );
  }
}

Eclipse Collections

Eclipse Collections support bags as Bag

dependencies {
  implementation 'org.eclipse.collections:eclipse-collections:10.2.0'
}

Following is a simple example of the fruit basket containing two apples and an orange.

package demo;

import org.eclipse.collections.api.bag.MutableBag;
import org.eclipse.collections.api.factory.Bags;

public class App {
  public static void main( final String[] args ) {
    final MutableBag<String> fruitBasket = Bags.mutable.of( "Apple", "Orange" );
    fruitBasket.add( "Apple" );

    final int apples = fruitBasket.occurrencesOf( "Apple" );
    System.out.printf( "We have %d apples in the basket%n", apples );
  }
}

Size of a bag

The size of the bag is equivalent to the sum of all its elements. In our bag of fruit, we have three fruits, two apples and an orange. In this case, the bag’s size is 3. This is different for the Map data type, where the size of the map is the number of its keys. In this case, it will be 2.

Google Guava

Consider the following example.

package demo;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;

public class App {
  public static void main( final String[] args ) {
    final Multiset<String> fruitBasket = HashMultiset.create();
    fruitBasket.add( "Apple" );
    fruitBasket.add( "Apple" );
    fruitBasket.add( "Orange" );

    final int numberOfFruit = fruitBasket.size();
    System.out.printf( "We have %d fruit in the basket%n", numberOfFruit );
  }
}

The size of the above bag is 3 as shown in the following output.

We have 3 fruit in the basket

Eclipse Collections

Consider the following example.

package demo;

import org.eclipse.collections.api.bag.MutableBag;
import org.eclipse.collections.api.factory.Bags;

public class App {
  public static void main( final String[] args ) {
    final MutableBag<String> fruitBasket = Bags.mutable.of( "Apple", "Orange" );
    fruitBasket.add( "Apple" );

    final int numberOfFruit = fruitBasket.size();
    System.out.printf( "We have %d fruit in the basket%n", numberOfFruit );
  }
}

The size of the above bag is 3 as shown in the following output.

We have 3 fruit in the basket

Retrieve the count of an element that does not exist

While bags can be seen as Map<T, Integer>, these behave slightly different. Counting elements that do not exist will return a 0 and not null.

Google Guava

Consider the following example.

package demo;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;

public class App {
  public static void main( final String[] args ) {
    final Multiset<String> fruitBasket = HashMultiset.create();
    fruitBasket.add( "Apple" );
    fruitBasket.add( "Apple" );
    fruitBasket.add( "Orange" );

    final int banana = fruitBasket.count( "Banana" );
    System.out.printf( "We have %d banana in the basket%n", banana );
  }
}

The fruit "Banana" does not exists in the bag, yet the count() method returns an int (primitive type and not the Integer object wrapper) and not a null. If the item does not exist, the count() method returns 0.

Guava’s count() method relies on the object’s equals() and hashCode() methods.

⚠ Do not use as is!!The following example does not work as expected as the hashCode() method is not implemented on purpose.
package demo;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import lombok.AllArgsConstructor;

import java.util.Objects;

public class App {
  public static void main( final String[] args ) {
    final Multiset<Fruit> fruitBasket = HashMultiset.create();
    fruitBasket.add( new Fruit( "Apple" ) );
    fruitBasket.add( new Fruit( "Apple" ) );
    fruitBasket.add( new Fruit( "Orange" ) );

    final int apples = fruitBasket.count( new Fruit( "Apple" ) );
    System.out.printf( "We have %d apples in the basket%n", apples );
  }
}

@AllArgsConstructor
class Fruit {

  private final String name;

  @Override
  public boolean equals( final Object object ) {
    if ( this == object )
      return true;

    if ( !( object instanceof Fruit ) )
      return false;

    final Fruit that = (Fruit) object;
    return Objects.equals( name, that.name );
  }
}

Running the above example, will produce unexpected results as the hashCode() method is not implemented.

We have 0 apple in the basket
ⓘ NoteThis is above result may vary between runs.

The relation between these two methods is so strong that the Effective Java book has an item about this, Item 11: Always override hashCode when you override equals.

Eclipse Collections

The Eclipse collection Bag’s count() method takes a Predicate, which provides more control to the caller but adds to the verbosity.

package demo;

import org.eclipse.collections.api.bag.MutableBag;
import org.eclipse.collections.api.factory.Bags;

public class App {
  public static void main( final String[] args ) {
    final MutableBag<String> fruitBasket = Bags.mutable.of( "Apple", "Apple", "Orange" );

    final int banana = fruitBasket.count( f -> f.equals( "Banana" ) );
    System.out.printf( "We have %d banana in the basket%n", banana );
  }
}

The Bag interface also provides the occurrencesOf() method which achieves the same thing.

package demo;

import org.eclipse.collections.api.bag.MutableBag;
import org.eclipse.collections.api.factory.Bags;

public class App {
  public static void main( final String[] args ) {
    final MutableBag<String> fruitBasket = Bags.mutable.of( "Apple", "Apple", "Orange" );

    final int banana = fruitBasket.occurrencesOf( "Banana" );
    System.out.printf( "We have %d banana in the basket%n", banana );
  }
}

The occurrencesOf() method relies on the object’s equals() and hashCode() methods.

⚠ Do not use as is!!The following example does not work as expected as the hashCode() method is not implemented on purpose.
package demo;

import lombok.AllArgsConstructor;
import org.eclipse.collections.api.bag.MutableBag;
import org.eclipse.collections.api.factory.Bags;

import java.util.Objects;

public class App {
  public static void main( final String[] args ) {
    final MutableBag<Fruit> fruitBasket = Bags.mutable.of( new Fruit( "Apple" ), new Fruit( "Apple" ), new Fruit( "Orange" ) );
    System.out.printf( "The basket has %s%n", fruitBasket );

    final int apples = fruitBasket.occurrencesOf( new Fruit( "Apple" ) );
    System.out.printf( "We have %d apples in the basket%n", apples );
  }
}

@AllArgsConstructor
class Fruit {

  private final String name;

  @Override
  public boolean equals( final Object object ) {
    if ( this == object )
      return true;

    if ( !( object instanceof Fruit ) )
      return false;

    final Fruit that = (Fruit) object;
    return Objects.equals( name, that.name );
  }

  @Override
  public String toString() {
    return name;
  }
}

Running the above example, will produce unexpected results as the hashCode() method is not implemented.

The basket has [Apple, Orange, Apple]
We have 0 apple in the basket
ⓘ NoteThis is above result may vary between runs.

The relation between these two methods is so strong that the Effective Java book has an item about this, Item 11: Always override hashCode when you override equals.

Take elements from the bag

Elements can be added and removed from the bag in a similar fashion to other collections. We can only take things that exists in the bag. We cannot take more than available in the bag, thus we will never have negative counts.

Google Guava

Consider the following example.

package demo;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;

public class App {
  public static void main( final String[] args ) {
    final Multiset<String> fruitBasket = HashMultiset.create();
    fruitBasket.add( "Apple" );
    fruitBasket.add( "Apple" );
    fruitBasket.add( "Orange" );

    final boolean tookOneOrange = fruitBasket.remove( "Orange" );
    System.out.printf( "Managed to take one orange from the basket. %s%n", tookOneOrange ? "YES" : "NO" );

    final boolean tookTheSecondOrange = fruitBasket.remove( "Orange" );
    System.out.printf( "Managed to take the second orange from the basket. %s%n", tookTheSecondOrange ? "YES" : "NO" );

    final int oranges = fruitBasket.count( "Orange" );
    System.out.printf( "We have %d oranges in the basket%n", oranges );
  }
}

The above example takes two oranges from the basket that contains one orange, using the remove() method. The remove() method removes one occurrence from the bag if one is found and returns true. The remove() method will simply return false if there are no more elements in the bag for the given key.

Managed to take one orange from the basket. YES
Managed to take the second orange from the basket. NO
We have 0 oranges in the basket

Eclipse Collections

Consider the following example.

package demo;

import org.eclipse.collections.api.bag.MutableBag;
import org.eclipse.collections.api.factory.Bags;

public class App {
  public static void main( final String[] args ) {
    final MutableBag<String> fruitBasket = Bags.mutable.of( "Apple", "Apple", "Orange" );

    final boolean tookOneOrange = fruitBasket.remove( "Orange" );
    System.out.printf( "Managed to take one orange from the basket. %s%n", tookOneOrange ? "YES" : "NO" );

    final boolean tookTheSecondOrange = fruitBasket.remove( "Orange" );
    System.out.printf( "Managed to take the second orange from the basket. %s%n", tookTheSecondOrange ? "YES" : "NO" );

    final int oranges = fruitBasket.occurrencesOf( "Orange" );
    System.out.printf( "We have %d oranges in the basket%n", oranges );
  }
}

The above example takes two oranges from the basket that contains one orange, using the remove() method. The remove() method removes one occurrence from the bag if one is found and returns true. The remove() method will simply return false if there are no more elements in the bag for the given key.

Managed to take one orange from the basket. YES
Managed to take the second orange from the basket. NO
We have 0 oranges in the basket