您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

跟着 Guava 学 Java 之 集合工具类

时间:2022-08-24 10:56:30  来源:  作者:小盒子的技术分享

背景

先来回顾一下 JDK 的 Collections , JAVA.util.Collections 提供了一系列静态方法,能更方便地操作各种集合。

比如:

  • 创建空集合 Collections.emptyList();
  • 创建单元素集合 Collections.singletonList("Apple");
  • 排序 Collections.sort(list);
  • 创建不可变集合 Collections.unmodifiableList(mutable);
  • 创建线程安全集合 Collections.synchronizedList(list);
  • ......

Guava 沿着 Collections 的思路 提供了 更多的工具方法,适用于所有集合的静态方法,使之成为更强大的集合工具类。

Guava 提供的集合工具不止是对 Collections 的扩展和增强,还包括了其他集合类型的工具类,我们把工具类与特定集合接口的对应关系归纳如下:

Interface JDK or Guava? 对应 Guava 工具类 Collection JDK Collections2 List JDK Lists Set JDK Sets SortedSet JDK Sets Map JDK Maps SortedMap JDK Maps Queue JDK Queues Multiset Guava Multisets Multimap Guava Multimaps BiMap Guava Maps Table Guava Tables

静态构造器

在 JDK 7 之前,构造新的范型集合时要讨厌地重复声明范型:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();

JDK 7 以后因为有了钻石操作符(Diamond Operator)可以自动推断参数类型,所以省点儿事儿

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();

用 Guava 可以这样写:

List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList();

你可能觉得:这没什么牛的呀,跟 JDK7 以后没啥区别呀,人家还是原生的。

是的,没错,尤其是你用 JDK 9 以后的版本,JDK 从功能上跟 Guava 就基本一样了,举个例子,比如带元素初始化:

List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");

上面这行是利用了 Guava 的 Lists ,JDK 7 没有比这行代码更好的方法,但 JDK9 人家有,比如:

List<String> theseElements2 = List.of("alpha", "beta", "gamma");

所以我们说,跟着 Guava 学 Java,随着版本的迭代,你觉得哪个好用,哪个适合你用哪个,我的重要是把这里面的知识点讲清楚。

我们再来看个例子:创建集合时指定初始化集合大小:

List<Type> exactly100 = Lists.newArrayListWithCapacity(100);

你可能说,哥们,这 JDK 有啊,这不多此一举吗?

ArrayList<Object> objects = new ArrayList<>(10);

是的,作用一样,但 Guava 的做法,可以利用工厂方法名称,提高代码的可读性,你不觉得虽然名字长了,但可读性比 JDK 那个好很多吗?代码不止是写给机器的,也是写给人看的,不能指望团队里所有人都是强者。代码可读性越高,越健壮越容易维护。

Iterables

Iterables 类包含了一系列的静态方法,来操作或返回 Iterable 对象

看几个常用的方法:

方法 描述 concat(Iterable) 串联多个 iterables 的懒视图 frequency(Iterable, Object) 返回对象在 iterable 中出现的次数 partition(Iterable, int) 把 iterable 按指定大小分割,得到的子集都不能进行修改操作 getFirst(Iterable, T default) 返回 iterable 的第一个元素,若 iterable 为空则返回默认值 getLast(Iterable) 返回 iterable 的最后一个元素,若 iterable 为空则抛出 NoSuchElementException elementsEqual(Iterable, Iterable) 如果两个 iterable 中的所有元素相等且顺序一致,返回 true unmodifiableIterable(Iterable) 返回 iterable 的不可变视图 limit(Iterable, int) 最多返回指定数量的元素 getOnlyElement(Iterable) 获取 iterable 中唯一的元素,如果 iterable 为空或有多个元素,则快速失败

对于上面这些常用的方法,可能你觉得使用 JDK8 以后的 Stream 一行也都搞定了,是的,还是那句话,Guava 是个工具,尤其在 JDK8 之前用来增强 API 很好用,但工具不止一个,Java 也在发展,有些东西就变成可选项了,看你的需求和习惯使用。Guava 也有对应的流式风格的工具类,比如 FluentIterable

Lists

除了静态工厂方法和函数式编程方法,Lists为 List 类型的对象提供了若干工具方法。

方法 描述 partition(List, int) 把 List 按指定大小分割 reverse(List) 返回给定 List 的反转视图。注:如果 List 是不可变的,考虑改用ImmutableList.reverse()。

List countUp = Ints.asList(1, 2, 3, 4, 5);
List countDown = Lists.reverse(theList); // {5, 4, 3, 2, 1}
List<List> parts = Lists.partition(countUp, 2);//{{1,2}, {3,4}, {5}}

Sets

Sets工具类包含了若干好用的方法。

Sets 中 为我们提供了很多标准的集合运算(Set-Theoretic)方法。比如我们常用的集合的 “交、并、差集” 以及对称差集

 

交集

Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> intersection = Sets.intersection(primes,wordsWithPrimeLength);
// intersection 包含"two", "three", "seven"
return intersection.immutableCopy();//可以使用交集,但不可变拷贝的读取效率更高

并集

Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> union = Sets.union(primes,wordsWithPrimeLength);

// union 包含 [two, three, five, seven, one, six, eight]
return intersection.immutableCopy();

差集

Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> difference = Sets.union(primes,wordsWithPrimeLength);

// difference 包含 “five”
return difference.immutableCopy();

对称差集

Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> symmetricDifference = Sets.union(primes,wordsWithPrimeLength);

// symmetricDifference 包含 [five, one, six, eight]
return symmetricDifference.immutableCopy();

注意返回的都是 SetView ,它可以:

  • 直接当作 Set 使用,因为 SetView 也实现了 Set 接口
  • 用copyInto(Set)拷贝进另一个可变集合
  • 用immutableCopy()对自己做不可变拷贝

笛卡儿积

方法 描述 cartesianProduct(List<Set>) 返回所有集合的笛卡儿积 powerSet(Set) 返回给定集合的所有子集

Set<String> animals = ImmutableSet.of("gerbil", "hamster");
Set<String> fruits = ImmutableSet.of("apple", "orange", "banana");

Set<List<String>> product = Sets.cartesianProduct(animals, fruits);
// {{"gerbil", "apple"}, {"gerbil", "orange"}, {"gerbil", "banana"},
//  {"hamster", "apple"}, {"hamster", "orange"}, {"hamster", "banana"}}

Set<Set<String>> animalSets = Sets.powerSet(animals);
// {{}, {"gerbil"}, {"hamster"}, {"gerbil", "hamster"}}

Maps

Maps 有若干很酷的方法。

uniqueIndex

有一组对象,它们在某个属性上分别有独一无二的值,而我们希望能够按照这个属性值查找对象。

比方说,我们有一堆字符串,这些字符串的长度都是独一无二的,而我们希望能够按照特定长度查找字符串:

ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings,
    new Function<String, Integer> () {
        public Integer apply(String string) {
            return string.length();
        }
 });

你可以想到了,我们常见的场景还有 把一个 List 的对象集合转成 Map,map 的 key 通常是对象的 ID。用 uniqueIndex 方法可以这样写:

 Map<Integer, Animal> map = Maps.uniqueIndex(list, Animal::getId);

或者你用 Java8 的 Stream 也一样:

  ArrayList<Animal> animals = Lists.newArrayList(new Animal(1L, "Dog"), new Animal(2L, "Cat"));
  //下面两种写法都可以
  Map<Long, Animal> map = animals.stream().collect(Collectors.toMap(Animal::getId, Function.identity()));
  Map<Long, Animal> map = animals.stream().collect(Collectors.toMap(Animal::getId, animal -> animal));

注意:key 要是唯一的,否则会报错。

difference

找不同,对比两个 map,告诉你哪里不同

Maps.difference(Map, Map)用来比较两个 Map 以获取所有不同点。该方法返回 MapDifference 对象

下面是 MapDifference 的一些方法:

entriesInCommon() 两个 Map 中都有的映射项,包括匹配的键与值 entriesDiffering() 键相同但是值不同值映射项。返回的 Map 的值类型为MapDifference.ValueDifference,以表示左右两个不同的值 entriesOnlyOnLeft() 键只存在于左边 Map 的映射项 entriesOnlyOnRight() 键只存在于右边 Map 的映射项

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
Map<String, Integer> right = ImmutableMap.of("b", 2, "c", 4, "d", 5);
MapDifference<String, Integer> diff = Maps.difference(left, right);

diff.entriesInCommon(); // {"b" => 2}
diff.entriesDiffering(); // {"c" => (3, 4)}
diff.entriesOnlyOnLeft(); // {"a" => 1}
diff.entriesOnlyOnRight(); // {"d" => 5}

看到这个你能想到什么? 我举个场景:审计日志或操作日志,谁什么时间做了什么,数据从旧值变更为新值,这些要记录下来

是不是可以用上面这个 Maps 的方法? 适合不适合你自己决定,这里是提供个思路。

MultiSets

下面要介绍的工具类都是新集合类型的工具类,比如 MultiSet 和 MultiMap 之类的,有关这些 Guava 的新集合类型,在之前的文章 《跟着 Guava 学 Java 之 新集合类型》 都有介绍,有不清楚的可以再翻回去看一看。

标准的 Collection 操作会忽略 Multiset 重复元素的个数,而只关心元素是否存在于 Multiset 中,如 contAInsAll 方法。为此,Multisets提供了若干方法,以顾及 Multiset 元素的重复性:

方法 说明 **和 Collection **方法的区别 containsOccurrences(Multiset sup, Multiset sub) 对任意 o,如果 sub.count(o)<=super.count(o),返回 true Collection.containsAll 忽略个数,而只关心 sub 的元素是否都在 super 中 removeOccurrences(Multiset removeFrom, Multiset toRemove) 对 toRemove 中的重复元素,仅在 removeFrom 中删除相同个数。 Collection.removeAll 移除所有出现在 toRemove 的元素 retainOccurrences(Multiset removeFrom, Multiset toRetain) 修改 removeFrom,以保证任意 o 都符合 removeFrom.count(o)<=toRetain.count(o) Collection.retainAll 保留所有出现在 toRetain 的元素 intersection(Multiset, Multiset) 返回两个 multiset 的交集; 没有类似方法

举例来说:

Multiset<String> multiset1 = HashMultiset.create();
multiset1.add("a", 2);

Multiset<String> multiset2 = HashMultiset.create();
multiset2.add("a", 5);

multiset1.containsAll(multiset2); //返回 true;因为包含了所有不重复元素,
//虽然 multiset1 实际上包含 2 个"a",而 multiset2 包含 5 个"a"
Multisets.containsOccurrences(multiset1, multiset2); // returns false

multiset2.removeOccurrences(multiset1); // multiset2 现在包含 3 个"a"
multiset2.removeAll(multiset1);//multiset2 移除所有"a",虽然 multiset1 只有 2 个"a"
multiset2.isEmpty(); // returns true

Multisets 中的其他工具方法还包括:

copyHighestCountFirst(Multiset) 返回 Multiset 的不可变拷贝,并将元素按重复出现的次数做降序排列 unmodifiableMultiset(Multiset) 返回 Multiset 的只读视图 unmodifiableSortedMultiset(SortedMultiset) 返回 SortedMultiset 的只读视图

Multiset<String> multiset = HashMultiset.create();
multiset.add("a", 3);
multiset.add("b", 5);
multiset.add("c", 1);

ImmutableMultiset highestCountFirst = Multisets.copyHighestCountFirst(multiset);
//highestCountFirst,包括它的 entrySet 和 elementSet,按{"b", "a", "c"}排列元素

Multimaps

index

Multimaps 的 index 方法跟前面介绍的 Maps.uniqueIndex 方法是兄弟方法。与 uniqueIndex 方法相反,通常针对的场景是:有一组对象,它们有共同的特定属性,我们希望按照这个属性的值查询对象,但属性值不一定是独一无二的。比方说,我们想把字符串按长度分组:

ImmutableSet digits = ImmutableSet.of("zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine");
Function<String, Integer> lengthFunction = new Function<String, Integer>() {
    public Integer apply(String string) {
        return string.length();
    }
};

ImmutableListMultimap<Integer, String> digitsByLength= Multimaps.index(digits, lengthFunction);
/*
*  digitsByLength maps:
*  3 => {"one", "two", "six"}
*  4 => {"zero", "four", "five", "nine"}
*  5 => {"three", "seven", "eight"}
*/

invertFrom

Multimap 可以把多个键映射到同一个值,也可以把一个键映射到多个值,反转 Multimap 也会很有用。Guava 提供了invertFrom(Multimap toInvert, Multimap dest)做这个操作,并且你可以自由选择反转后的 Multimap 实现。

ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.putAll("b", Ints.asList(2, 4, 6));
multimap.putAll("a", Ints.asList(4, 2, 1));
multimap.putAll("c", Ints.asList(2, 5, 3));

TreeMultimap<Integer, String> inverse = Multimaps.invertFrom(multimap, TreeMultimap<String, Integer>.create());
//注意我们选择的实现,因为选了 TreeMultimap,得到的反转结果是有序的
/*
* inverse maps:
*  1 => {"a"}
*  2 => {"a", "b", "c"}
*  3 => {"c"}
*  4 => {"a", "b"}
*  5 => {"c"}
*  6 => {"b"}
*/

forMap

想在 Map 对象上使用 Multimap 的方法吗?forMap(Map)把 Map 包装成 SetMultimap。这个方法特别有用,例如,与 Multimaps.invertFrom 结合使用,可以把多对一的 Map 反转为一对多的 Multimap。

Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2);
SetMultimap<String, Integer> multimap = Multimaps.forMap(map);
// multimap:["a" => {1}, "b" => {1}, "c" => {2}]
Multimap<Integer, String> inverse = Multimaps.invertFrom(multimap, HashMultimap<Integer, String>.create());
// inverse:[1 => {"a","b"}, 2 => {"c"}] 

参考

  • https://www.baeldung.com/java-list-to-map
  • https://wizardforcel.gitbooks.io/guava-tutorial/content/11.html


Tags:Java   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Oracle正式发布Java 22
Oracle 正式发布 Java 22,这是备受欢迎的编程语言和开发平台推出的全新版本。Java 22 (Oracle JDK 22) 在性能、稳定性和安全性方面进行了数千种改进,包括对Java 语言、其API...【详细内容】
2024-03-21  Search: Java  点击:(10)  评论:(0)  加入收藏
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  Search: Java  点击:(15)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  Search: Java  点击:(78)  评论:(0)  加入收藏
如何提高 Java 代码的可重用性
译者 | 刘汪洋审校 | 重楼对于软件开发者而言,编写可重用的代码是一项基本而重要的技能。每位工程师都应掌握如何尽可能地提高代码的复用性。当前,一些开发人员可能会认为微服...【详细内容】
2024-01-03  Search: Java  点击:(65)  评论:(0)  加入收藏
Java 21 神仙特性:虚拟线程使用指南
虚拟线程是由 Java 21 版本中实现的一种轻量级线程。它由 JVM 进行创建以及管理。虚拟线程和传统线程(我们称之为平台线程)之间的主要区别在于,我们可以轻松地在一个 Java 程序...【详细内容】
2023-12-28  Search: Java  点击:(108)  评论:(0)  加入收藏
三分钟理解 Java 虚拟线程
虚拟线程是 Java 语言中实现的一种轻量级线程,在 Java 项目中可以减少编写、维护和调试高吞吐量并发应用程序的工作量。有关虚拟线程的背景介绍,大家可以参阅 JEP 444。https:...【详细内容】
2023-12-27  Search: Java  点击:(160)  评论:(0)  加入收藏
Java Lambda 表达式各种用法,你都会了吗
Lambda表达式是 Java 8 中引入的最有影响力的功能之一。它们通过允许简洁而优雅地创建匿名函数来实现 Java 中的函数式编程。在这篇博文中,我们将探讨编写 lambda 表达式的各...【详细内容】
2023-12-25  Search: Java  点击:(96)  评论:(0)  加入收藏
别再乱用了,Java 21 将弃用、删除这些功能!
尽管Java 是我使用过的向后兼容程度最高的语言和环境之一,但始终存在功能弃用甚至删除的可能性。Java 21 将弃用两个功能,这就是我们今天要讨论的内容。1 为什么要弃用功能?弃...【详细内容】
2023-12-25  Search: Java  点击:(140)  评论:(0)  加入收藏
java 一次性处理百万数据,用了它,内存再也不会溢出了
背景最近在用一个同事写的后台管理导出数据进行数据分析,然后发现前端一直卡起,后来到服务器上查询日志,发现内存溢出了。分析出来原因:原来是表数据量过大超过百万,然后导致查出...【详细内容】
2023-12-15  Search: Java  点击:(93)  评论:(0)  加入收藏
Java 异步编程本应更简单才对
在过去的好多年里,多线程和异步一直作为 Java 技术里的高级部分,在技术序列中,一个语言分为入门部分、进阶部分和高级部分,所以,异步是作为其中的高级技术部分存在的。关于异步和...【详细内容】
2023-12-12  Search: Java  点击:(190)  评论:(0)  加入收藏
▌简易百科推荐
Java 8 内存管理原理解析及内存故障排查实践
本文介绍Java8虚拟机的内存区域划分、内存垃圾回收工作原理解析、虚拟机内存分配配置,以及各垃圾收集器优缺点及场景应用、实践内存故障场景排查诊断,方便读者面临内存故障时...【详细内容】
2024-03-20  vivo互联网技术    Tags:Java 8   点击:(15)  评论:(0)  加入收藏
如何编写高性能的Java代码
作者 | 波哥审校 | 重楼在当今软件开发领域,编写高性能的Java代码是至关重要的。Java作为一种流行的编程语言,拥有强大的生态系统和丰富的工具链,但是要写出性能优异的Java代码...【详细内容】
2024-03-20    51CTO  Tags:Java代码   点击:(24)  评论:(0)  加入收藏
在Java应用程序中释放峰值性能:配置文件引导优化(PGO)概述
译者 | 李睿审校 | 重楼在Java开发领域,优化应用程序的性能是开发人员的持续追求。配置文件引导优化(Profile-Guided Optimization,PGO)是一种功能强大的技术,能够显著地提高Ja...【详细内容】
2024-03-18    51CTO  Tags:Java   点击:(26)  评论:(0)  加入收藏
Java生产环境下性能监控与调优详解
堆是 JVM 内存中最大的一块内存空间,该内存被所有线程共享,几乎所有对象和数组都被分配到了堆内存中。堆被划分为新生代和老年代,新生代又被进一步划分为 Eden 和 Survivor 区,...【详细内容】
2024-02-04  大雷家吃饭    Tags:Java   点击:(57)  评论:(0)  加入收藏
在项目中如何避免和解决Java内存泄漏问题
在Java中,内存泄漏通常指的是程序中存在一些不再使用的对象或数据结构仍然保持对内存的引用,从而导致这些对象无法被垃圾回收器回收,最终导致内存占用不断增加,进而影响程序的性...【详细内容】
2024-02-01  编程技术汇  今日头条  Tags:Java   点击:(70)  评论:(0)  加入收藏
Java中的缓存技术及其使用场景
Java中的缓存技术是一种优化手段,用于提高应用程序的性能和响应速度。缓存技术通过将计算结果或者经常访问的数据存储在快速访问的存储介质中,以便下次需要时可以更快地获取。...【详细内容】
2024-01-30  编程技术汇    Tags:Java   点击:(73)  评论:(0)  加入收藏
JDK17 与 JDK11 特性差异浅谈
从 JDK11 到 JDK17 ,Java 的发展经历了一系列重要的里程碑。其中最重要的是 JDK17 的发布,这是一个长期支持(LTS)版本,它将获得长期的更新和支持,有助于保持程序的稳定性和可靠性...【详细内容】
2024-01-26  政采云技术  51CTO  Tags:JDK17   点击:(90)  评论:(0)  加入收藏
Java并发编程高阶技术
随着计算机硬件的发展,多核处理器的普及和内存容量的增加,利用多线程实现异步并发成为提升程序性能的重要途径。在Java中,多线程的使用能够更好地发挥硬件资源,提高程序的响应...【详细内容】
2024-01-19  大雷家吃饭    Tags:Java   点击:(107)  评论:(0)  加入收藏
这篇文章彻底让你了解Java与RPA
前段时间更新系统的时候,发现多了一个名为Power Automate的应用,打开了解后发现是一个自动化应用,根据其描述,可以自动执行所有日常任务,说的还是比较夸张,简单用了下,对于office、...【详细内容】
2024-01-17  Java技术指北  微信公众号  Tags:Java   点击:(99)  评论:(0)  加入收藏
Java 在 2023 年仍然流行的 25 个原因
译者 | 刘汪洋审校 | 重楼学习 Java 的过程中,我意识到在 90 年代末 OOP 正值鼎盛时期,Java 作为能够真正实现这些概念的语言显得尤为突出(尽管我此前学过 C++,但相比 Java 影响...【详细内容】
2024-01-10  刘汪洋  51CTO  Tags:Java   点击:(78)  评论:(0)  加入收藏
站内最新
站内热门
站内头条