BidiMap
in Commons Collections
provides an implementation of Map
,
which can be reversed if both the keys and values are unique; you can
use a BidiMap
to retrieve a value for a key or a key for a value. The
following example demonstrates the use of a BidiMap
to access state names by state
abbreviation and state abbreviations by state names:
BidiMap bidiMap = new DualHashBidiMap( ); bidiMap.put( "il", "Illinois" ); bidiMap.put( "az", "Arizona" ); bidiMap.put( "va", "Virginia" ); // Retrieve the key with a value via the inverse map String vaAbbreviation = bidiMap.inverseBidiMap( ).get( "Virginia" ); // Retrieve the value from the key String illinoisName = bidiMap.get( "il" );
DualHashBidiMap
stores keys and
values in two HashMap
instances. One
HashMap
stores keys as keys and
values as values, and the other HashMap
stores the inverse—values as keys and
keys as values.
In Example 5-11, a
BidiMap
is used to store country
names and country codes; an application stores ISO country codes and
translates between ISO country codes and country names to present
intelligible output—"us" is translated to "United States."
Alternatively, when a user types in a name of a country, the application
needs to be able to produce the country code for that country
name—"United States" must be translated back to "us."
Example 5-11. Storing ISO country codes in a BidiMap
package com.discursive.jccook.collections.bidi; import org.apache.commons.collections.BidiMap; import org.apache.commons.collections.bidimap.DualHashBidiMap; public class BidiMapExample { private BidiMap countryCodes = new DualHashBidiMap( ); public static void main(String[] args) { BidiMapExample example = new BidiMapExample( ); example.start( ); } private void start( ) { populateCountryCodes( ); String countryName = (String) countryCodes.get( "tr" ); System.out.println( "Country Name for code 'tr': " + countryName ); String countryCode = (String) countryCodes.inverseBidiMap( ).get("Uruguay"); System.out.println( "Country Code for name 'Uruguay': " + countryCode ); countryCode = (String) countryCodes.getKey("Ukraine"); System.out.println( "Country Code for name 'Ukraine': " + countryCode ); } private void populateCountryCodes( ) { countryCodes.put("to","Tonga"); countryCodes.put("tr","Turkey"); countryCodes.put("tv","Tuvalu"); countryCodes.put("tz","Tanzania"); countryCodes.put("ua","Ukraine"); countryCodes.put("ug","Uganda"); countryCodes.put("uk","United Kingdom"); countryCodes.put("um","USA Minor Outlying Islands"); countryCodes.put("us","United States"); countryCodes.put("uy","Uruguay"); } }
The previous example makes sense because country codes and country
names are both unique; there is only one entry for "Djibouti," "dj,"and
no other country has an overlapping code because country codes are
defined by an International Organization for Standardization (ISO)
standard, ISO 3166. If you attempt to insert a duplicate key in a
regular map, the existing entry with the same key would be replaced by
the new value. In a BidiMap
, if you
insert a duplicate value, or a duplicate key, the
entry holding this value is replaced by a new entry. The following
example illustrates this concept:
private BidiMap bidiMap = new DualHashBidiMap( ); // Insert initial content { "one:"red", "two":"green", "three":"blue" } bidiMap.put("one","red"); bidiMap.put("two","green"); bidiMap.put("three","blue"); // replace "one" key entry bidiMap.put("one","black"); // replace "green" value entry bidiMap.put("five","green"); // Contents are now { "one":"black", "three":"blue", "five":"green" }
Changing key "one," value "black" replaces the original key "one,"
value "red" because the key is duplicated; this behavior is consistent
with a normal implementation of Map
.
The difference in a BidiMap
is that
when adding key "five," value "green" to a BidiMap
, the previous key "two," value "green"
is replaced with a new entry because "green" is a duplicate value. A
regular Map
simply adds another
entry, and getting the value of either the "five," or "two," key would
return the value "green." Because "green" already occurs as a key in the
inverse map, the entry corresponding to the "two," key is removed and
replaced by a new entry. Bidirectional access to keys by value is only
possible if keys and values form two unique sets.
There are three implementations of the BidiMap
interface: DualHashBidiMap
, DialTreeBidiMap
, and TreeBidiMap
. A DualHashBidiMap
is the simplest option,
storing keys and values in two separate instances of HashMap
. When a value is requested by key, one
HashMap
is consulted, and when a key is requested by value, the
inverse HashMap
is consulted. The
DualHashMap
is likely to be your
implementation of choice if it is not important to keep track of the
insertion order; it has a straightforward implementation using the
familiar HashMap
.
If you need to preserve the order of insertion, a DualTreeBidiMap
uses two separate TreeMap
instances to hold the regular and
inverse mappings. This implementation implements the SortedMap
interface that keeps track of the
order of insertion and provides subMap(
)
, headMap( )
, and tailMap( )
methods. A third implementation,
TreeBidiMap
, implements BidiMap
without maintaining two internal
storage maps. Instead, TreeBidiMap
stores nodes in a red-black tree, identifying each node as both a key
and a value; it costs twice as much to put(
)
into a TreeBidiMap
, but
this implementation comes in handy if you are worried about memory
consumption—it does not need to store each key and value twice in two
maps.
This example used ISO 3166 country codes, a list of every country and corresponding two letter country code. If you are writing an application for a worldwide audience, you may find the following list helpful: http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html.