You need to find out how many times an object occurs within a Collection, and you need a Collection that lets you manipulate the
cardinality of objects it contains.
Use a Bag. A Bag can store the same object multiple times
while keeping track of how many copies it contains. For example, a
Bag object can contain 20 copies of
object "A" and 50 copies of object "B," and it can be queried to see how
many copies of an object it contains. You can also add or remove
multiple copies of an object—add 10 copies of "A" or remove 4 copies of
"B." The following example creates a Bag and adds multiple copies of two String objects:
import org.apache.commons.collections.Bag; import org.apache.commons.collections.bag.HashBag; Bag bag = new HashBag( ); bag.add( "TEST1", 100 ); bag.add( "TEST2", 500 ); int test1Count = bag.getCount( "TEST1" ); int test2Count = bag.getCount( "TEST2" ); System.out.println( "Counts: TEST1: " + test1Count + ", TEST2: " + test2Count ); bag.remove( "TEST1", 1 ); bag.remove( "TEST2", 10 ); int test1Count = bag.getCount( "TEST1" ); int test2Count = bag.getCount( "TEST2" ); System.out.println( "Counts: TEST1: " + test1Count + ", TEST2: " + test2Count );
This example put 100 copies of the String "TEST1" and 500 copies of the String "TEST2" into a HashBag. The contents of the Bag are then printed, and 1 instance of
"TEST1" and 10 instances of "TEST2" are removed from the Bag:
Counts: TEST1: 100, TEST2: 500 Counts: TEST1: 99, TEST2: 490
Bag has two
implementations—HashBag and TreeBag—which use a HashMap and a TreeMap to store the contents of a Bag. The same design considerations apply to
Bag that apply to Map. Use HashBag for performance and TreeBag when it is important to maintain the
order that each distinct object was added to a Bag. A TreeBag returns unique objects in the order
they were introduced to the Bag.
To demonstrate the Bag object,
a system to track inventory is created using a Bag as an underlying data structure. An
inventory management system must find out how many copies of a product
are in stock, and a Bag is
appropriate because it allows you to keep track of the cardinality of an
object in a Collection. In Example 5-3, a record store tracks an
inventory of albums, consisting of 200 Radiohead albums, 100 Kraftwerk
albums, 500 Charlie Parker albums, and 900 ABBA albums.
Example 5-3. Using a Bag to track inventory
package com.discursive.jccook.collections.bag;
import java.text.NumberFormat;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import org.apache.commons.collections.Bag;
import org.apache.commons.collections.bag.HashBag;
import org.apache.commons.lang.StringUtils;
public class BagExample {
Bag inventoryBag = new HashBag( );
// Define 4 Albums
Album album1 = new Album( "Radiohead", "OK Computer" );
Album album2 = new Album( "Kraftwerk", "The Man-Machine" );
Album album3 = new Album( "Charlie Parker", "Now's the Time" );
Album album4 = new Album( "ABBA", "ABBA - Gold: Greatest Hits" );
public static void main(String[] args) {
BagExample example = new BagExample( );
example.start( );
}
private void start( ) {
// Read our inventory into a Bag
populateInventory( );
System.out.println( "Inventory before Transactions" );
printAlbums( inventoryBag );
printSeparator( );
// A Customer wants to purchase 500 ABBA, 2 Radiohead, and 150 Parker
Bag shoppingCart1 = new HashBag( );
shoppingCart1.add( album4, 500 );
shoppingCart1.add( album3, 150 );
shoppingCart1.add( album1, 2 );
checkout( shoppingCart1, "Customer 1" );
// Another Customer wants to purchase 600 copies of ABBA
Bag shoppingCart2 = new HashBag( );
shoppingCart2.add( album4, 600 );
checkout( shoppingCart2, "Customer 2" );
// Another Customer wants to purchase 3 copies of Kraftwerk
Bag shoppingCart3 = new HashBag( );
shoppingCart3.add( album2, 3 );
checkout( shoppingCart3, "Customer 3" );
System.out.println( "Inventory after Transactions" );
printAlbums( inventoryBag );
}
private void populateInventory( ) {
// Adds items to a Bag
inventoryBag.add( album1, 200 );
inventoryBag.add( album2, 100 );
inventoryBag.add( album3, 500 );
inventoryBag.add( album4, 900 );
}
private void printAlbums( Bag albumBag ) {
Set albums = albumBag.uniqueSet( );
Iterator albumIterator = albums.iterator( );
while( albumIterator.hasNext( ) ) {
Album album = (Album) albumIterator.next( );
NumberFormat format = NumberFormat.getInstance( );
format.setMinimumIntegerDigits( 3 );
format.setMaximumFractionDigits( 0 );
System.out.println( "\t" +
format.format( albumBag.getCount( album ) ) +
" - " + album.getBand( ) );
}
}
private void checkout( Bag shoppingCart, String customer ) {
// Check to see if we have the inventory to cover this purchase
if( inventoryBag.containsAll( (Collection) shoppingCart ) ) {
// Remove these items from our inventory
inventoryBag.removeAll( (Collection) shoppingCart );
System.out.println( customer + " purchased the following items:" );
printAlbums( shoppingCart );
} else {
System.out.println( customer + ", I'm sorry " +
"but we are unable to fill your order." );
}
printSeparator( );
}
private void printSeparator( ) {
System.out.println( StringUtils.repeat( "*", 65 ) );
}
}Albums are stored in the inventoryBag variable, which is populated by a call to populateInventory() method. The printAlbums() method demonstrates how a Bag's iterator will iterate through all of the
distinct objects stored in a Bag,
printing out the count for each album by calling getCount( ) on the inventoryBag. After populating and printing
the store's inventory, the start( ) method models
the behavior of three customers. Each customer creates a Bag instance, shoppingBag, which holds the Album objects she wishes to purchase.
When a customer checks out of the store, the containsAll() method is called to make sure
that the inventoryBag contains
adequate inventory to fulfill a customer's order. If a customer attempts
to buy 40 copies of an album, we create a Bag with 40 instances of the Album object, and containsAll( ) will only return true if the inventoryBag contains at least 40 matching
albums. Certain that the order can be fulfilled, removeAll( ) reduces the number of albums in
the inventoryBag by 40 and the
customer's transaction is considered complete.
Each customer transaction is modeled by a Bag that is subtracted from the inventoryBag using the removeAll( ) method. Example
5-3 prints the inventory before and after the three customer
transactions, summarizing the result of each:
Inventory before Transactions
200 - Radiohead
100 - Kraftwerk
900 - ABBA
500 - Charlie Parker
*****************************************************************
Customer 1 purchased the following items:
002 - Radiohead
500 - ABBA
150 - Charlie Parker
*****************************************************************
Customer 2, I'm sorry but we are unable to fill your order.
*****************************************************************
Customer 3 purchased the following items:
003 - Kraftwerk
*****************************************************************
Inventory after Transactions
198 - Radiohead
097 - Kraftwerk
400 - ABBA
350 - Charlie ParkerTechnically speaking, Bag is
not a real Collection implementation.
The removeAll( ), containsAll( ), add(), remove(
), and retainAll( ) methods
do not strictly follow the contract defined by the Collection interface. Adhering to a strict
interpretation of the Collection
interface, removeAll( ) should remove
all traces of an object from a collection, and containsAll( ) should not pay attention to the
cardinality of an object in a collection. Calling removeAll( ) with a single Album object should clear the Bag of any references to the specified
Album object, and containsAll( ) should return true if a collection contains even one
instance of a specified object. In Bag, a call to removeAll( ) with three Album objects will remove only the specified
number of each Album object. In Example 5-3, the checkout( ) method uses removeAll( ) to remove albums from the
inventory. A call to containsAll( )
will only return true if a Bag contains a number greater than or equal to
the cardinality specified in the Collection. In Example 5-3, the checkout( ) method uses containsAll( ) to see if there is sufficient
inventory to satisfy an order. These violations should not keep you from
using Bag, but keep these exceptions
to the collection interface in mind if you are going to expose a
Bag as a collection in a widely used
API.
For more information about the bag data structure, look at a definition from the National Institute of Standards and Technology (NIST) at http://www.nist.gov/dads/HTML/bag.html.
