引言
Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] 、缓存 [caching] 、原生类型支持 [primitives support] 、并发库 [concurrency libraries] 、通用注解 [common annotations] 、字符串处理 [string processing] 、I/O 等等。 所有这些工具每天都在被Google的工程师应用在产品服务中
多少次你写了像下面一样的代码:
Map<String, Map<Long, List<String>>> map = new HashMap<String, Map<Long,List<String>>>();
或者像这样的不堪入目的代码:
int a = 5; int b = 10; int compareTo = Integer.valueOf(a).compareTo(Integer.valueOf(b));
又有多少次你写了像下面这样的代码,只为了从一个文件中读一点点东西?:
File file = new File(getClass().getResource("/test.txt").getFile()); BufferedReader reader; String text = ""; try { reader = new BufferedReader(new FileReader(file)); String line = null; while (true) { line = reader.readLine(); if (line == null) { break; } text += line.trim() + "\n"; } reader.close(); reader = null; } catch (FileNotFoundException e1) { e1.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
好吧,我想说… 这是什么TM玩意?我们已经有Apache Commons Collections很多年了。那为什么我们还需要另外一个collections库呢?我看过很多像这样的评论:
“任何有一段时间开发经验的Java程序员都会积累这些类型的实用的工具类”
好吧,是的,这对于大多数开发者来说可能是(应该是)对的。但是,有太多理由来摆脱垃圾代码和重用漂亮的工具类!在这个博客里,我将要告诉你一些的确引起我求知欲-让我沉溺于其中的事情,那就是Google Collections。
这个库简化了你的代码,使它易写、易读、易于维护。它能提高你的工作效率,让你从大量重复的底层代码中脱身。
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 的一些高级特性。
Google Collections一览
显然一篇博文不能深入地覆盖Google Collections的方方面面,所以我决定把时间放在我日常编码中使用到的基础且不失强大的特性上,首先,不要这么做:
Map<String, Map<Long, List<String>>> map = new HashMap<String, Map<Long,List<String>>>();
要这么做:
Map<String, Map<Long, List<String>>> map = Maps.newHashMap();
或者更甚者直接使用静态导入
Map<String, Map<Long, List<String>>>map = newHashMap();
操作lists和maps
当你在写单元测试时,经常会构造一些测试数据,可能是list、map、set等,对于一些像我一样草率的人来说,测试代码中会经常看到类似下面的语句:
List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d");
其实我也知道,这几行代码看起来很烂,我只是想用一些测试数据构造一个不可变的list而已,我希望能像下面这样写一行代码搞定这些。。如何办到?好吧,这很简单!
ImmutableList<String> of = ImmutableList.of("a", "b", "c", "d");
Map也一样
ImmutableMap<String,String> map = ImmutableMap.of("key1", "value1", "key2", "value2");
我现在慢慢的越来越喜欢这种简单而又有效的写代码的方式,我还想缩短一下上面的代码,但是由于ImmutableList和ImmutableMap都有of方法,不能使用静态导入。不过有一个变通的方法就是说在我们自己的集合工具中包装这些创建工厂方法,那么对于不可变的list和map我只需要简单的这么写就好:
ImmutableMap<String,String> map2 = mapOf("key1", "value1", "key2", "value2") 或者 ImmutableList<String> list2 = listOf("a", "b", "c", "d");
而且如果我想构造填充一个ArrayList(或者一个HashMap),我可以这样:
ArrayList<String> list3 = arrayListOf("a", "b", "c", "d");
两种方式都可以,选择权在你手上,很明显,这种较之前面的方式更灵活优雅一些,你说呢?
除去可以使用方便干净的方式来创建并填充集合类,我们也提供了很多额外的工具方法,比如过滤,对set取交集和并集,排序等等一些更优雅的方法
静态导入和Eclipse模板
作为一个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“,并增加下面的内容:
${:importStatic(com.google.common.collect.Maps.newHashMap)}newHashMap();${cursor}
以上就是创建快捷补全的全部步骤了,现在去为你常用到的所有方法添加模板吧!
Guava走马观花
最后,但并非不重要,我将向你展示一下如果使用Guava来处理本文开头留下来的两个问题:
1、从文件中按行读取内容
File file = new File(getClass().getResource("/test.txt").getFile());List<String> lines = null; try { lines = Files.readLines(file, Charsets.UTF_8); } catch (IOException e) { e.printStackTrace(); }
2、比较两个基本类型
int compare = Ints.compare(a, b);
3、把一个List转换为int数组
List<Integer> list = listOf(1, 2, 3, 4); 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类,在处理Java基本类型时Guava给我们带来的别的工具类。
The Guava CharMatcher
CharMatcher可以非常方便地添加到你的java工具箱中。有些人形容它:“像打了兴奋剂的StringUtils
你可以使用预先设定好的常量,比如CharMatcher.WHITESPACE、CharMatcher.JAVA_DIGIT 或者CharMatcher.ASCII,此外你还有很多简便的工厂方法如CharMatcher.is(‘aaa’), CharMatcher.isNot(‘bbb’), CharMatcher.oneOf(‘abcd’).negate(),甚至更复杂一些比如:
CharMatcher.inRange('a', 'z').or(inRange('A', 'Z'));
当然你可以继承它然后实现方法 matches(char c)。你可以把 Google Collection中都创造实现一遍(当然下次我们会覆盖到)!
如果你想从字符串中得到所有的数字,那么你可以这样
System.out.println(CharMatcher.DIGIT.retainFrom("some te0xt 89983 and 101more")); // 从字符串中得到所有的数字 System.out.println(CharMatcher.DIGIT.removeFrom("some te0xt 89983 and 101more")); // 从字符串中删除所有的数字
还有好多匹配的方法:
matchesAllOf(CharSequence)
matchesAnyOf(CharSequence)
matchesNoneOf(CharSequence)
除了indexIn, lastIndexIn and countIn这些方法,还有很多trimming, replacing and collapsing相关的方法.
Joiner and Splitter
目前Joiner还是Collections 的一部分,Splitter已经加入了Guava (不过一旦Collections 发布1.0版本,那么它们会一起加入到Guava)。
可以这么使用Joiner:
String[] subdirs = { "usr", "local", "lib" }; String directory = Joiner.on("/").join(subdirs); System.out.println(directory); // usr/local/lib
得益于Guava对基本型的支持,可以很方便这么处理:
int[] numbers = { 1, 2, 3, 4, 5 }; String numbersAsStringDirectly = Ints.join(";", numbers); System.out.println(numbersAsStringDirectly); // 1;2;3;4;5
对于字符串,我们可以直接进行分割,但是这样做多少有些奇怪, Splitter 提供了更多的操作,而且更加健壮。字符创分割通常返回的是一个数组而 Splitter 返回的是一个迭代 Iterable。
int[] numbers = { 1, 2, 3, 4, 5 }; String numbersAsString = Joiner.on(";").join(Ints.asList(numbers)); Iterable split = Splitter.on(";").split(numbersAsString); Iterator<Object> it = split.iterator(); while (it.hasNext()) { System.out.println(it.next().toString()); }
你可以简单地分割字符串:
String[] splitRegular = testString.split(",");
但是当需要处理下面这样的字符串时:
Iterable split = Splitter.on(",").split("foo , what,,,more,"); Iterator<Object> it = split.iterator(); while (it.hasNext()) { System.out.println("\""+it.next().toString()+"\""); }
输出结果是:
“foo ”
” what”
“”
“”
“more”
“”
这样的结果也许还可以,但是Splitter允许我们对分割结果做更多的控制:
Iterable<String> split = Splitter.on(",").omitEmptyStrings().trimResults().split("foo , what,,,more,"); Iterator<String> it = split.iterator(); while (it.hasNext()) { System.out.println("\""+it.next().toString()+"\""); }
得到的结果是 foo’,’what’,’more’
Joiner和Splitter都是可配置的,甚至你可以把Joiner使用在map中。
关于基本数据类型处理
Guava已经提供了诸如Ints.compare(a, b)和Ints.toArray(list)
假如有一个整型数字数组,我们想知道数组中是否有特定的整型数字。传统的写法如下:
int[] array = { 1, 2, 3, 4, 5 }; int a = 4; boolean hasA = false; for (int i : array) { if (i == a) { hasA = true; } }
使用Guava,我们可以如下:
boolean contains = Ints.contains(array, a);
同样,其他类型的基本型数组也可以这么来做。我们甚至可以直接对数组做如下的事:
int indexOf = Ints.indexOf(array, a); int max = Ints.max(array); int min = Ints.min(array); int[] concat = Ints.concat(array, array2);
Function
Google collections提供了Function
topMap = Maps.transformValues(fromMap, function); toList = Lists.transform(fromList, function);
举个例子来说,假设你有一个Map,key是物品,value是对应的价格,单位是欧元。那么,你有个需求是将里面的价格都转换为美元,传统的做法是遍历整个Map,然后更新每个value值,将价格转换为美元价格,好麻烦。。。
有了Functions,世界一下子变清净了…
Map usdPriceMap = Maps.transformValues(eurPriceMap, new Function() { double eurToUsd = 1.4888; public Double apply(final Double from) { return from * eurToUsd; } });
好吧,你可能说匿名内部类搞的有点糟,因为 你可能想重用这个function—这里只是演示函数式的一些特点而已。
和这个类似的,我们也可以使用Functions来把一个对象转换成一个完全不同的对象,比如将一个整形转换为字符串。
…
可以从这里获得源码:
svn checkout http://guava-libraries.googlecode.com/svn/trunk/guava-libraries-read-only
如果你们也用过Guava或者Google Collections请在评论一栏分享一下你们的心得。
关注下 Google Collections library包的更高级特性如:Functions, Filtering and Ordering! 欢迎继续收看,请与我们分享你的看法。
持续更新……..
参考文章,向大牛致敬:http://www.oschina.net/translate/beautiful-code-with-google-collections-guava-and-static-imports-part-1、
http://www.oschina.net/translate/diving-into-the-google-guava-library-part-2、
http://ifeve.com/google-guava/