Guava中的增强Map - Table、BiMap、Multimap、RangeMap、ClassToInstanceMap

发布时间 2023-11-02 20:26:15作者: C3Stones

1. 简介

  日常开发中使用Map时经常会遇到很多复杂的处理场景,例如:多个键的Map、不仅可以根据键获取值也可以根据值获取键且不用遍历、重复键的Map、数字等范围内映射相同的值、内存中缓存对象等,Guava提供了以上场景的解决方案。

场景 解决方案 具体实现
多个键的Map Table HashBasedTable、TreeBasedTable、ImmutableTable
不仅可以根据键获取值也可以根据值获取键且不用遍历 BiMap HashBiMap、ImmutableBiMap
重复键的Map Multimap ArrayListMultimap、LinkedListMultimap、LinkedHashMultimap、ImmutableListMultimap、ImmutableSetMultimap
数字等范围内映射相同的值 RangeMap TreeRangeMap、ImmutableRangeMap
内存中缓存对象 ClassToInstanceMap MutableClassToInstanceMap、ImmutableClassToInstanceMap

  本博客将详细描述具体的示例代码。

2. 添加依赖

  Maven项目pom.xml中添加依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.0.0-jre</version>
</dependency>

3. Tbale - 表结构数据

  官方注释翻译:将一对有序键(称为行键和列键)与单个值相关联的集合。
  示例代码(需求: 记录各个公司每个部门的人数):

// HashMap
Map<String, Integer> deptMap = new HashMap<>();
deptMap.put("A部门", 10);
deptMap.put("B部门", 20);
Map<String, Map<String, Integer>> companyMap = new HashMap<>();
companyMap.put("xx公司", deptMap);
// HashMap 获取值
Integer val = companyMap.get("xx公司").get("A部门");
System.out.println("HashMap 获取值: " + val);

// 创建Hash类型Table, 基于Hash表实现
// Table<R, C, V>中三个泛型: R-行, C-列, V-值
Table<String, String, Integer> hashTable = HashBasedTable.create();
hashTable.put("xx公司", "A部门", 10);
hashTable.put("xx公司", "B部门", 20);
hashTable.put("xx公司", "C部门", 30);

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

// 创建Tree类型Table, 基于红黑树实现
Table<String, String, Integer> treeTable = TreeBasedTable.create();
treeTable.put("xx公司", "C部门", 30);
treeTable.put("xx公司", "B部门", 20);
treeTable.put("xx公司", "A部门", 10);

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

// 创建不可变Table, 无法新增、更新或删除
Table<String, String, Integer> immutableTable = ImmutableTable.<String, String, Integer>builder()
        .put("xx公司", "C部门", 30)
        .put("xx公司", "B部门", 20)
        .put("xx公司", "A部门", 10)
        .build();

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

// Table 获取值
Integer val2 = hashTable.get("xx公司", "A部门");
System.out.println("\nTable 获取值: " + val2);

// Table 删除值
Integer remove = hashTable.remove("xx公司", "C部门");
System.out.println("\nTable 删除值: " + remove);

// 根据行获取列和值映射
Map<String, Integer> columnvalueMap = hashTable.row("xx公司");
System.out.println("\nTable 列和值 映射: " + columnvalueMap);

// 根据列获取行和值映射
Map<String, Integer> rowvalueMap = hashTable.column("A部门");
System.out.println("\nTable 行和值 映射: " + rowvalueMap);

// 获取key集合
Set<String> rowKeySet = hashTable.rowKeySet();
System.out.println("\nTable Row key 集合: " + rowKeySet);
Set<String> columnKeySet = hashTable.columnKeySet();
System.out.println("\nTable Column key 集合: " + columnKeySet);

// 获取值集合
Collection<Integer> values = hashTable.values();
System.out.println("\nTable 值集合: " + values);

// 判断包含行
boolean containsRow = hashTable.containsRow("xx公司");
System.out.println("\nTable 包含行: " + containsRow);

// 判断包含列
boolean containsColumn = hashTable.containsColumn("A部门");
System.out.println("\nTable 包含列: " + containsColumn);

// 判断包含行和列
boolean contains = hashTable.contains("xx公司", "A部门");
System.out.println("\nTable 包含行和列: " + contains);

// 判断包含值
boolean containsValue = hashTable.containsValue(10);
System.out.println("\nTable 包含值: " + containsValue);

// 行和列转置 - 行 转 列
Table<String, String, Integer> transposeTable = Tables.transpose(hashTable);

// 获取所有的行
Set<Table.Cell<String, String, Integer>> cells = transposeTable.cellSet();

// 遍历输出
System.out.println("\n遍历输出开始----------------------------");
cells.forEach(cell -> System.out.println(cell.getRowKey() + ", " + cell.getColumnKey() + ", " + cell.getValue()));
System.out.println("\n遍历输出结束----------------------------");

// 转换为嵌套的Map
Map<String, Map<String, Integer>> rowMap = hashTable.rowMap();
System.out.println("\nTable RowMap: " + rowMap);
Map<String, Map<String, Integer>> columnMap = hashTable.columnMap();
System.out.println("\nTable ColumnMap: " + columnMap);

  执行结果:

HashMap 获取值: 10

Hash Table: {xx公司={A部门=10, B部门=20, C部门=30}}

Tree Table: {xx公司={A部门=10, B部门=20, C部门=30}}

Immutable Table: {xx公司={C部门=30, B部门=20, A部门=10}}

Table 获取值: 10

Table 删除值: 30

Table 列和值 映射: {A部门=10, B部门=20}

Table 行和值 映射: {xx公司=10}

Table Row key 集合: [xx公司]

Table Column key 集合: [A部门, B部门]

Table 值集合: [10, 20]

Table 包含行: true

Table 包含列: true

Table 包含行和列: true

Table 包含值: true

遍历输出开始----------------------------
A部门, xx公司, 10
B部门, xx公司, 20

遍历输出结束----------------------------

Table RowMap: {xx公司={A部门=10, B部门=20}}

Table ColumnMap: {A部门={xx公司=10}, B部门={xx公司=20}}

4. BiMap - 双向映射Map

  官方注释翻译:双映射(或“双向映射”)是一种保留其值及其键的唯一性的映射。此约束使双映射能够支持“反向视图”,即另一个双映射,其中包含与此双映射相同的条目,但具有相反的键和值。
  示例代码(需求: 数组和英文翻译):

// 创建BiMap, 底层为两个Hash表的Map
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);

// 创建不可变BiMap, 无法新增、更新或删除
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("\nImmutable BiMap: " + immutableBiMap);

// 通过key获取value
String value = biMap.get(1);
System.out.println("\nBiMap 根据key获取value: " + value);

Integer key = biMap.inverse().get("one");
System.out.println("\nBiMap 根据value获取key: " + key);

// 翻转后修改
biMap.inverse().put("six", 6);
// 返回双映射的逆视图, 并没有创建新对象, 还是之前的对象, 所以操作翻转后的BiMap会影响之前的BiMap
System.out.println("\nBiMap 被影响: " + biMap);

// 底层是HashMap, key不可重复
// value不可重复
try {
    biMap.put(11, "one");
} catch (Exception e) {
    System.err.println("BiMap 替换value异常: " + e.getMessage());
}

// 翻转后key不能重复
try {
    biMap.inverse().put("first", 1);
} catch (Exception e) {
    System.err.println("BiMap 替换key异常: " + e.getMessage());
}

// key和value可为null
biMap.put(null, null);
System.out.println("\nBiMap 根据Null key获取Null value: " + biMap.get(null));
System.out.println("\nBiMap 根据Null value获取Null key: " + biMap.inverse().get(null));

// 强制替换key
biMap.forcePut(11, "one");
System.out.println("\nBiMap 获取新key: " + biMap.inverse().get("one"));

// values为Set集合
Set<String> values = biMap.values();
System.out.println("\nBiMap 不重复的value: " + values);

  执行结果:

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

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

BiMap 根据key获取value: one

BiMap 根据value获取key: 1

BiMap 被影响: {1=one, 2=two, 3=three, 4=four, 5=five, 6=six}
BiMap 替换value异常: value already present: one
BiMap 替换key异常: key already present: 1

BiMap 根据Null key获取Null value: null

BiMap 根据Null value获取Null key: null

BiMap 获取新key: 11

BiMap 不重复的value: [two, three, four, five, six, null, one]

5. Multimap - 多重映射Map

  官方注释翻译将键映射到值的集合,类似于 Map,但其中每个键可能与 多个 值相关联。
  示例代码(需求: 学生和各科选修课成绩):

// 创建Multimap, key为HashMap, value为ArrayList
Multimap<String, Integer> arrayListMultimap = ArrayListMultimap.create();
arrayListMultimap.put("张三", 90);
arrayListMultimap.put("张三", 80);
arrayListMultimap.put("张三", 100);
arrayListMultimap.put("李四", 88);

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

// 创建Multimap, key为HashMap, value为HashSet
Multimap<String, Integer> hashMultimap = HashMultimap.create();
hashMultimap.put("张三", 90);
hashMultimap.put("张三", 80);
hashMultimap.put("张三", 100);
hashMultimap.put("李四", 88);

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

// 创建Multimap, key为LinkedHashMap, value为LinkedList
Multimap<String, Integer> linkedListMultimap = LinkedListMultimap.create();
linkedListMultimap.put("张三", 90);
linkedListMultimap.put("张三", 80);
linkedListMultimap.put("张三", 100);
linkedListMultimap.put("李四", 88);

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

// 创建Multimap, key为LinkedHashMap, value为LinkedHashMap
Multimap<String, Integer> linkedHashMultimap = LinkedHashMultimap.create();
linkedHashMultimap.put("张三", 90);
linkedHashMultimap.put("张三", 80);
linkedHashMultimap.put("张三", 100);
linkedHashMultimap.put("李四", 88);

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

// 创建Multimap, key为TreeMap, value为TreeSet
Multimap<String, Integer> treeMultimap = TreeMultimap.create();
treeMultimap.put("张三", 90);
treeMultimap.put("张三", 80);
treeMultimap.put("张三", 100);
treeMultimap.put("李四", 88);

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

// 创建不可变Multimap, 无法新增、更新或删除, key为ImmutableMap, value为ImmutableList
Multimap<String, Integer> immutableListMultimap = ImmutableListMultimap.<String, Integer>builder()
        .put("张三", 90)
        .put("张三", 80)
        .put("张三", 100)
        .put("李四", 88)
        .build();

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

// 创建不可变Multimap, 无法新增、更新或删除, key为ImmutableMap, value为ImmutableSet
Multimap<String, Integer> immutableSetMultimap = ImmutableSetMultimap.<String, Integer>builder()
        .put("张三", 90)
        .put("张三", 80)
        .put("张三", 100)
        .put("李四", 88)
        .build();

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

// 获取值
Collection<Integer> values = arrayListMultimap.get("张三");
System.out.println("\nMultimap 获取值集合: " + values);

// 获取不存在key的值, 返回的是空集合, 而不是null
Collection<Integer> valuesByNotExistsKey = arrayListMultimap.get("王五");
System.out.println("\nMultimap 获取不存在的Key值集合: " + valuesByNotExistsKey);

// 获取值集合添加值
// 返回的是多重映射中关联的值的视图集合, 并没有创建新对象, 还是之前的对象, 所以操作值集合会影响之前的Multimap
values.add(60);
System.out.println("\nMultimap 被影响: " + arrayListMultimap);

// 获取大小
System.out.println("\nMultimap 大小:" + arrayListMultimap.size());

// 判断是否为空
System.out.println("\nMultimap 是否为空: " + arrayListMultimap.isEmpty());

// 包含key
System.out.println("\nMultimap 包含key: " + arrayListMultimap.containsKey("张三"));

// 包含value
System.out.println("\nMultimap 包含value: " + arrayListMultimap.containsValue(60));

// 包含key-value键值对
System.out.println("\nMultimap 包含key-value对: " + arrayListMultimap.containsEntry("张三", 60));

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

// 根据key-value删除
arrayListMultimap.remove("张三", 10);
System.out.println("\nMultimap 根据key-value删除: " + arrayListMultimap);

// 根据key删除
Collection<Integer> removeAll = arrayListMultimap.removeAll("张三");
System.out.println("\nMultimap 根据key删除: " + removeAll);

// 获取key集合
Set<String> keySet = arrayListMultimap.keySet();
System.out.println("\nMultimap 获取key集合(HashSet): " + keySet);
Multiset<String> keys = arrayListMultimap.keys();
System.out.println("\nMultimap 获取key集合(MultiSet): " + keys);

// 获取所有的key-value
Collection<Map.Entry<String, Integer>> entries = arrayListMultimap.entries();
System.out.println("\n遍历key-value开始--------------------------");
entries.forEach(entry -> System.out.println(entry.getKey() + " : " + entry.getValue()));
System.out.println("\n遍历key-value结束--------------------------");

// 转换为Map<K, Collection<V>>
Map<String, Collection<Integer>> collectionMap = arrayListMultimap.asMap();
System.out.println("\nMultimap 转换为Map<K, Collection<V>>: " + collectionMap);

  执行结果:

Multimap key为HashMap, value为ArrayList: {李四=[88], 张三=[90, 80, 100]}

Multimap key为HashMap, value为HashSet: {李四=[88], 张三=[80, 100, 90]}

Multimap key为LinkedHashMap, value为LinkedList: {张三=[90, 80, 100], 李四=[88]}

Multimap key为LinkedHashMap, value为LinkedHashMap: {张三=[90, 80, 100], 李四=[88]}

Multimap key为TreeMap, value为TreeSet: {张三=[80, 90, 100], 李四=[88]}

Multimap key为ImmutableMap, value为ImmutableList: {张三=[90, 80, 100], 李四=[88]}

Multimap key为ImmutableMap, value为ImmutableSet: {张三=[90, 80, 100], 李四=[88]}

Multimap 获取值集合: [90, 80, 100]

Multimap 获取不存在的Key值集合: []

Multimap 被影响: {李四=[88], 张三=[90, 80, 100, 60]}

Multimap 大小:5

Multimap 是否为空: false

Multimap 包含key: true

Multimap 包含value: true

Multimap 包含key-value对: true

Multimap 替换value: {李四=[88], 张三=[10, 20, 30]}

Multimap 根据key-value删除: {李四=[88], 张三=[20, 30]}

Multimap 根据key删除: [20, 30]

Multimap 获取key集合(HashSet): [李四]

Multimap 获取key集合(MultiSet): [李四]

遍历key-value开始--------------------------
李四 : 88

遍历key-value结束--------------------------

Multimap 转换为Map<K, Collection<V>>: {李四=[88]}

6. RangeMap - 范围映射Map

  官方注释翻译:从不相交的非空范围到非 null 值的映射。查询查找与包含指定键的范围(如果有)关联的值。
  示例代码(需求:考试成绩分类):

// if-else
int score = 88;
String rank;
if (0 <= score && score < 60) {
    rank = "不及格";
} else if (60 <= score && score <= 84) {
    rank = "及格";
} else if (84 < score && score <= 100) {
    rank = "优秀";
} else {
    rank = "无效";
}

System.out.println("if-else 获取值: " + rank);

// 创建RangeMap, 基于TreeMap(红黑树)实现
RangeMap<Integer, String> treeRangeMap = TreeRangeMap.create();
treeRangeMap.put(Range.closedOpen(0, 60), "不及格");
treeRangeMap.put(Range.closed(60, 84), "及格");
treeRangeMap.put(Range.openClosed(84, 100), "优秀");
treeRangeMap.put(Range.lessThan(0), "无效");
treeRangeMap.put(Range.greaterThan(100), "无效");

rank = treeRangeMap.get(score);
System.out.println("\nRangeMap 获取值: " + rank);

// 创建不可变RangeMap, 无法新增、更新或删除
ImmutableRangeMap<Integer, String> immutableRangeMap = ImmutableRangeMap.<Integer, String>builder()
        .put(Range.closedOpen(0, 60), "不及格")
        .put(Range.closed(60, 84), "及格")
        .put(Range.openClosed(84, 100), "优秀")
        .put(Range.lessThan(0), "无效")
        .put(Range.greaterThan(100), "无效")
        .build();

rank = immutableRangeMap.get(score);
System.out.println("\nImmutableRangeMap 获取值: " + rank);

// 获取key-value对
Map.Entry<Range<Integer>, String> entry = treeRangeMap.getEntry(88);
System.out.println("\nRangeMap 获取key-value对: " + entry.getKey() + " : " + entry.getValue());

// 返回不可变的升序的Map
Map<Range<Integer>, String> asMapOfRanges = treeRangeMap.asMapOfRanges();
System.out.println("\nRangeMap 不可变的升序的Map: " + asMapOfRanges);

// 返回不可变的降序的Map
Map<Range<Integer>, String> asDescendingMapOfRanges = treeRangeMap.asDescendingMapOfRanges();
System.out.println("\nRangeMap 不可变的降序的Map: " + asDescendingMapOfRanges);

// 相连范围合并
RangeMap<Integer, String> treeRangeMap2 = TreeRangeMap.create();
treeRangeMap2.putCoalescing(Range.closedOpen(0, 60), "不及格");
treeRangeMap2.putCoalescing(Range.closed(60, 84), "及格");
treeRangeMap2.putCoalescing(Range.openClosed(84, 100), "及格"); // 或者 [60..84]范围合并
treeRangeMap2.putCoalescing(Range.lessThan(0), "无效");
treeRangeMap2.putCoalescing(Range.greaterThan(100), "无效");
System.out.println("\nRangeMap 不合并相连范围: " + treeRangeMap.asMapOfRanges());
System.out.println("RangeMap 合并相连范围: " + treeRangeMap2.asMapOfRanges());

// 最小范围
Range<Integer> span = treeRangeMap.span();
System.out.println("\nRangeMap 最小范围: " + span);

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

// 合并范围
treeRangeMap.merge(Range.closed(60, 100), "及格", (s, s2) -> s2);
System.out.println("\nRangeMap 合并Map: " + treeRangeMap);

// 移除范围
treeRangeMap.remove(Range.open(90, 95));
System.out.println("\nRangeMap 移除范围: " + treeRangeMap);

// 清除所有范围
treeRangeMap.clear();
System.out.println("\nRangeMap 清除所有范围: " + treeRangeMap);

  执行结果:

if-else 获取值: 优秀

RangeMap 获取值: 优秀

ImmutableRangeMap 获取值: 优秀

RangeMap 获取key-value对: (84..100] : 优秀

RangeMap 不可变的升序的Map: {(-∞..0)=无效, [0..60)=不及格, [60..84]=及格, (84..100]=优秀, (100..+∞)=无效}

RangeMap 不可变的降序的Map: {(100..+∞)=无效, (84..100]=优秀, [60..84]=及格, [0..60)=不及格, (-∞..0)=无效}

RangeMap 不合并相连范围: {(-∞..0)=无效, [0..60)=不及格, [60..84]=及格, (84..100]=优秀, (100..+∞)=无效}
RangeMap 合并相连范围: {(-∞..0)=无效, [0..60)=不及格, [60..100]=及格, (100..+∞)=无效}

RangeMap 最小范围: (-∞..+∞)

RangeMap 子范围Map: {[70..84]=及格, (84..90]=优秀}

RangeMap 合并Map: [(-∞..0)=无效, [0..60)=不及格, [60..84]=及格, (84..100]=及格, (100..+∞)=无效]

RangeMap 移除范围: [(-∞..0)=无效, [0..60)=不及格, [60..84]=及格, (84..90]=及格, [95..100]=及格, (100..+∞)=无效]

RangeMap 清除所有范围: []

7. ClassToInstanceMap - 类型映射到实例Map

  官方注释翻译:映射,其每个条目将一个 Java 原始类型 映射到该类型的实例。除了实现 Map之外,还提供额外的类型安全操作 putInstance 和 getInstance 。与任何其他 Map<Class, Object>映射一样,此映射可能包含基元类型的条目,并且基元类型及其相应的包装器类型可以映射到不同的值。
  示例代码(需求:缓存Bean(不交给Spring管理,自己管理Bean)):

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 + '\'' + '}';
    }
}
// 创建Bean
UserBean userBean = new UserBean(1, "张三");

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

// 获取值,需要强转
UserBean value = (UserBean) hashMap.get(UserBean.class);
System.out.println("HashMap 获取对象实例: " + value);
System.out.println("HashMap 获取对象实例等于创建的Bean: " + (value == userBean));

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

// 获取值,无需强转
UserBean value2 = classToInstanceMap.getInstance(UserBean.class);
System.out.println("\nClassToInstanceMap 获取对象实例: " + value2);
System.out.println("ClassToInstanceMap 获取对象实例等于创建的Bean: " + (value2 == userBean));

// 创建不可变ClassToInstanceMap, 无法新增、更新或删除
ClassToInstanceMap<UserBean> immutableClassToInstanceMap = ImmutableClassToInstanceMap.<UserBean>builder()
        .put(UserBean.class, userBean)
        .build();

// 获取值,无需强转
UserBean value3 = immutableClassToInstanceMap.getInstance(UserBean.class);
System.out.println("\nImmutableClassToInstanceMap 获取对象实例: " + value3);
System.out.println("ImmutableClassToInstanceMap 获取对象实例等于创建的Bean: " + (value3 == userBean));


// 限制类型,避免使用HashMap存储对象时,因为使用Object值类型而在添加缓存时需要今天类型校验
ClassToInstanceMap<Collection> classToInstanceMap1 = MutableClassToInstanceMap.create();
classToInstanceMap1.put(ArrayList.class, new ArrayList());
classToInstanceMap1.put(HashSet.class, new HashSet());
// 编译保存: 'put(java.lang.Class<? extends java.util.@org.checkerframework.checker.nullness.qual.NonNull 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());

  执行结果:

HashMap 获取对象实例: UserBean{id=1, username='张三'}
HashMap 获取对象实例等于创建的Bean: true

ClassToInstanceMap 获取对象实例: UserBean{id=1, username='张三'}
ClassToInstanceMap 获取对象实例等于创建的Bean: true

ImmutableClassToInstanceMap 获取对象实例: UserBean{id=1, username='张三'}
ImmutableClassToInstanceMap 获取对象实例等于创建的Bean: true