1-使用 GOOGLE COLLECTIONS,GUAVA,STATIC IMPORTS 编写漂亮代码
写在前面:
以前在一个项目中用到过guava,当时匆匆用,也没细研究,今天偶然在occhina看到这个系列教程的翻译,感觉不错,介绍得还比较全面,就一口气全看完了,但看到第四节,发现还没翻译,于是自己就硬着头皮看了英文,发现也能看懂大概意思,就顺手翻译了一下,也算是为开源事业做点儿贡献吧。把文章转到自己博客记录一下。
附:
Guava API
Guava 用法整理
本文转自网络,如有侵权,请联系本人删除! 原文 译文
这个夏天的早些时候,我已经极力向我的所有同事推荐了 Google Collections 。 Kevin Bourrillion说他的一个同事告诉他“没有使用Google Collections前编程就像把一只手绑在背后”。
我灰常同意Kevin的这个同事!
可能文章的标题有点奇怪。我指的是“编写漂亮代码”。我猜我应该说“简洁的”Java代码,但是,它们意思可不一样(译者注:漂亮的代码看着很爽,简洁的不一定爽)。
在我准备开始卖力的吆喝这个我最最喜欢的Java类库前,我有几个额问题要问问你:
多少次你写了像下面一样的代码:
1 |
Map<String, Map<Long, List<String>>> map = new HashMap<String, Map<Long,List<String>>>(); |
或者像这样的不堪入目的代码:
3 |
int compareTo = Integer.valueOf(a).compareTo(Integer.valueOf(b)); |
或者有很多的if和else :-(
又有多少次你写了像下面这样的代码,只为了从一个文件中读一点点东西?:
01 |
File file = new File(getClass().getResource( "/test.txt" ).getFile()); |
02 |
BufferedReader reader; |
05 |
reader = new BufferedReader( new FileReader(file)); |
08 |
line = reader.readLine(); |
12 |
text += line.trim() + "\n" ; |
16 |
} catch (FileNotFoundException e1) { |
18 |
} catch (IOException e) { |
好吧,我想说… 这是什么TM玩意?我们已经有Apache Commons Collections很多年了。那为什么我们还需要另外一个collections库呢?我看过很多像这样的评论:
“任何有一段时间开发经验的Java程序员都会积累这些类型的实用的工具类”
好吧,是的,这对于大多数开发者来说可能是(应该是)对的。但是,有太多理由来摆脱垃圾代码和重用漂亮的工具类!在这个博客里,我将要告诉你一些的确引起我求知欲-让我沉溺于其中的事情,那就是Google Collections。
正如Jared Levy 曾说过的:
这个库简化了你的代码,使它易写、易读、易于维护。它能提高你的工作效率,让你从大量重复的底层代码中脱身。
此外,我会给你们展示一下Guava里面包含的很多非常酷的功能,让你们看看如何用它来写出更漂亮的代码,一旦看到这些,你们就会和以前那种僵化的令人讨厌的写代码方式说拜拜了。
Google Guava 是 Google 为 Java 1.6 编写的核心库。它仍然不是一个很成熟的库,在未来几个月还将不断的变化。Google Collections 将在 1.0 版本发布时将成为 Guava 的一部分。Guava (和 Google Collections) 已经率先被很多 Google 开发者使用。支持该项目的开发者有 Kevin Bourrillion, Jared Levy, Crazy Bob Lee, Josh Bloch(!) (Google 的 Java 首席架构师) 和 limpbizkit (我们找不到这家伙的真实姓名). Google Collections 在 2007 年就已经有了,但 Guava 是在 2009年9月推出的。
作为这个系列的博客,我将向你介绍 Google Collections 并告诉你使用 Guava 和 Google Collections 能为你带来什么好处。包括代码量的减少以及新的更快的数据结构。在第二部分我们将深入探讨 Guava 和 Collections 的一些高级特性。
1、Google Collections一览
显然一篇博文不能深入地覆盖Google Collections的方方面面,所以我决定把时间放在我日常编码中使用到的基础且不失强大的特性上,首先,不要这么做:
1 |
Map<String, Map<Long, List<String>>> map = new HashMap<String, Map<Long,List<String>>>(); |
要这么做:
1 |
Map<String, Map<Long, List<String>>> map = Maps.newHashMap(); |
或者更甚者直接使用静态导入:
1 |
Map<String, Map<Long, List<String>>>map = newHashMap(); |
很棒,不是吗?多亏有泛型和写Collections的哥们提供给我们的这些方便工厂方法,我们不再需要写一些Java本身应该自带的东西。好吧,我知道这些会是JDK 7里的一部分,那当然很好,不过Google Collections现在就有这些功能。
类似于com.google.common.collect.Maps提供的这些静态工具方法,Lists和Sets也有:
还有更多! 自己去看看吧![http://code.google.com/p/google-collections/]
2、操作lists和maps
当你在写单元测试时,经常会构造一些测试数据,可能是list、map、set等,对于一些像我一样草率的人来说,测试代码中会经常看到类似下面的语句:
1 |
List<String> list = new ArrayList<String>(); |
其实我也知道,这几行代码看起来很烂,我只是想用一些测试数据构造一个不可变的list而已,我希望能像下面这样写一行代码搞定这些。。如何办到?好吧,这很简单!
1 |
ImmutableList<String> of = ImmutableList.of( "a" , "b" , "c" , "d" ); |
Map也一样
1 |
ImmutableMap<String,String> map = ImmutableMap.of( "key1" , "value1" , "key2" , "value2" ); |
我现在慢慢的越来越喜欢这种简单而又有效的写代码的方式,我还想缩短一下上面的代码,但是由于ImmutableList和ImmutableMap都有of方法,不能使用静态导入。不过有一个变通的方法就是说在我们自己的集合工具中包装这些创建工厂方法,那么对于不可变的list和map我只需要简单的这么写就好:
1 |
ImmutableMap<String,String> map2 = mapOf( "key1" , "value1" , "key2" , "value2" ) |
或者
1 |
ImmutableList<String> list2 = listOf( "a" , "b" , "c" , "d" ); |
而且如果我想构造填充一个ArrayList(或者一个HashMap),我可以这样:
1 |
ArrayList<String> list3 = arrayListOf( "a" , "b" , "c" , "d" ); |
两种方式都可以,选择权在你手上,很明显,这种较之前面的方式更灵活优雅一些,你说呢? 除去可以使用方便干净的方式来创建并填充集合类,我们也提供了很多额外的工具方法,比如过滤,对set取交集和并集,排序等等一些更优雅的方法,我将会在第二部分中讲解它们。
3、静态导入和Eclipse模板
在这之前,我想先告诉你我在写代码时,是如果借用Eclipse的Code模板功能更高效的使用集合类的。(我假设你了解在IDEA或者其他IDE中也存在类似的功能,你可以根据这部分叙述做出相应的扩展或变通)
作为一个Eclipse的用户,我非常喜欢快捷键(参照这篇介绍Eclipse快捷键的博文:MouseFeed and how you can easily learn Eclipse shortcuts)
OK,Ctrl+space是你们的好朋友!他也是代码自动补全的快捷键。(译者注:当然。在中国,由于这个快捷键和切换输入法冲突,一般都设置为了“Alt+/”作为代码补全的快捷键)
在Eclipse下你可以创建一个模板来绑定到一个自动补全的快捷关键字上,这也是魔力所在!
相对于键入Maps.newHashMap()来创建一个HashMap,或者干脆使用静态导入,就可以简单的键入newHashMap()来创建一个HashMap,我只是简单的敲入newH,按下ctrl+space,见证奇迹的时刻到了!
在Eclipse菜单栏选择Window -> Preferences,进入Java -> Editor -> Templates,点击“New”。Name处就是你想敲入的快捷关键字,我通常命名为我的方法名,比如在这里,就是“newHashMap”,加上你喜欢的描述,比如”Import static Maps.newHashMap“,并增加下面的内容:
1 |
${:importStatic(com.google.common.collect.Maps.newHashMap)}newHashMap();${cursor} |
以上就是创建快捷补全的全部步骤了,现在去为你常用到的所有方法添加模板吧!
4、Guava走马观花
最后,但并非不重要,我将向你展示一下如果使用Guava来处理本文开头留下来的两个问题:
1.从文件中按行读取内容:
1 |
File file = new File(getClass().getResource( "/test.txt" ).getFile()); |
2 |
List<String> lines = null ; |
4 |
lines = Files.readLines(file, Charsets.UTF_8); |
5 |
} catch (IOException e) { |
2.比较两个基本类型:
1 |
int compare = Ints.compare(a, b); |
3.把一个List转换为int数组:
1 |
List<Integer> list = listOf( 1 , 2 , 3 , 4 ); |
2 |
int [] array2 = Ints.toArray(list); |
Guava为我们提供了对Core Java类库全面的扩展,我们可以使用com.google.common.primitices包下的Ints,Doubles,Floats,Shorts,Bytes以及Bools等工具类操作基本类型的数据;com.google.common.io包提供了操作streams,buffers以及files等等,而且并发包中提供了一些像Futures,Callables以及Executors等方便的工具类来减轻我们写并发代码的痛苦。除此之外,Guava提供了对Collections的增强,以及非常优雅的CharMatcher、Joiner以及Splitter类,这些类我将在下篇博文中提到。
可以从这里获得源码:
下次我们会深入Guava 的高级功能,探索一下集合的用法,看看怎样通过Multimap使java的功能更加强大,如何用mapping功能来转换集合。请拭目以待。如果你们也用过Guava或者Google Collections请分享一下你们的心得。
2-深入探索 GOOGLE GUAVA 库
在这个系列的第一部分里,我简单的介绍了非常优秀的Google collections和Guava类库,并简要的解释了作为Java程序员,如果使用Guava库来减少项目中大量的样板代码。在这篇博文中我们将深入挖掘Guava提供的更高级的特性。 我们将深入挖掘Guava库,并了解一下优雅的CharMatcher类、Joiner以及Splitter类,以及在处理Java基本类型时Guava给我们带来的别的工具类。
1、The Guava CharMatcher
CharMatcher 可以非常方便地添加到你的java工具箱中。有些人形容它:“像打了兴奋剂的StringUtils”:p 你可以使用预先设定好的常量,比如CharMatcher.WHITESPACE, CharMatcher.JAVA_DIGIT 或者CharMatcher.ASCII,此外你还有很多简便的工厂方法如CharMatcher.is(‘aaa’), CharMatcher.isNot(‘bbb’), CharMatcher.oneOf(‘abcd’).negate(),甚至更复杂一些比如:
1 |
CharMatcher.inRange( 'a' , 'z' ).or(inRange( 'A' , 'Z' )); |
当然你可以继承它然后实现方法 matches(char c)。你可以把 Google Collection中都创造实现一遍(当然下次我们会覆盖到)!
如果你想从字符串中得到所有的数字,那么你可以这样:
1 |
String string = CharMatcher.DIGIT.retainFrom( "some text 89983 and more" ); |
如果你想把字符串中的数据都去掉,可以如下:
1 |
String string = CharMatcher.DIGIT.removeFrom( "some text 89983 and more" ); |
还有好多匹配的方法:
matchesAllOf(CharSequence)
atchesAnyOf(CharSequence)
matchesNoneOf(CharSequence)
除了indexIn, lastIndexIn and countIn这些方法,还有很多trimming, replacing and collapsing相关的方法。 更多信息查看Java doc.
2、Joiner and Splitter
目前Joiner还是Collections 的一部分,Splitter 已经加入了Guava (不过一旦Collections 发布1.0版本,那么它们会一起加入到Guava)。
可以这么使用Joiner:
1 |
String[] subdirs = { "usr" , "local" , "lib" }; |
2 |
String directory = Joiner.on( "/" ).join(subdirs); |
或者这样:
1 |
int [] numbers = { 1 , 2 , 3 , 4 , 5 }; |
2 |
String numbersAsString = Joiner.on( ";" ).join(Ints.asList(numbers)); |
得益于Guava对基本型的支持,可以很方便这么处理:
1 |
String numbersAsStringDirectly = Ints.join( ";" , numbers); |
对于字符串,我们可以直接进行分割,但是这样做多少有些奇怪, Splitter 提供了更多的操作,而且更加健壮。字符创分割通常返回的是一个数组而 Splitter 返回的是一个迭代 Iterable。
1 |
Iterable split = Splitter.on( "," ).split(numbsAsString); |
你可以简单地分割字符串:
1 |
String[] splitRegular = testString.split( "," ); |
但是当需要处理下面这样的字符串时:
1 |
String testString = "foo , what,,,more," ; |
输出结果是:
‘foo ‘
‘ what’
”
”
‘more
这样的结果也许还可以,但是Splitter允许我们对分割结果做更多的控制:
1 |
Iterable<String> split = Splitter.on( "," ).omitEmptyStrings().trimResults().split(testString); |
得到的结果是 foo’,'what’,'more’
Joiner和Splitter都是可配置的,甚至你可以把Joiner使用在map中。
3、Working with primitives cont’d
在博客的第一部分,我简单提到了基本型的用法。Guava已经提供了诸如Ints.compare(a, b)和Ints.toArray(list)。
让我介绍Guava 关于基本型提供的更多的一些用法吧。
假如我有一个整型数字数组,我们想知道数组中是否有特定的整型数字。传统的写法如下:
1 |
int [] array = { 1 , 2 , 3 , 4 , 5 }; |
使用Guava,我们可以如下:
1 |
boolean contains = Ints.contains(array, a); |
同样,其他类型的基本型数组也可以这么来做。我们甚至可以直接对数组做如下的事:
1 |
int indexOf = Ints.indexOf(array, a); |
2 |
int max = Ints.max(array); |
3 |
int min = Ints.min(array); |
4 |
int [] concat = Ints.concat(array, array2); |
在这个系列的下一章我们将关注下 Google Collections library包的更高级特性如:Functions, Filtering and Ordering!欢迎继续收看,请与我们分享你的看法。
3-JAVA 的函数式编程,通过 GOOGLE COLLECTIONS 过滤和调用
在本系列博客的第一、二部分,我介绍了非常优秀的Google Collections和Guava包。本篇博客中我们来看看如何使用Google Collections来做到过滤和排序功能。此外,我会带你看看Google Collections是如何使Java有一点点“functional(方法化)”的进步了。
1、Functions, Functions, Functions!!
Google Collections给我们带来了一对非常优雅的东东,叫做:Functions and Predicates! 和你使用的Scala一样有神奇的地方,现在你可以使用在no-functional 的java身上了。你可以在com.google.common.base包里找到这些(更多)。
我们将在下一部分过滤集合的时候谈到Predicates类,首先我们先看一下Function的用法!
Google collections提供了Function接口,实际上,一个function就是从一个对象到另外一个对象的转换变形。
像Lists和Maps这类的Collection工具类给我们提供了转换的方法:
1 |
topMap = Maps.transformValues(fromMap, function); |
2 |
toList = Lists.transform(fromList, function); |
举个例子来说,假设你有一个Map,key是物品,value是对应的价格,单位是欧元。那么,你有个需求是将里面的价格都转换为美元,传统的做法是遍历整个Map,然后更新每个value值,将价格转换为美元价格,好麻烦...
有了Functions,世界一下子变清净了...
1 |
Map usdPriceMap = Maps.transformValues(eurPriceMap, new Function() { |
2 |
double eurToUsd = 1.4888 ; |
3 |
public Double apply( final Double from) { |
4 |
return from * eurToUsd; |
好吧,你可能说匿名内部类搞的有点糟,因为 你可能想重用这个function---这里只是演示函数式的一些特点而已。
和这个类似的,我们也可以使用Functions来把一个对象转换成一个完全不同的对象,比如将一个整形转换为字符串。
我们稍后再深入Functions类,首先我们浏览一下Multimap集合以及我们如果使用一点function来转换一个集合。
2、使用条件过滤集合
我在Integrasco做数据工作时遇到的最常见的任务是过滤数据和对大集合进行排序。 简单起见,我们假设你有一个姓名列表想要过滤(看起来有点幼稚):
1 |
List<String> names = listOf( "Aleksander" , "Jaran" , "Integrasco" , "Guava" , "Java" ); |
我们可以使用com.google.common.collect.Iterables和com.google.common.base.Predicates类来过滤例子中的列表,使过滤后的列表中只包含Aleksander或者Jaran:
现在我知道这听起来有点傻帽,但是你仍然可以实现自己的Predicates条件类,比如返回名字长度小于5的列表(我从codemonkeyism.com偷到了下面这个例子):
1 |
Iterable<String> filtered = filter(names, or(or(equalTo( "Aleksander" ),equalTo( "Jaran" )), lengthLessThan( 5 ))); |
这个例子返回的是Aleksander,Jaran以及Java(因为Java的长度小于5)。or条件的部分读起来有点绕,但我还可以忍受。equalTo以及or条件都是Predicates自带的方法,用起来很方便。
现在我们来实现一个lengthLessThan的条件,我们只需要这么做:
1 |
private static class LengthLessThanPredicate implements Predicate<String> { |
2 |
private final int length; |
3 |
private LengthLessThanPredicate( final int length) { |
6 |
public boolean apply( final String s) { |
7 |
return s.length() < length; |
把这个实现在你的工具类里包装一下就像这样:
1 |
public static Predicate<String> lengthLessThan( final int length) { |
2 |
return new LengthLessThanPredicate(length); |
关注一下Stephan的博文fluent interfaces for Google Collections --写的相当优雅~!
3、对集合排序
多亏有了java Collections类,我们可以这么排序:
1 |
Collections.sort(List<T>, Comparator<? super T>) |
但有时候我们想做更复杂一些的事情,比如合并多个Comparator或者我们可能只是想要排序过的集合的一个视图,而不改变原来集合的顺序。
Google Collections给我们提供了Ordering,让我们更好地掌控排序。假如我们有两个对Person类排序的comparator,一个是根据lastName排序,一个是根据firstName排序:
01 |
Comparator<Person> byLastName = new Comparator<Person>() { |
02 |
public int compare( final Person p1, final Person p2) { |
03 |
return p1.getLastName().compareTo(p2.getLastName()); |
08 |
Comparator<Person> byFirstName = new Comparator<Person>() { |
09 |
public int compare( final Person p1, final Person p2) { |
10 |
return p1.getFirstName().compareTo(p2.getFirstName()); |
那么,假如我们现在想现根据last name排序,再根据first name排序,然后对排序结果反序,那我们我们需要做的是:
1 |
List<Person> sortedCopy = Ordering.from(byLastName).compound(byFirstName).reverse().sortedCopy(persons); |
而且,你甚至无需创建Comparator比较器,你可以直接扩展Ordering类!
1 |
List<Person> sortedCopy = orderByLastName.compound(orderByFirstName).reverse().sortedCopy(persons); |
4、继续过滤和排序
在这个系列的第一部分,Steve提到了在Scala中,你可以这么做:
1 |
people.filter(_.firstName == "Steve" ).sort(_.lastName < _.lastName) |
功能是说从people这个集合中筛选出firstName为“Steve”的,并且按他们的lastName属性排序。
从语法上来看,这行代码非常优雅!那么我们也来看一下使用Google Collections时应该怎么做。Google Collections给我们提供了前面提到的Iterables类,我们可以使用Iterables类来实现过滤和转换(你可以使用Google Collections里的Collections2来实现同样的功能)。
那现在我们就来看一下如何实现和Steve的那行Scala代码一样的功能,虽然看起来没有Scala实现的那么优雅,但却是使用Predicates和Ordering类来实现上面功能的一种方式。
1 |
Ordering.from(byLastName).sortedCopy(filter(persons, withFirstName( "Steve" ))); |
虽然跟Scala提供的语法糖有点差距(很明显我们还是需要我们的“withFirstName”条件谓词以及“byLastName”比较器),但至少这比我们不使用Google Collections接近多了!(如果采用Stephen的流式接口的话,代码会更易读)。
Kevin Bourrillion在另一篇关于使用Google Collections编写函数式的Java的文章中提到:
语法很烂。而且同时这在语言本身改变之前只是权宜之计,到那时我们才真正的选择最佳的语法并开始真正的函数式编程。所以我还没决定我会投入多大的努力到Function/Predicate中去(这段真的很难翻译..我去..)
在下篇也是最后一篇关于Google Collections和Guava的博文中,我们将看到如果使用Google Collections操作Set和Map,以及使用Preconditiones来做验证,而且我们带你体验一下奇妙的Multimap集合类以及如何进行切分!拭目以待吧!
4-条件,多重映射和分片
在本系列博客的前三章,我们大概介绍了Google的Guava类库和Collections类库,作为一名Java开发人员,相信你会从使用这些类库,进而来减少在你项目中使用样板文件的数量而获益。在本系列博客的最后一篇中,我将带大家来了解一个会让你完全爱上并沉浸于其中的的集合工具类-Multimap。我们将带大家了解下如何使用Google Collections的Preconditions来做校验,但是在此之前,先让我们来了解下如何对Set和Map进行交集、并集和差集的运算。
1、Set的交集,并集和map的相关操作
有时,当我们需要去对两个Set的取交集、并集和差集的时候,那是一件相关麻烦的事情,而且代码看起来很凌乱。经常情况,你会以一遍又一遍的循环来结束这种做法。但是如果我们使用Google Collections提供的Sets类就可以轻而易举的完成这些操作,并且是完全免费的!
01 |
HashSet setA = newHashSet( 1 , 2 , 3 , 4 , 5 ); |
02 |
HashSet setB = newHashSet( 4 , 5 , 6 , 7 , 8 ); |
04 |
SetView union = Sets.union(setA, setB); |
05 |
System.out.println( "union:" ); |
06 |
for (Integer integer : union) |
07 |
System.out.println(integer); |
09 |
SetView difference = Sets.difference(setA, setB); |
10 |
System.out.println( "difference:" ); |
11 |
for (Integer integer : difference) |
12 |
System.out.println(integer); |
14 |
SetView intersection = Sets.intersection(setA, setB); |
15 |
System.out.println( "intersection:" ); |
16 |
for (Integer integer : intersection) |
17 |
System.out.println(integer); |
From the Public Object blog:
“不同于惯例,这些方法没有做任何的拷贝。相反,他们返回了代表了这两个集合的视图。 但是在有些情况下,这些拷贝又很有用,我们可以用immutableCopy类中提供的一个便利方法来实现拷贝”
同样地,对于Map而言,我们可以像下面这样处理:
1 |
MapDifference differenceMap = Maps.difference(mapA, mapB); |
如果用MapDifference类,我们还可以这样:
1 |
differenceMap.areEqual(); |
2 |
Map entriesDiffering = differenceMap.entriesDiffering(); |
3 |
Map entriesOnlyOnLeft = differenceMap.entriesOnlyOnLeft(); |
4 |
Map entriesOnlyOnRight = differenceMap.entriesOnlyOnRight(); |
5 |
Map entriesInCommon = differenceMap.entriesInCommon(); |
Thank you, Google Collections!
2、用Preconditions进行校验
早在初夏的时候,一个叫刚刚加入我们的同事Bent André向我介绍了用Preconditions进行校验的想法,但是,直到最近我才发现Google Collections实际上已经包含了相应的实现(我们自己也已经有了相似的实现)。
那么这个实现具体是什么样子的呢?校验就是要求我们准确无误的做一些事情,通常情况下,它看起来就是类似下面这样的代码:
if (count <= 0) { throw new IllegalArgumentException("must be positive: " + count); }
我们想校验调用我们的方法的方法传参是否正确,如果错了,应该发出一个“他出错了”的警告信息。 JavaDoc中是这样解释的:
“Precondition异常通常表示被调用方法发生了一个错误。它告诉调用者不能用这种方式、这种参数或者其它来调用这个方法。Postcondition或其它运行失败是不会抛出这种类型的异常的。”
因此用Preconditions类或者静态导入,我们就可以用下面这行简单的代码来替代上面那些的冗长的代码了:
1 |
checkArgument(count > 0 , "must be positive: %s" , count); |
同样,该类里还有其它类似的方法来检验状态和空值。
相当简洁,不是么?
“请注意这里是相反的表达(译者注:应该指的是checkNotNull中有not的意思,所以是相反的); 你用Preconditions声明什么是你期望返回true的,就好像你在Java中运用断言,或者在JUnit中调用assertTrue方法一样。”
From the excellent blog series on Google Collections over at Public Object:
“Preconditions提供对方法状态的校验。它能使你输入的参数如你所期望的的那样足够简洁,与Java内建的断言不同的是Preconditions是一直开启的。”
下面是我在Integrasco上翻遍我所有的代码中,找到的一个比较好的例子:
1 |
public PostExample( final String title, final Date date, final String author) { |
用了Preconditions之后我们再来看看代码变得有多工整:
1 |
public PostExample( final String title, final Date date, final String author) { |
2 |
this .title = checkNotNull(title); |
3 |
this .date = checkNotNull(date); |
4 |
this .author = checkNotNull(author); |
很简洁,不是么?
稍后我们会发表一篇关于异常和校验的更详细的文章,敬请关注!
3、一个集合统治一切 – Multimap
我已经数不清我有多少次需要在一个Map中实现一个key对应多个value的需求,最终不得不以Map<K, List<V>>这种结构来实现的经历了。 并且用这种方式实现一键多值的需求还存在很大的争议,如下所示:
01 |
Map<Person, List<BlogPost>> map = new HashMap<Person, List<BlogPost>>(); |
03 |
public void addBlogPost( final Person author, final BlogPost blogPost) { |
04 |
List<BlogPost> blogPosts = map.get(author); |
05 |
if (blogPosts == null ) { |
06 |
blogPosts = new ArrayList<BlogPost>(); |
07 |
map.put(author, blogPosts); |
09 |
blogPosts.add(blogPost); |
别告诉你你从来没这样做过?利用Google Collections中的Multimap类我们可以轻松实现上述需求,而且感觉很时髦(我很开心我可以这样表达):
1 |
Multimap<Person, BlogPost> multimap = ArrayListMultimap.create(); |
3 |
public void addBlogPost( final Person author, final BlogPost blogPost) { |
4 |
multimap.put(author, blogPost) |
Whoop, whoop!
Google Collections提供了多种Multimaps的实现,如果你想防止出现键值对,可以用HashMultimap;如果你需要键值对按照自然顺序排列,你可以使用TreeMultimap;甚至你想按插入顺序来遍历集合,LinkedHashMultimap可以满足你的需求。
Multimaps同样提供了很多有用的方法,详细内容可以参考Multimap 和 Multimaps的API文档!很激动是么?那就继续往下看吧...
4、分片集合
我管这节叫分片,相信很多人会觉得叫我们在之前文章叫提到的“过滤集合”更准确。但是当我们把把Multimap的概念和功能相结合的时候,你就会意识到他到底有多强大了,而不仅仅是过滤那么简单!
我们假设我们已经拥有了包含了一组map的list。list里的每一个Map代表拥有指定属性的一个文档。这个Map看起来可能会是下面这个样子:
1 |
mapOf( "type" , "blog" , "id" , "292" , "author" , "john" ); |
即每个Map中我们拥有3个属性,他们分别是“type”、 “id”和“author”。
如上所示,所有我们的List看起来应该是下面这个样子:
1 |
List<Map<String, String>> listOfMaps |
现在,我们想把这个list根据所装载对象的类型不同分成多个list,比如一个叫“blog”,一个叫“news”等等...
如果没有Google Collections这将是一场恶梦!我们必须得先循环这个list,然后再分别检查每一个map中的key,然后再把根据类型的不同放入不同的list中。但如果我们不知道map里都有哪些类型,这个过程会更痛苦!
想不想和我一起来看看有没有轻松的办法解决?
用一点点Function的魔法加上Multimaps,我样可以以一种上相当优雅的方式来解决这个问题:
1 |
Multimap<String, Map<String, String>> partitionedMap = Multimaps.index(listOfMaps, new Function<Map<String, String>, String>() { |
2 |
public String apply( final Map<String, String> from) { |
3 |
return from.get( "type" ); |
现在我们拥有了每一个key代表不同类型的Multimaps了!
如果现在我们想要指定类型的所有map,唯一需要做的就是找Multimaps要!
好,就到这里为止了!希望你喜欢这段旅程,同时也希望你能从Google这些真正伟大的项目中受到启发!
欢迎反馈!
Refer:
https://github.com/ecchanger/myblog/tree/master/posts
http://tangsan.im/
http://macrochen.iteye.com/blog/737058
http://www.oschina.net/search?scope=translate&q=Google+Collections