Enhanced Map in Guava – Table, BiMap, Multimap, RangeMap, ClassToInstanceMap

1. Introduction

When using Map in daily development, we often encounter many complex processing scenarios, such as: Map with multiple keys, not only the value can be obtained based on the key, but also the key can be obtained based on the value without traversing, Map with repeated keys, numbers and other ranges Mapping the same value internally, caching objects in memory, etc. Guava provides solutions to the above scenarios.

Scenario Solution Specific implementation
Map with multiple keys Table HashBasedTable, TreeBasedTable, ImmutableTable
Not only can the value be obtained based on the key, but also the value can be obtained based on the key. Get the key by value without traversing BiMap HashBiMap, ImmutableBiMap
Map of repeated keys Multimap ArrayListMultimap, LinkedListMultimap, LinkedHashMultimap, ImmutableListMultimap, ImmutableSetMultimap
Map the same value within the range of numbers RangeMap TreeRangeMap, ImmutableRangeMap
Cache objects in memory ClassToInstanceMap MutableClassToInstanceMap, ImmutableClassToInstanceMap

This blog will describe the specific sample code in detail.

2. Add dependencies

Add dependencies in Maven project pom.xml:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.0.0-jre</version>
</dependency>
3. Tbale – table structure data

Official comment translation: A collection that associates a pair of ordered keys (called a row key and a column key) with a single value.
Sample code (Requirement: record the number of people in each department of each company):

// HashMap
Map<String, Integer> deptMap = new HashMap<>();
deptMap.put("Department A", 10);
deptMap.put("Department B", 20);
Map<String, Map<String, Integer>> companyMap = new HashMap<>();
companyMap.put("xx company", deptMap);
// HashMap gets value
Integer val = companyMap.get("xx company").get("Department A");
System.out.println("HashMap gets value: " + val);

// Create Hash type Table, implemented based on Hash table
//Three generics in Table<R, C, V>: R-row, C-column, V-value
Table<String, String, Integer> hashTable = HashBasedTable.create();
hashTable.put("xx company", "A department", 10);
hashTable.put("xx company", "B department", 20);
hashTable.put("xx company", "C department", 30);

System.out.println("\\
Hash Table: " + hashTable);

//Create a Tree type Table, implemented based on red-black tree
Table<String, String, Integer> treeTable = TreeBasedTable.create();
treeTable.put("xx company", "C department", 30);
treeTable.put("xx company", "B department", 20);
treeTable.put("xx company", "A department", 10);

System.out.println("\\
Tree Table: " + treeTable);

//Create an immutable Table, which cannot be added, updated or deleted
Table<String, String, Integer> immutableTable = ImmutableTable.<String, String, Integer>builder()
        .put("xx company", "C department", 30)
        .put("xx company", "B department", 20)
        .put("xx company", "A department", 10)
        .build();

System.out.println("\\
Immutable Table: " + immutableTable);

// Table gets value
Integer val2 = hashTable.get("xx company", "A department");
System.out.println("\\
Table get value: " + val2);

//Table delete value
Integer remove = hashTable.remove("xx company", "C department");
System.out.println("\\
Table delete value: " + remove);

// Get column and value mapping based on row
Map<String, Integer> columnvalueMap = hashTable.row("xx company");
System.out.println("\\
Table column and value mapping: " + columnvalueMap);

// Get row and value mapping based on column
Map<String, Integer> rowvalueMap = hashTable.column("Department A");
System.out.println("\\
Table row and value mapping: " + rowvalueMap);

// Get the key collection
Set<String> rowKeySet = hashTable.rowKeySet();
System.out.println("\\
Table Row key set: " + rowKeySet);
Set<String> columnKeySet = hashTable.columnKeySet();
System.out.println("\\
Table Column key set: " + columnKeySet);

// Get value collection
Collection<Integer> values = hashTable.values();
System.out.println("\\
Table value collection: " + values);

// Determine containing rows
boolean containsRow = hashTable.containsRow("xx company");
System.out.println("\\
Table contains rows: " + containsRow);

// Determine the included columns
boolean containsColumn = hashTable.containsColumn("Department A");
System.out.println("\\
Table contains columns: " + containsColumn);

// Determine whether rows and columns are included
boolean contains = hashTable.contains("xx company", "A department");
System.out.println("\\
Table contains rows and columns: " + contains);

// Determine the value contained
boolean containsValue = hashTable.containsValue(10);
System.out.println("\\
Table contains value: " + containsValue);

// Row and column transpose - row to column
Table<String, String, Integer> transposeTable = Tables.transpose(hashTable);

// Get all rows
Set<Table.Cell<String, String, Integer>> cells = transposeTable.cellSet();

//Loop through the output
System.out.println("\\
Traverse output starts-------------------------------");
cells.forEach(cell -> System.out.println(cell.getRowKey() + ", " + cell.getColumnKey() + ", " + cell.getValue()));
System.out.println("\\
End of traversal output-------------------------------");

//Convert to nested Map
Map<String, Map<String, Integer>> rowMap = hashTable.rowMap();
System.out.println("\\
Table RowMap: " + rowMap);
Map<String, Map<String, Integer>> columnMap = hashTable.columnMap();
System.out.println("\\
Table ColumnMap: " + columnMap);

Results of the:

HashMap gets value: 10

Hash Table: {xx company={Department A=10, Department B=20, Department C=30}}

Tree Table: {xx company={Department A=10, Department B=20, Department C=30}}

Immutable Table: {xx company={Department C=30, Department B=20, Department A=10}}

Table gets value: 10

Table delete value: 30

Table column and value mapping: {Department A=10, Department B=20}

Table row and value mapping: {xx company=10}

Table Row key collection: [xx company]

Table Column key collection: [Department A, Department B]

Table value collection: [10, 20]

Table contains rows: true

Table contains columns: true

Table contains rows and columns: true

Table contains values: true

Traversal output starts---------------------------
Department A, Company xx, 10
Department B, xx company, 20

End of traversal output---------------------------

Table RowMap: {xx company={Department A=10, Department B=20}}

Table ColumnMap: {A department={xx company=10}, B department={xx company=20}}
4. BiMap – bidirectional mapping Map

Official Note Translation: A bimap (or “bidirectional map”) is a map that preserves the uniqueness of its values and its keys. This constraint enables a double map to support a “reverse view”, that is, another double map containing the same entries as this double map, but with opposite keys and values.
Sample code (Requirements: array and English translation):

// Create BiMap, the bottom layer is a Map of two Hash tables
BiMap<Integer, String> biMap = HashBiMap.create();
biMap.put(1, "one");
biMap.put(2, "two");
biMap.put(3, "three");
biMap.put(4, "four");
biMap.put(5, "five");

System.out.println("BiMap: " + biMap);

//Create an immutable BiMap, which cannot be added, updated or deleted.
BiMap<Object, Object> immutableBiMap = ImmutableBiMap.builder()
        .put(1, "one")
        .put(2, "two")
        .put(3, "three")
        .put(4, "four")
        .put(5, "five")
        .build();

System.out.println("\\
Immutable BiMap: " + immutableBiMap);

// Get value through key
String value = biMap.get(1);
System.out.println("\\
BiMap gets value based on key: " + value);

Integer key = biMap.inverse().get("one");
System.out.println("\\
BiMap gets key based on value: " + key);

//Modify after flipping
biMap.inverse().put("six", 6);
// Return the reverse view of the double mapping. No new object is created, it is still the previous object, so operating the flipped BiMap will affect the previous BiMap
System.out.println("\\
BiMap is affected: " + biMap);

//The bottom layer is HashMap, the key cannot be repeated
// value cannot be repeated
try {
    biMap.put(11, "one");
} catch (Exception e) {
    System.err.println("BiMap value replacement exception: " + e.getMessage());
}

//The key cannot be repeated after flipping
try {
    biMap.inverse().put("first", 1);
} catch (Exception e) {
    System.err.println("BiMap key replacement exception: " + e.getMessage());
}

// key and value can be null
biMap.put(null, null);
System.out.println("\\
BiMap gets Null value based on Null key: " + biMap.get(null));
System.out.println("\\
BiMap gets Null key based on Null value: " + biMap.inverse().get(null));

//Force replacement key
biMap.forcePut(11, "one");
System.out.println("\\
BiMap gets new key: " + biMap.inverse().get("one"));

// values are Set collection
Set<String> values = biMap.values();
System.out.println("\\
BiMap unique value: " + values);

Results of the:

BiMap: {1=one, 2=two, 3=three, 4=four, 5=five}

Immutable BiMap: {1=one, 2=two, 3=three, 4=four, 5=five}

BiMap gets value based on key: one

BiMap gets key based on value: 1

BiMap affected: {1=one, 2=two, 3=three, 4=four, 5=five, 6=six}
BiMap replace value exception: value already present: one
BiMap key replacement exception: key already present: 1

BiMap gets Null value based on Null key: null

BiMap gets Null key based on Null value: null

BiMap gets new key: 11

BiMap unique value: [two, three, four, five, six, null, one]
5. Multimap – Multiple mapping Map

Official Note Translation Maps keys to collections of values, similar to Map, but where each key may be associated with multiple values.
Sample code (Requirements: students and elective course scores in each subject):

// Create Multimap, key is HashMap, value is ArrayList
Multimap<String, Integer> arrayListMultimap = ArrayListMultimap.create();
arrayListMultimap.put("Zhang San", 90);
arrayListMultimap.put("Zhang San", 80);
arrayListMultimap.put("Zhang San", 100);
arrayListMultimap.put("李思", 88);

System.out.println("Multimap key is HashMap, value is ArrayList: " + arrayListMultimap);

//Create Multimap, key is HashMap, value is HashSet
Multimap<String, Integer> hashMultimap = HashMultimap.create();
hashMultimap.put("Zhang San", 90);
hashMultimap.put("Zhang San", 80);
hashMultimap.put("Zhang San", 100);
hashMultimap.put("李思", 88);

System.out.println("\\
Multimap key is HashMap, value is HashSet: " + hashMultimap);

// Create Multimap, key is LinkedHashMap, value is LinkedList
Multimap<String, Integer> linkedListMultimap = LinkedListMultimap.create();
linkedListMultimap.put("Zhang San", 90);
linkedListMultimap.put("Zhang San", 80);
linkedListMultimap.put("Zhang San", 100);
linkedListMultimap.put("李思", 88);

System.out.println("\\
Multimap key is LinkedHashMap, value is LinkedList: " + linkedListMultimap);

// Create Multimap, key is LinkedHashMap, value is LinkedHashMap
Multimap<String, Integer> linkedHashMultimap = LinkedHashMultimap.create();
linkedHashMultimap.put("张三", 90);
linkedHashMultimap.put("张三", 80);
linkedHashMultimap.put("张三", 100);
linkedHashMultimap.put("李思", 88);

System.out.println("\\
Multimap key is LinkedHashMap, value is LinkedHashMap: " + linkedHashMultimap);

//Create Multimap, key is TreeMap, value is TreeSet
Multimap<String, Integer> treeMultimap = TreeMultimap.create();
treeMultimap.put("Zhang San", 90);
treeMultimap.put("Zhang San", 80);
treeMultimap.put("Zhang San", 100);
treeMultimap.put("李思", 88);

System.out.println("\\
Multimap key is TreeMap, value is TreeSet: " + treeMultimap);

//Create an immutable Multimap, which cannot be added, updated or deleted. The key is ImmutableMap and the value is ImmutableList.
Multimap<String, Integer> immutableListMultimap = ImmutableListMultimap.<String, Integer>builder()
        .put("Zhang San", 90)
        .put("Zhang San", 80)
        .put("Zhang San", 100)
        .put("李思", 88)
        .build();

System.out.println("\\
Multimap key is ImmutableMap, value is ImmutableList: " + immutableListMultimap);

//Create an immutable Multimap, which cannot be added, updated or deleted. The key is ImmutableMap and the value is ImmutableSet.
Multimap<String, Integer> immutableSetMultimap = ImmutableSetMultimap.<String, Integer>builder()
        .put("Zhang San", 90)
        .put("Zhang San", 80)
        .put("Zhang San", 100)
        .put("李思", 88)
        .build();

System.out.println("\\
Multimap key is ImmutableMap, value is ImmutableSet: " + immutableSetMultimap);

// get value
Collection<Integer> values = arrayListMultimap.get("Zhang San");
System.out.println("\\
Multimap gets the value set: " + values);

// Get the value of the key that does not exist, and return an empty collection instead of null
Collection<Integer> valuesByNotExistsKey = arrayListMultimap.get("王五");
System.out.println("\\
Multimap gets the non-existent Key value set: " + valuesByNotExistsKey);

// Get value collection and add value
// What is returned is the view collection of the values associated in the multimap. No new object is created, it is still the previous object, so the operation value collection will affect the previous Multimap
values.add(60);
System.out.println("\\
Multimap is affected: " + arrayListMultimap);

// Get the size
System.out.println("\\
Multimap size:" + arrayListMultimap.size());

// Determine whether it is empty
System.out.println("\\
Multimap is empty: " + arrayListMultimap.isEmpty());

// Contains key
System.out.println("\\
Multimap contains key: " + arrayListMultimap.containsKey("Zhang San"));

// contains value
System.out.println("\\
Multimap contains value: " + arrayListMultimap.containsValue(60));

// Contains key-value key-value pairs
System.out.println("\\
Multimap contains key-value pairs: " + arrayListMultimap.containsEntry("Zhang San", 60));

//replace value
arrayListMultimap.replaceValues("张三", Arrays.asList(10, 20, 30));
System.out.println("\\
Multimap replace value: " + arrayListMultimap);

//Delete based on key-value
arrayListMultimap.remove("Zhang San", 10);
System.out.println("\\
Multimap delete based on key-value: " + arrayListMultimap);

//Delete based on key
Collection<Integer> removeAll = arrayListMultimap.removeAll("Zhang San");
System.out.println("\\
Multimap delete based on key: " + removeAll);

// Get the key collection
Set<String> keySet = arrayListMultimap.keySet();
System.out.println("\\
Multimap gets the key set (HashSet): " + keySet);
Multiset<String> keys = arrayListMultimap.keys();
System.out.println("\\
Multimap gets the key set (MultiSet): " + keys);

// Get all key-values
Collection<Map.Entry<String, Integer>> entries = arrayListMultimap.entries();
System.out.println("\\
Start traversing key-value---------------------------");
entries.forEach(entry -> System.out.println(entry.getKey() + " : " + entry.getValue()));
System.out.println("\\
End of key-value traversal--------------------------");

// Convert to Map<K, Collection<V>>
Map<String, Collection<Integer>> collectionMap = arrayListMultimap.asMap();
System.out.println("\\
Multimap converted to Map<K, Collection<V>>: " + collectionMap);

Results of the:

Multimap key is HashMap, value is ArrayList: {李思=[88], Zhang San=[90, 80, 100]}

Multimap key is HashMap, value is HashSet: {李思=[88], Zhang San=[80, 100, 90]}

Multimap key is LinkedHashMap, value is LinkedList: {Zhang San=[90, 80, 100], Li Si=[88]}

Multimap key is LinkedHashMap, value is LinkedHashMap: {Zhang San=[90, 80, 100], Li Si=[88]}

Multimap key is TreeMap, value is TreeSet: {Zhang San=[80, 90, 100], Li Si=[88]}

Multimap key is ImmutableMap, value is ImmutableList: {Zhang San=[90, 80, 100], Li Si=[88]}

Multimap key is ImmutableMap, value is ImmutableSet: {Zhang San=[90, 80, 100], Li Si=[88]}

Multimap gets the value set: [90, 80, 100]

Multimap gets a non-existent Key value set: []

Multimap is affected: {李思=[88], Zhang San=[90, 80, 100, 60]}

Multimap size:5

Whether Multimap is empty: false

Multimap contains key: true

Multimap contains value: true

Multimap contains key-value pairs: true

Multimap replace value: {李思=[88], Zhang San=[10, 20, 30]}

Multimap delete based on key-value: {李思=[88], Zhang San=[20, 30]}

Multimap delete based on key: [20, 30]

Multimap gets the key set (HashSet): [李思]

Multimap gets the key set (MultiSet): [李思]

Start traversing key-value--------------------------
John Doe : 88

End of traversing key-value--------------------------

Multimap converted to Map<K, Collection<V>>: {李思=[88]}
6. RangeMap – range mapping Map

Official comment translation: Mapping from disjoint non-empty ranges to non-null values. The query finds the value associated with the range (if any) that contains the specified key.
Sample code (Requirement: Examination score classification):

// if-else
int score = 88;
String rank;
if (0 <= score & amp; & amp; score < 60) {
    rank = "failed";
} else if (60 <= score & amp; & amp; score <= 84) {
    rank = "pass";
} else if (84 < score & amp; & amp; score <= 100) {
    rank = "Excellent";
} else {
    rank = "invalid";
}

System.out.println("if-else get value: " + rank);

// Create RangeMap, implemented based on TreeMap (red-black tree)
RangeMap<Integer, String> treeRangeMap = TreeRangeMap.create();
treeRangeMap.put(Range.closedOpen(0, 60), "failed");
treeRangeMap.put(Range.closed(60, 84), "pass");
treeRangeMap.put(Range.openClosed(84, 100), "Excellent");
treeRangeMap.put(Range.lessThan(0), "invalid");
treeRangeMap.put(Range.greaterThan(100), "invalid");

rank = treeRangeMap.get(score);
System.out.println("\\
RangeMap gets the value: " + rank);

//Create an immutable RangeMap, which cannot be added, updated or deleted
ImmutableRangeMap<Integer, String> immutableRangeMap = ImmutableRangeMap.<Integer, String>builder()
        .put(Range.closedOpen(0, 60), "failed")
        .put(Range.closed(60, 84), "pass")
        .put(Range.openClosed(84, 100), "Excellent")
        .put(Range.lessThan(0), "invalid")
        .put(Range.greaterThan(100), "invalid")
        .build();

rank = immutableRangeMap.get(score);
System.out.println("\\
ImmutableRangeMap gets the value: " + rank);

// Get the key-value pair
Map.Entry<Range<Integer>, String> entry = treeRangeMap.getEntry(88);
System.out.println("\\
RangeMap gets the key-value pair: " + entry.getKey() + " : " + entry.getValue());

// Return an immutable ascending Map
Map<Range<Integer>, String> asMapOfRanges = treeRangeMap.asMapOfRanges();
System.out.println("\\
RangeMap immutable ascending Map: " + asMapOfRanges);

// Return an immutable descending Map
Map<Range<Integer>, String> asDescendingMapOfRanges = treeRangeMap.asDescendingMapOfRanges();
System.out.println("\\
RangeMap immutable descending Map: " + asDescendingMapOfRanges);

// Merge connected ranges
RangeMap<Integer, String> treeRangeMap2 = TreeRangeMap.create();
treeRangeMap2.putCoalescing(Range.closedOpen(0, 60), "failed");
treeRangeMap2.putCoalescing(Range.closed(60, 84), "passed");
treeRangeMap2.putCoalescing(Range.openClosed(84, 100), "pass"); // or [60..84] range merging
treeRangeMap2.putCoalescing(Range.lessThan(0), "invalid");
treeRangeMap2.putCoalescing(Range.greaterThan(100), "invalid");
System.out.println("\\
RangeMap does not merge connected ranges: " + treeRangeMap.asMapOfRanges());
System.out.println("RangeMap merges connected ranges: " + treeRangeMap2.asMapOfRanges());

// minimum range
Range<Integer> span = treeRangeMap.span();
System.out.println("\\
RangeMap minimum range: " + span);

// Subrange Map
RangeMap<Integer, String> subRangeMap = treeRangeMap.subRangeMap(Range.closed(70, 90));
System.out.println("\\
RangeMap subrangeMap: " + subRangeMap);

//Merge range
treeRangeMap.merge(Range.closed(60, 100), "passed", (s, s2) -> s2);
System.out.println("\\
RangeMap merge Map: " + treeRangeMap);

// remove range
treeRangeMap.remove(Range.open(90, 95));
System.out.println("\\
RangeMap remove range: " + treeRangeMap);

// Clear all ranges
treeRangeMap.clear();
System.out.println("\\
RangeMap clears all ranges: " + treeRangeMap);

Results of the:

if-else gets value: excellent

RangeMap gets value: Excellent

ImmutableRangeMap gets value: excellent

RangeMap obtains key-value pairs: (84..100]: Excellent

RangeMap immutable ascending map: {(-∞..0)=invalid, [0..60)=fail, [60..84]=pass, (84..100]=excellent, (100. . + ∞)=invalid}

RangeMap immutable descending map: {(100.. + ∞)=invalid, (84..100]=excellent, [60..84]=passing, [0..60)=failed, (-∞ ..0)=invalid}

RangeMap does not merge connected ranges: {(-∞..0)=invalid, [0..60)=fail, [60..84]=pass, (84..100]=excellent, (100.. + ∞)=invalid}
RangeMap merges connected ranges: {(-∞..0)=invalid, [0..60)=fail, [60..100]=pass, (100.. + ∞)=invalid}

RangeMap minimum range: (-∞.. + ∞)

RangeMap sub-range Map: {[70..84]=pass, (84..90]=excellent}

RangeMap merge Map: [(-∞..0)=invalid, [0..60)=fail, [60..84]=pass, (84..100]=pass, (100.. + ∞) =invalid]

RangeMap removes the range: [(-∞..0)=invalid, [0..60)=fail, [60..84]=pass, (84..90]=pass, [95..100] =pass, (100.. + ∞)=invalid]

RangeMap clears all ranges: []
7. ClassToInstanceMap – Type mapping to instance Map

Official Note Translation: A mapping whose each entry maps a Java primitive type to an instance of that type. In addition to implementing Map, additional type-safe operations putInstance and getInstance are provided. Like any other Map mapping, this map may contain entries of primitive types, and primitive types and their corresponding wrapper types can be mapped to different values.
Sample code (Requirement: cache beans (not handed over to Spring for management, manage beans yourself)):

class UserBean {
    private final Integer id;
    private final String username;

    public UserBean(Integer id, String username) {
        this.id = id;
        this.username = username;
    }

    @Override
    public String toString() {
        return "UserBean{" + "id=" + id + ", username='" + username + '\'' + '}';
    }
}
//Create Bean
UserBean userBean = new UserBean(1, "Zhang San");

// HashMap
HashMap<Class, Object> hashMap = new HashMap<>();
hashMap.put(UserBean.class, userBean);

// Get value, need to force transfer
UserBean value = (UserBean) hashMap.get(UserBean.class);
System.out.println("HashMap gets object instance: " + value);
System.out.println("HashMap gets the object instance equal to the created Bean: " + (value == userBean));

// Create ClassToInstanceMap
ClassToInstanceMap<Object> classToInstanceMap = MutableClassToInstanceMap.create();
classToInstanceMap.putInstance(UserBean.class, userBean);

// Get the value, no need to force transfer
UserBean value2 = classToInstanceMap.getInstance(UserBean.class);
System.out.println("\\
ClassToInstanceMap Get object instance: " + value2);
System.out.println("ClassToInstanceMap gets the object instance equal to the created Bean: " + (value2 == userBean));

//Create an immutable ClassToInstanceMap, which cannot be added, updated or deleted
ClassToInstanceMap<UserBean> immutableClassToInstanceMap = ImmutableClassToInstanceMap.<UserBean>builder()
        .put(UserBean.class, userBean)
        .build();

// Get the value, no need to force transfer
UserBean value3 = immutableClassToInstanceMap.getInstance(UserBean.class);
System.out.println("\\
ImmutableClassToInstanceMap Get object instance: " + value3);
System.out.println("ImmutableClassToInstanceMap gets the object instance equal to the created Bean: " + (value3 == userBean));


// Restrict the type to avoid using HashMap to store objects. Because the Object value type is used, type verification is required when adding cache.
ClassToInstanceMap<Collection> classToInstanceMap1 = MutableClassToInstanceMap.create();
classToInstanceMap1.put(ArrayList.class, new ArrayList());
classToInstanceMap1.put(HashSet.class, new HashSet());
// Compile and save: 'put(java.lang.Class<? extends [email protected] Collection>, java.util.Collection)' in 'com.google .common.collect.MutableClassToInstanceMap' cannot be applied to '(java.lang.Class<java.util.HashMap>, java.util.HashMap)'
// classToInstanceMap1.put(HashMap.class, new HashMap());

Results of the:

HashMap gets object instance: UserBean{id=1, username='Zhang San'}
HashMap gets the object instance equal to the created Bean: true

ClassToInstanceMap gets the object instance: UserBean{id=1, username='Zhang San'}
ClassToInstanceMap gets the object instance equal to the created Bean: true

ImmutableClassToInstanceMap gets the object instance: UserBean{id=1, username='Zhang San'}
ImmutableClassToInstanceMap gets the object instance equal to the created Bean: true