强大的集合工具类,java.util.Collections中未包含的集合工具

Java技术精选

共 8544字,需浏览 18分钟

 ·

2022-03-02 05:46


任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections包含的工具方法。Guava沿着这些路线提供了更多的工具方法:适用于所有集合的静态方法。这是Guava最流行和成熟的部分之一。


我们用相对直观的方式把工具类与特定集合接口的对应关系归纳如下:



 

1

静态工厂方法


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


List list = new ArrayList();


我想我们都认为这很讨厌。因此Guava提供了能够推断范型的静态工厂方法:


List list = Lists.newArrayList();
Map map = Maps.newLinkedHashMap();


可以肯定的是,JDK7版本的钻石操作符(<>)没有这样的麻烦:


List list = new ArrayList<>();


但Guava的静态工厂方法远不止这么简单。用工厂方法模式,我们可以方便地在初始化时就指定起始元素。


Set copySet = Sets.newHashSet(elements);
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");


此外,通过为工厂方法命名(Effective Java第一条),我们可以提高集合初始化大小的可读性:


List<Type> exactly100 = Lists.newArrayListWithCapacity(100);
List<Type> approx100 = Lists.newArrayListWithExpectedSize(100);
Set<Type> approx100Set = Sets.newHashSetWithExpectedSize(100);


确切的静态工厂方法和相应的工具类一起罗列在下面的章节。


注意:Guava引入的新集合类型没有暴露原始构造器,也没有在工具类中提供初始化方法。而是直接在集合类中提供了静态工厂方法,例如:


Multiset multiset = HashMultiset.create();


Iterables


在可能的情况下,Guava提供的工具方法更偏向于接受Iterable而不是Collection类型。在Google,对于不存放在主存的集合——比如从数据库或其他数据中心收集的结果集,因为实际上还没有攫取全部数据,这类结果集都不能支持类似size()的操作 ——通常都不会用Collection类型来表示。


因此,很多你期望的支持所有集合的操作都在Iterables类中。大多数Iterables方法有一个在Iterators类中的对应版本,用来处理Iterator。另外,搜索公众号终码一生后台回复“资料”,获取最新面试资料和教程。


截至Guava 1.2版本,Iterables使用FluentIterable类进行了补充,它包装了一个Iterable实例,并对许多操作提供了”fluent”(链式调用)语法。


下面列出了一些最常用的工具方法。


常规方法



Iterable concatenated = Iterables.concat(
        Ints.asList(1, 2, 3),
        Ints.asList(4, 5, 6)); // concatenated包括元素 1, 2, 3, 4, 5, 6
String lastAdded = Iterables.getLast(myLinkedHashSet);
String theElement = Iterables.getOnlyElement(thisSetIsDefinitelyASingleton);
//如果set不是单元素集,就会出错了!


与Collection方法相似的工具方法


通常来说,Collection的实现天然支持操作其他Collection,但却不能操作Iterable。


下面的方法中,如果传入的Iterable是一个Collection实例,则实际操作将会委托给相应的Collection接口方法。例如,往Iterables.size方法传入是一个Collection实例,它不会真的遍历iterator获取大小,而是直接调用Collection.size。



FluentIterable


除了上面提到的方法,FluentIterable还有一些便利方法用来把自己拷贝到不可变集合




2

Lists


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



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}}


静态工厂方法


Lists提供如下静态工厂方法:



 

3

Sets


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


集合理论方法


我们提供了很多标准的集合运算(Set-Theoretic)方法,这些方法接受Set参数并返回SetView,可用于:


  • 直接当作Set使用,因为SetView也实现了Set接口;

  • 用copyInto(Set)拷贝进另一个可变集合;

  • 用immutableCopy()对自己做不可变拷贝。


方法

  • union(Set, Set)

  • intersection(Set, Set)

  • difference(Set, Set)

  • symmetricDifference(Set,   Set)


使用范例:


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工具方法



Set animals = ImmutableSet.of("gerbil", "hamster");
Set fruits = ImmutableSet.of("apple", "orange", "banana");
Set>>
product = Sets.cartesianProduct(animals, fruits);
// {{"gerbil", "apple"}, {"gerbil", "orange"}, {"gerbil", "banana"},
//  {"hamster", "apple"}, {"hamster", "orange"}, {"hamster", "banana"}}
Set>> animalSets = Sets.powerSet(animals);
// {{}, {"gerbil"}, {"hamster"}, {"gerbil", "hamster"}}


静态工厂方法


Sets提供如下静态工厂方法:



 

4

Maps


Maps类有若干值得单独说明的、很酷的方法。


uniqueIndex


Maps.uniqueIndex(Iterable,Function)通常针对的场景是:有一组对象,它们在某个属性上分别有独一无二的值,而我们希望能够按照这个属性值查找对象——注:这个方法返回一个Map,键为Function返回的属性值,值为Iterable中相应的元素,因此我们可以反复用这个Map进行查找操作。


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


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


如果索引值不是独一无二的,请参见下面的Multimaps.index方法。另外,搜索公众号终码一生后台回复“资料”,获取最新面试资料和教程。


difference


Maps.difference(Map, Map)用来比较两个Map以获取所有不同点。该方法返回MapDifference对象,把不同点的维恩图分解为:



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


处理BiMap的工具方法


Guava中处理BiMap的工具方法在Maps类中,因为BiMap也是一种Map实现。



静态工厂方法


Maps提供如下静态工厂方法:




5

Multisets


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



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

Multiset 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中的其他工具方法还包括:



Multiset 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"}排列元素



6

Multimaps


Multimaps提供了若干值得单独说明的通用工具方法


index


作为Maps.uniqueIndex的兄弟方法,Multimaps.index(Iterable, Function)通常针对的场景是:有一组对象,它们有共同的特定属性,我们希望按照这个属性的值查询对象,但属性值不一定是独一无二的。


比方说,我们想把字符串按长度分组。


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();
    }
};
 
ImmutableListMultimapString
> digitsByLength= Multimaps.index(digits, lengthFunction);
/*
* digitsByLength maps:
* 3 => {"one", "two", "six"}
* 4 => {"zero", "four", "five", "nine"}
* 5 => {"three", "seven", "eight"}
*/


invertFrom


鉴于Multimap可以把多个键映射到同一个值(注:实际上这是任何map都有的特性),也可以把一个键映射到多个值,反转Multimap也会很有用。Guava 提供了invertFrom(Multimap toInvert,

Multimap dest)做这个操作,并且你可以自由选择反转后的Multimap实现。


注:如果你使用的是ImmutableMultimap,考虑改用ImmutableMultimap.inverse()做反转。


ArrayListMultimap 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 inverse = Multimaps.invertFrom(multimap, TreeMultimap.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}]
MultimapString
> inverse = Multimaps.invertFrom(multimap, HashMultimapString>.create());
// inverse:[1 => {"a","b"}, 2 => {"c"}]


包装器


Multimaps提供了传统的包装方法,以及让你选择Map和Collection类型以自定义Multimap实现的工具方法。



自定义Multimap的方法允许你指定Multimap中的特定实现。但要注意的是:


  • Multimap假设对Map和Supplier产生的集合对象有完全所有权。这些自定义对象应避免手动更新,并且在提供给Multimap时应该是空的,此外还不应该使用软引用、弱引用或虚引用。

  • 无法保证修改了Multimap以后,底层Map的内容是什么样的。

  • 即使Map和Supplier产生的集合都是线程安全的,它们组成的Multimap也不能保证并发操作的线程安全性。并发读操作是工作正常的,但需要保证并发读写的话,请考虑用同步包装器解决。

  • 只有当Map、Supplier、Supplier产生的集合对象、以及Multimap存放的键值类型都是可序列化的,Multimap才是可序列化的。

  • Multimap.get(key)返回的集合对象和Supplier返回的集合对象并不是同一类型。但如果Supplier返回的是随机访问集合,那么Multimap.get(key)返回的集合也是可随机访问的。


请注意,用来自定义Multimap的方法需要一个Supplier参数,以创建崭新的集合。下面有个实现ListMultimap的例子——用TreeMap做映射,而每个键对应的多个值用LinkedList存储。


ListMultimap<String, Integer> myMultimap = Multimaps.newListMultimap(
    Maps.<String, Collection>newTreeMap(),
    new Supplier() {
        public LinkedList get() {
            return Lists.newLinkedList();
        }
    });



7

Tables


Tables类提供了若干称手的工具方法。


自定义Table


堪比Multimaps.newXXXMultimap(Map, Supplier)工具方法,Tables.newCustomTable(Map, Supplier)允许你指定Table用什么样的map实现行和列。另外,搜索公众号终码一生后台回复“资料”,获取最新面试资料和教程。


// 使用LinkedHashMaps替代HashMaps
Table<String, Character, Integer> table = Tables.newCustomTable(
Maps.<String, Map>newLinkedHashMap(),
new Supplier<Map> () {
public Map get() {
return Maps.newLinkedHashMap();
}
});


transpose


transpose(Table)方法允许你把Table转置成Table。例如,如果你在用Table构建加权有向图,这个方法就可以把有向图反转。


包装器


还有很多你熟悉和喜欢的Table包装类。然而,在大多数情况下还请使用ImmutableTable



来源:ifeve.com/google-guava-collectionutilities



往期推荐



内存耗尽后,Redis会发生什么?

Java多线程学习之wait、notify/notifyAll 详解

为什么要尽量避免使用 IN 和 NOT IN 呢?

一键生成数据库文档,堪称数据库界的Swagger

什么是流式SQL,它有什么用?

哪些优秀的无代码/低代码后端开发利器!你知道几个?



浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报