Guava is a Java class library extension toolkit developed by Google. It contains rich APIS, covering collection, caching, concurrency, I/O, and many other aspects. Using these apis simplifies our code and makes it more elegant. On the other hand, it adds a lot of functionality not found in the JDK and makes our development more efficient.

Hydra today shares some of the Map operations that Guava encapsulates, and after using these features, it’s nice to say. Let’s introduce dependency coordinates first, and then start our formal experience

<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> < version > 30.1.1 - jre < / version > < / dependency >Copy the code

Table – Double key Map

A Map in Java allows only one key and one value, whereas a Table in Guava allows two keys for a value. The two keys in a Table are called rowKey and columnKey, which are rows and columns. (I don’t think it’s quite right to think of them as rows and columns, but it’s more appropriate to think of them as two columns)

As a simple example, consider keeping track of the number of days an employee works each month. A normal Map implementation in Java would require two levels of nesting:

Map<String,Map<String,Integer>> map=new HashMap<>(); Map<String,Integer> workMap=new HashMap<>(); workMap.put("Jan",20); workMap.put("Feb",28); map.put("Hydra",workMap); Integer dayCount = map.get("Hydra").get("Jan");Copy the code

This is easy if you use tables. Take a look at the simplified code:

Table<String,String,Integer> table= HashBasedTable.create(); Table. Put ("Hydra", "Jan", 20); table.put("Hydra", "Feb", 28); table.put("Trunks", "Jan", 28); table.put("Trunks", "Feb", 16); Integer dayCount = table.get("Hydra", "Feb");Copy the code

We don’t need to build a complex two-layer Map, just one layer. In addition to element access, let’s look at some other practical operations.

1. Get a set of keys or values

Set<String> rowKeys = table.rowKeySet(); Set<String> columnKeys = table.columnKeySet(); //value Collection<Integer> values = table.values();Copy the code

Print their results separately. The key set contains no duplicate elements, and the value set contains all elements without de-duplicating:

[Hydra, Trunks][Jan, Feb][20, 28, 28, 16]
Copy the code

2. Calculate the sum of all values corresponding to the key

The following uses the sum of rowKeys as an example:

for (String key : table.rowKeySet()) { Set<Map.Entry<String, Integer>> rows = table.row(key).entrySet(); int total = 0; for (Map.Entry<String, Integer> row : rows) { total += row.getValue(); } System.out.println(key + ": " + total); }Copy the code

Print result:

Hydra: 48Trunks: 44
Copy the code

3. Convert rowKey to columnKey

This operation, also known as transpose of rows and columns, calls Tables’s static method transpose directly:

Table<String, String, Integer> table2 = Tables.transpose(table); Set<Table.Cell<String, String, Integer>> cells = table2.cellSet(); cells.forEach(cell-> System.out.println(cell.getRowKey()+","+cell.getColumnKey()+":"+cell.getValue()));Copy the code

The cellSet method is used to get all the rows, print the result, and see that the row and column are swapped:

Jan,Hydra:20Feb,Hydra:28Jan,Trunks:28Feb,Trunks:16
Copy the code

4. Convert to a nested Map

Remember the format we used to store data in before using Table. If you want to restore data to the form of a nested Map, you can use the rowMap or columnMap methods of Table:

Map<String, Map<String, Integer>> rowMap = table.rowMap(); Map<String, Map<String, Integer>> columnMap = table.columnMap();Copy the code

Look at the transformed Map, summarized by row and column:

{Hydra={Jan=20, Feb=28}, Trunks={Jan=28, Feb=16}}{Jan={Hydra=20, Trunks=28}, Feb={Hydra=28, Trunks=16}}
Copy the code

BiMap – Bidirectional Map

In a normal Map, there is no easy way to find a key based on a value, either using a for loop or an iterator. For example, loop keySet:

public List<String> findKey(Map<String, String> map, String val){ List<String> keys=new ArrayList<>(); for (String key : map.keySet()) { if (map.get(key).equals(val)) keys.add(key); } return keys; }Copy the code

BiMap in Guava provides a two-way data structure for key and value associations. Let’s start with a simple example:

HashBiMap<String, String> biMap = HashBiMap.create(); biMap.put("Hydra","Programmer"); biMap.put("Tony","IronMan"); biMap.put("Thanos","Titan"); // Get valuesystem.out. println(bimap.get ("Tony")); BiMap<String, String> inverse = biMap.inverse(); // Get keySystem.out.println(inverse.get("Titan")) with value;Copy the code

Execution result, :

IronManThanos
Copy the code

Looks practical, doesn’t it? But there are still a few pits to avoid in the use of the following comb.

1. Influence of operation after inversion

Above, we invert the original BiMap’s key-value map using inverse method, but the inverted BiMap is not a new object, it implements a view association, so all operations performed on the inverted BiMap will apply to the original BiMap.

HashBiMap<String, String> biMap = HashBiMap.create(); biMap.put("Hydra","Programmer"); biMap.put("Tony","IronMan"); biMap.put("Thanos","Titan"); BiMap<String, String> inverse = biMap.inverse(); inverse.put("IronMan","Stark"); System.out.println(biMap);Copy the code

After making changes to the contents of the reversed BiMap, look at the contents of the original BiMap:

{Hydra=Programmer, Thanos=Titan, Stark=IronMan}
Copy the code

As you can see, the original IronMan key was Tony, although it has not been changed directly, but now the key is Stark.

2. Value cannot be repeated

The bottom layer of BiMap inherits Map. As we know, key is not allowed to repeat in Map, while key and value can be considered to be equivalent in BiMap. Therefore, restrictions are added on this basis, and value is not allowed to repeat. Take a look at this code:

HashBiMap<String, String> biMap = HashBiMap.create(); biMap.put("Tony","IronMan"); biMap.put("Stark","IronMan");Copy the code

Instead of terminating normally, the code will throw an IllegalArgumentException:

If you do not want to map the new key to an existing value, you can also use the forcePut method to force the replacement of the existing key:

HashBiMap<String, String> biMap = HashBiMap.create(); biMap.put("Tony","IronMan"); biMap.forcePut("Stark","IronMan");Copy the code

Print the replaced BiMap:

{Stark=IronMan}
Copy the code

By the way, since BiMap’s values are not allowed to be duplicated, its values method returns an unduplicated Set instead of a normal Collection:

Set<String> values = biMap.values();
Copy the code

Multimap – Multi-valued Map

A Map in Java maintains a one-to-one relationship between keys and values. If you want to Map a key to multiple values, you can only set the contents of the values as a set. Simple implementation is as follows:

Map<String, List<Integer>> map=new HashMap<>(); List<Integer> list=new ArrayList<>(); list.add(1); list.add(2); map.put("day",list);Copy the code

The Multimap in Guava provides a form of mapping a key to multiple values. Instead of defining a complex inner set, it can be used just like a normal Map, defining and putting data as follows:

Multimap<String, Integer> multimap = ArrayListMultimap.create(); multimap.put("day",1); multimap.put("day",2); multimap.put("day",8); multimap.put("month",3);Copy the code

Print the contents of this Multimap, and you can see that each key corresponds to a set:

{month=[3], day=[1, 2, 8]}
Copy the code

1. Get a collection of values

In the above operation, the get(key) method of the created plain Multimap returns a Collection of type Collection:

Collection<Integer> day = multimap.get("day");
Copy the code

If the type is specified at creation time as ArrayListMultimap, then the get method returns a List:

ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create(); List<Integer> day = multimap.get("day");Copy the code

Similarly, you can create types of multimaps such as HashMultimap and TreeMultimap.

The Multimap get method returns a non-null collection, but the contents of the collection may be empty, as shown in the following example:

List<Integer> day = multimap.get("day"); List<Integer> year = multimap.get("year"); System.out.println(day); System.out.println(year);Copy the code

Print result:

[1, 2, 8] []Copy the code

2. Set after operation get

Similar to BiMap, the collection returned by get is not a separate object, and can be understood as an association of the collection view. Operations on the new collection still apply to the original Multimap, as in the following example:

ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create(); multimap.put("day",1); multimap.put("day",2); multimap.put("day",8); multimap.put("month",3); List<Integer> day = multimap.get("day"); List<Integer> month = multimap.get("month"); day.remove(0); // This 0 is the subscript month.add(12); System.out.println(multimap);Copy the code

View the modified result:

{month=[3, 12], day=[2, 8]}
Copy the code

3. Convert to Map

Using the asMap method, you can convert a Multimap to a Map

. This Map can also be viewed as an associated view, and operations on this Map will apply to the original Multimap.
,collection>

Map<String, Collection<Integer>> map = multimap.asMap(); for (String key : map.keySet()) { System.out.println(key+" : "+map.get(key)); }map.get("day").add(20); System.out.println(multimap);Copy the code

Execution Result:

month : [3]day : [1, 2, 8]{month=[3], day=[1, 2, 8, 20]}
Copy the code

4. Quantity problem

The numbers in Multimap can also be confusing, as shown in the following example:

System.out.println(multimap.size()); System.out.println(multimap.entries().size()); for (Map.Entry<String, Integer> entry : multimap.entries()) { System.out.println(entry.getKey()+","+entry.getValue()); }Copy the code

Print result:

44month,3day,1day,2day,8
Copy the code

This is because the size() method returns a mapping of all keys to a single value, so the result is 4, and so does the entries() method, which returns a collection of key-value pairs for keys and a single value. But its keySet holds a different number of keys, for example, the following line of code would print 2.

System.out.println(multimap.keySet().size());
Copy the code

See what happens when you convert it to Map:

Set<Map.Entry<String, Collection<Integer>>> entries = multimap.asMap().entrySet(); System.out.println(entries.size());Copy the code

The code runs with a result of 2, because it gets the mapping from the key to the Collection.

RangeMap – RangeMap

Let’s start with an example. If we were to sort test scores by score, we would get this ugly if-else in our code:

public static String getRank(int score){ if (0<=score && score<60) return "fail"; else if (60<=score && score<=90) return "satisfactory"; else if (90<score && score<=100) return "excellent"; return null; }Copy the code

RangeMap in Guava describes a mapping from an interval to a specific value, allowing us to write code in a more elegant way. Let’s modify the above code with RangeMap and test it:

RangeMap<Integer, String> rangeMap = TreeRangeMap.create(); RangeMap. Put (Range. ClosedOpen (0 '), "fail"); RangeMap. Put (Range. Closed (60 living), "satisfactory"); RangeMap. Put (Range. OpenClosed (90100), "excellent"); System.out.println(rangeMap.get(59)); System.out.println(rangeMap.get(60)); System.out.println(rangeMap.get(90)); System.out.println(rangeMap.get(91));Copy the code

In the above code, the left closed and right open interval for [0,60), the closed interval for [60,90], and the closed interval for (90,100) are successively created and mapped to a certain value. Run result print:

failsatisfactorysatisfactoryexcellent
Copy the code

We can also remove a segment of space, and the following code removes the closed segment [70,80] and returns null:

RangeMap. Remove (Range. Closed (70)); System.out.println(rangeMap.get(75));Copy the code

ClassToInstanceMap – InstanceMap

ClassToInstanceMap is a special Map whose key is Class and whose value is the instance object of that Class. Let’s look at a simple example of using the putInstance method to store objects:

ClassToInstanceMap<Object> instanceMap = MutableClassToInstanceMap.create(); User user=new User("Hydra",18); Dept dept=new Dept("develop",200); instanceMap.putInstance(User.class,user); instanceMap.putInstance(Dept.class,dept);Copy the code

Use the getInstance method to fetch the object:

User user1 = instanceMap.getInstance(User.class); System.out.println(user==user1);Copy the code

The result prints true, indicating that it is indeed the object we created and put in earlier.

You might wonder if you could use a normal Map

to save only objects:
,object>

Map<Class,Object> map=new HashMap<>(); User user=new User("Hydra",18); Dept dept=new Dept("develop",200); map.put(User.class,user); map.put(Dept.class,dept);Copy the code

So what are the benefits of using ClassToInstanceMap?

First, the most obvious thing here is that complex casts are eliminated when fetching objects, avoiding the error of manual casts. Second, we can look at the definition of the ClassToInstanceMap interface, which is generic:

public interface ClassToInstanceMap<B> extends Map<Class<? extends B>, B>{... }Copy the code

This generic type can also be used to constrain the type of a key. Value must match the type of key.

ClassToInstanceMap<Map> instanceMap = MutableClassToInstanceMap.create(); HashMap<String, Object> hashMap = new HashMap<>(); TreeMap<String, Object> treeMap = new TreeMap<>(); ArrayList<Object> list = new ArrayList<>(); instanceMap.putInstance(HashMap.class,hashMap); instanceMap.putInstance(TreeMap.class,treeMap);Copy the code

This works fine because both HashMap and TreeMap integrate the Map parent class, but an error will be reported if you try to insert other types:

So, if you want to cache objects without doing complex type validation, you can use the convenient ClassToInstanceMap.

conclusion

This article introduces five guava data structures that extend maps, which provide very useful functionality and greatly simplify our code. But at the same time, there are also many pits to avoid, such as modifying the associated view will affect the original data and so on, we need to be careful in the specific use