另外一对我经常需要用到的, 是
Resources和
Files.
一般来说, 如果我有一大块的文本, 或者properties文件, xml, 我会选择把它们存到一个文本文件里, 放到jar里. 然后在运行时, 把这个文件当作资源读出. 这样做比直接存到文件系统里面的好处, 在于它对部署方式不敏感, 不管我的代码如何部署, 我只需要从ClassLoader找到我要的文件就好了.
我可以直接用ClassLoader来寻找我的资源, 象这样:
URL url = getClass().getClassLoader().getResource("mypackage/myfile.txt"); if (url == null) { throw new IOException("mypackage/myfile.txt 没找到!"); } ...
把要找的资源名包括在错误信息中很重要. 很多时候, 或者是部署的问题, 或者是程序错误, getResource()会返回null. 如果你不包括这个资源名, 甚至不做这个null检查就直接用这个url变量, 程序会抛出异常, 但是查找错误相当不方便, 无谓浪费时间.
我也可以用Resources来更方便地做同样的事:
URL url = Resources.getResource("mypackage/myfile.txt");
Resources.getResource()自动包括了错误检查.
得到了这个URL, 我就可以从里面读出内容. 如果它是一个文本文件, 我可以用Resources.toString():
String content = Resources.toString(url);
或者如果我需要按行读出:
String[] lines = Resources.readLines(url, Charsets.UTF_8);
这里, 要给 com.google.common.base.Charsets做个广告. 它提供了一些标准的所有平台都支持的Charset常量, 非常非常有用!
而如果是一个二进制文件, 我可以用Resources.toByteArray():
byte[] content = Resources.toByteArray(url);
相比之下, Files相对不是那么常用. 但是有时候当你需要操作文件的时候, 它提供的工具函数还是很顶事的. 比如, 你可以同样地从文本或二进制文件读取字符串或者字节:
String content = Files.toString(textFile, Charsets.UTF_8); byte[] byteArray = Files.toByteArray(binaryFile);
可以向文件里写内容:
Files.write(content, textFile, Charsets.ASCII); Files.write(byteArray, binaryFile);
也可以拷贝文件:
Files.copy(fromFile, toFile);
具体的大家看文档吧, 应该很简单地.
一般来说, 如果我有一大块的文本, 或者properties文件, xml, 我会选择把它们存到一个文本文件里, 放到jar里. 然后在运行时, 把这个文件当作资源读出. 这样做比直接存到文件系统里面的好处, 在于它对部署方式不敏感, 不管我的代码如何部署, 我只需要从ClassLoader找到我要的文件就好了.
我可以直接用ClassLoader来寻找我的资源, 象这样:
- URLurl=getClass().getClassLoader().getResource("mypackage/myfile.txt");
- if(url==null){
- thrownewIOException("mypackage/myfile.txt没找到!");
- }
- ...
把要找的资源名包括在错误信息中很重要. 很多时候, 或者是部署的问题, 或者是程序错误, getResource()会返回null. 如果你不包括这个资源名, 甚至不做这个null检查就直接用这个url变量, 程序会抛出异常, 但是查找错误相当不方便, 无谓浪费时间.
我也可以用Resources来更方便地做同样的事:
- URLurl=Resources.getResource("mypackage/myfile.txt");
Resources.getResource()自动包括了错误检查.
得到了这个URL, 我就可以从里面读出内容. 如果它是一个文本文件, 我可以用Resources.toString():
- Stringcontent=Resources.toString(url);
或者如果我需要按行读出:
- String[]lines=Resources.readLines(url,Charsets.UTF_8);
这里, 要给 com.google.common.base.Charsets做个广告. 它提供了一些标准的所有平台都支持的Charset常量, 非常非常有用!
而如果是一个二进制文件, 我可以用Resources.toByteArray():
- byte[]content=Resources.toByteArray(url);
相比之下, Files相对不是那么常用. 但是有时候当你需要操作文件的时候, 它提供的工具函数还是很顶事的. 比如, 你可以同样地从文本或二进制文件读取字符串或者字节:
- Stringcontent=Files.toString(textFile,Charsets.UTF_8);
- byte[]byteArray=Files.toByteArray(binaryFile);
可以向文件里写内容:
- Files.write(content,textFile,Charsets.ASCII);
- Files.write(byteArray,binaryFile);
也可以拷贝文件:
- Files.copy(fromFile,toFile);
具体的大家看文档吧, 应该很简单地.
用瓜娃以前, 每当遇到把一串东西用逗号分割打印出来的需求, 俺都有点烦. 这算是挺简单无聊的活, 但是每次写起来那代码总是觉得象56k猫拨号上网那么让人磨牙:
StringBuilder builder = new StringBuilder(); int first = true; for (String s : strings) { if (first) { first = false; } else { builder.append(','); } builder.append(s); } String result = builder.build();
瓜娃的 Joiner用起来就顺手顺气多了. 不多废话, 看代码:
String joined = Joiner.on(',').join(strings);
要是需要对null做特殊处理, 比如打印"NA":
String joined = Joiner.on(',').useForNull("NA").join(strings);
或者干脆把null滤掉:
String joined = Joiner.on(',').skipNulls().join(strings);
还有一些其它的定制功能, 自己看javadoc吧.
如果你所有的不是一个对象数组或者Iterable, 而是一个int[], Joiner不提供直接支持. 但是瓜娃有另外一个相当有用的包: com.google.common.primitives. 这里用到的是一个int数组, 所以咱们来看看Ints是否有啥能使的. 哈, 找到拉! (废话, 当然拉, 你事先就知道的嘛!)
String joined = Ints.join(",", intArray);
总之记住一句话, 当你跟原始类型打交道的时候, 看看primitives包里有没有你合适用的铲子, 铁锹什么的.
呵呵, 注意呀, 这里要扣题了(否则高考是要被扣分地), 所谓天下大势, 合久必分, 分久必合. 有Joiner, 就有Splitter.
要说JDK的String类已经有了split(), 但是 这个函数的设计有点那个, 嗯, 有个性. 你要是经常用它就会发现它经常会给你一些惊喜, 嗯, 东坡居士说: 败亦喜, 所以, 这里就是"失败"的意思, 是你会被String.split()华丽地打败的意思.
当然, 我们还可以用JDK的StringTokenizer, 如果你想炫耀你用java正确写老式循环不出错的技巧的话.
瓜娃的Splitter可以让我们用java 5的enhanced for loop, 而且一般你看着一个它像是做什么的, 它就是做的那个.
比如:
for (String word : Splitter.on(',').split("ajoo,so,handsome!")) { System.out.println(word); } // 打印出 ajoo so handsome!
对了, Splitter还支持正则表达式.
- StringBuilderbuilder=newStringBuilder();
- intfirst=true;
- for(Strings:strings){
- if(first){
- first=false;
- }else{
- builder.append(',');
- }
- builder.append(s);
- }
- Stringresult=builder.build();
瓜娃的 Joiner用起来就顺手顺气多了. 不多废话, 看代码:
- Stringjoined=Joiner.on(',').join(strings);
要是需要对null做特殊处理, 比如打印"NA":
- Stringjoined=Joiner.on(',').useForNull("NA").join(strings);
或者干脆把null滤掉:
- Stringjoined=Joiner.on(',').skipNulls().join(strings);
还有一些其它的定制功能, 自己看javadoc吧.
如果你所有的不是一个对象数组或者Iterable, 而是一个int[], Joiner不提供直接支持. 但是瓜娃有另外一个相当有用的包: com.google.common.primitives. 这里用到的是一个int数组, 所以咱们来看看Ints是否有啥能使的. 哈, 找到拉! (废话, 当然拉, 你事先就知道的嘛!)
- Stringjoined=Ints.join(",",intArray);
总之记住一句话, 当你跟原始类型打交道的时候, 看看primitives包里有没有你合适用的铲子, 铁锹什么的.
呵呵, 注意呀, 这里要扣题了(否则高考是要被扣分地), 所谓天下大势, 合久必分, 分久必合. 有Joiner, 就有Splitter.
要说JDK的String类已经有了split(), 但是 这个函数的设计有点那个, 嗯, 有个性. 你要是经常用它就会发现它经常会给你一些惊喜, 嗯, 东坡居士说: 败亦喜, 所以, 这里就是"失败"的意思, 是你会被String.split()华丽地打败的意思.
当然, 我们还可以用JDK的StringTokenizer, 如果你想炫耀你用java正确写老式循环不出错的技巧的话.
瓜娃的Splitter可以让我们用java 5的enhanced for loop, 而且一般你看着一个它像是做什么的, 它就是做的那个.
比如:
- for(Stringword:Splitter.on(',').split("ajoo,so,handsome!")){
- System.out.println(word);
- }
- //打印出ajoosohandsome!
对了, Splitter还支持正则表达式.
有时候我们不可避免地要实现Comparator, 好做排序之类的事情.
要比较两个整数的时候, 我一度曾经这么写:
return a - b;
多简单啊! 如果a比b大, 无疑这个东西返回正数了.
可惜啊, 现实永远比理想残酷. java的整数不是数学中的整数, 它可能溢出地!
int a = -2000000000; int b = 2000000000; System.out.println(a - b); // prints "294967296"
正确的写法是:
if (a > b) { return 1; } else if (a < b) { return -1; } else { return 0; }
但是, 太麻烦了哇! 好吧, 好吧, 我知道java是一门罗唆的语言艺术, 讲究如何如何啥的, 可是, 可是, 太麻烦了哇! 太麻烦了哇!
在guava里, 对所有原始类型都提供了比较的工具函数来避免这个麻烦. 比如对long, 可以用Longs.compare():
return Longs.compare(a, b);
其它, 自然还有Ints, Shorts, Floats, Doubles等等, 就不骗字数了.
下面看一个简单的model类:
class Person { final String firstName; final String lastName; final int age; }
下面我来实现一个Comparator, 按照名字然后年龄排序:
class PersonComparator implements Comparator<Person> { @Override public int compare(Person p1, Person p2) { int result = p1.firstName.compareTo(p2.firstName); if (result != 0) { return result; } result = p1.lastName.compareTo(p2.lastName); if (result != 0) { return result; } return Ints.compare(p1.age, p2.age); } }
算中规中矩吧? 嗯, 就是觉得有点罗唆 (好啦, 好啦, "java是一门罗唆的语言艺术", 你好罗唆啊!). 要是能直接就说: 按firstName, lastName, age比较就好了.
有一种做法是把这些东西存到一个List<Comparable>然后用一个Comparator<List>来比较:
Ordering.natural().lexicographical().compare( Arrays.asList(p1.firstName, p1.lastName, p1.age), Arrays.asList(p2.firstName, p2.lastName, p2.age));
但是这个东西有点步骤过多, 而且, 自动box那个int, 以及创建两个临时List对象, 都似乎有点过了, 毕竟, Comparator往往是被调用多次来排序很多对象的.
对此, guava有一个相当聪明的解决办法, 用 ComparisonChain:
class PersonComparator implements Comparator<Person> { @Override public int compare(Person p1, Person p2) { return ComparisonChain.start() .compare(p1.firstName, p2.firstName) .compare(p1.lastName, p2.lastName) .compare(p1.age, p2.age) .result(); } }
这个东西的原理哪, 就是利用多态, 当p1.firstName比p2.firstName大的时候, 后续的compare()函数都是空的, 直接返回, 尽量节省计算.
另外, 因为它对所有原始类型都做了重载, 所以也不会付装箱的代价.
(个人意见, 不代表组织认可: 这个start()函数有点别扭. ComparisonChain应该提供静态compare()方法, 这样客户端就可以省去那个讨厌的start())
对了, 刚才在例子中我实在忍不住引用了 Ordering类. 要说这个类不是做了多少了不得的事情, 它的好处是相关的功能都在一个类里面, 好找 (点一下ctrl-space, IDE的自动提示就够用了). 比较常用的几个函数:
比如, 上面如果我lastName可能为null, 然后我要把null列到后面, 我就可以写:
class PersonOrdering extends Ordering<Person> { @Override public int compare(Person p1, Person p2) { return ComparisonChain.start() .compare(p1.firstName, p2.firstName) .compare(p1.lastName, p2.lastName, Ordering.<Person>natural().nullsLast()) .compare(p1.age, p2.age) .result(); } }
这里, 既然我已经用Ordering了, 我就顺手牵羊把PersonComparator变成PersonOrdering了.
要比较两个整数的时候, 我一度曾经这么写:
- returna-b;
多简单啊! 如果a比b大, 无疑这个东西返回正数了.
可惜啊, 现实永远比理想残酷. java的整数不是数学中的整数, 它可能溢出地!
- inta=-2000000000;
- intb=2000000000;
- System.out.println(a-b);
- //prints"294967296"
正确的写法是:
- if(a>b){
- return1;
- }elseif(a<b){
- return-1;
- }else{
- return0;
- }
但是, 太麻烦了哇! 好吧, 好吧, 我知道java是一门罗唆的语言艺术, 讲究如何如何啥的, 可是, 可是, 太麻烦了哇! 太麻烦了哇!
在guava里, 对所有原始类型都提供了比较的工具函数来避免这个麻烦. 比如对long, 可以用Longs.compare():
- returnLongs.compare(a,b);
其它, 自然还有Ints, Shorts, Floats, Doubles等等, 就不骗字数了.
下面看一个简单的model类:
- classPerson{
- finalStringfirstName;
- finalStringlastName;
- finalintage;
- }
下面我来实现一个Comparator, 按照名字然后年龄排序:
- classPersonComparatorimplementsComparator<Person>{
- @Overridepublicintcompare(Personp1,Personp2){
- intresult=p1.firstName.compareTo(p2.firstName);
- if(result!=0){
- returnresult;
- }
- result=p1.lastName.compareTo(p2.lastName);
- if(result!=0){
- returnresult;
- }
- returnInts.compare(p1.age,p2.age);
- }
- }
算中规中矩吧? 嗯, 就是觉得有点罗唆 (好啦, 好啦, "java是一门罗唆的语言艺术", 你好罗唆啊!). 要是能直接就说: 按firstName, lastName, age比较就好了.
有一种做法是把这些东西存到一个List<Comparable>然后用一个Comparator<List>来比较:
- Ordering.natural().lexicographical().compare(
- Arrays.asList(p1.firstName,p1.lastName,p1.age),
- Arrays.asList(p2.firstName,p2.lastName,p2.age));
但是这个东西有点步骤过多, 而且, 自动box那个int, 以及创建两个临时List对象, 都似乎有点过了, 毕竟, Comparator往往是被调用多次来排序很多对象的.
对此, guava有一个相当聪明的解决办法, 用 ComparisonChain:
- classPersonComparatorimplementsComparator<Person>{
- @Overridepublicintcompare(Personp1,Personp2){
- returnComparisonChain.start()
- .compare(p1.firstName,p2.firstName)
- .compare(p1.lastName,p2.lastName)
- .compare(p1.age,p2.age)
- .result();
- }
- }
这个东西的原理哪, 就是利用多态, 当p1.firstName比p2.firstName大的时候, 后续的compare()函数都是空的, 直接返回, 尽量节省计算.
另外, 因为它对所有原始类型都做了重载, 所以也不会付装箱的代价.
(个人意见, 不代表组织认可: 这个start()函数有点别扭. ComparisonChain应该提供静态compare()方法, 这样客户端就可以省去那个讨厌的start())
对了, 刚才在例子中我实在忍不住引用了 Ordering类. 要说这个类不是做了多少了不得的事情, 它的好处是相关的功能都在一个类里面, 好找 (点一下ctrl-space, IDE的自动提示就够用了). 比较常用的几个函数:
- natural(): 比较两个Comparable.
- reverse(): 把当前ordering反过来, 大的变小, 小的变大.
- compound(): 如果当前ordering比较结果是平局, 用另外一个Comparator做加时赛.
- nullsFirst(): 把null当作最小的, 排在前面.
- nullsLast(): null最大.
- binarySearch(): 根据当前ordering在排序列表里二分查找.
比如, 上面如果我lastName可能为null, 然后我要把null列到后面, 我就可以写:
- classPersonOrderingextendsOrdering<Person>{
- @Overridepublicintcompare(Personp1,Personp2){
- returnComparisonChain.start()
- .compare(p1.firstName,p2.firstName)
- .compare(p1.lastName,p2.lastName,Ordering.<Person>natural().nullsLast())
- .compare(p1.age,p2.age)
- .result();
- }
- }
这里, 既然我已经用Ordering了, 我就顺手牵羊把PersonComparator变成PersonOrdering了.
为人父母, 一个比较纠结的事情, 就是到底怎么保护那个啥也不懂的小家伙. 如果护着她太紧了, 会不会让她失去和外部接触, 学习的机会, 变得孤僻, 依赖性强? 如果保护不利, 被人欺负了, 或者甚至被拐跑了, 后悔药没地方买呀. 到底要不要告诉她外面有很多坏人呐?
唉. 不自寻烦恼了. 埋头写代码!
不过, 嗯, 这个好像我写代码怎么也在想着类似的东西? "要不要检查这个参数是不是null?", "要不要判断当前状态对不对?"
一个好编程习惯是尽量不要用null, 除非特殊情况, 参数都不允许是null. 而那些特殊的需要null的场合, 用 @Nullable标注出来.
一般情况下, 如果你马上就会调用girl.kiss(), 这个girl如果是null的话, 你马上就能即时得到一个NullPointerException, jvm已经帮你做了null检查. 但是有时候, 比如对构造函数来说, 参数不是马上使用, 而是存在成员变量里面, 以后再用. 这时候检查就很重要了. 否则, 如果客户不小心传递一个null, 错误就要延后到可能很久以后才会发现了.
最直观的检查就是:
if (girl == null) { throw new NullPointerException("谁这么缺德, 给我一个山寨美眉呀?!"); }
但是这有点繁琐, 瓜娃有一个工具, 叫 Preconditions.
用它, 上面的代码可以简化成:
Preconditions.checkNotNull(girl, "谁这么缺德, 给我一个山寨美眉呀?!");
Preconditions还有两个常用的检查: checkArgument()和checkState(). 用法大同小异. 一个用来检查参数, 一个用来检查对象状态. 一个抛IllegalArgumentException, 一个抛IllegalStateException.
Preconditions这些工具函数有一个潜在的问题: 当你写测试同时用测试覆盖工具的时候, 如果你用传统的if-else, 测试覆盖工具会告诉你如果你忘记了测试那个错误情况. 而用了Preconditions, 这些工具就被骗了, 只会傻乎乎地报告100%覆盖.
唉. 不自寻烦恼了. 埋头写代码!
不过, 嗯, 这个好像我写代码怎么也在想着类似的东西? "要不要检查这个参数是不是null?", "要不要判断当前状态对不对?"
一个好编程习惯是尽量不要用null, 除非特殊情况, 参数都不允许是null. 而那些特殊的需要null的场合, 用 @Nullable标注出来.
一般情况下, 如果你马上就会调用girl.kiss(), 这个girl如果是null的话, 你马上就能即时得到一个NullPointerException, jvm已经帮你做了null检查. 但是有时候, 比如对构造函数来说, 参数不是马上使用, 而是存在成员变量里面, 以后再用. 这时候检查就很重要了. 否则, 如果客户不小心传递一个null, 错误就要延后到可能很久以后才会发现了.
最直观的检查就是:
- if(girl==null){
- thrownewNullPointerException("谁这么缺德,给我一个山寨美眉呀?!");
- }
但是这有点繁琐, 瓜娃有一个工具, 叫 Preconditions.
用它, 上面的代码可以简化成:
- Preconditions.checkNotNull(girl,"谁这么缺德,给我一个山寨美眉呀?!");
Preconditions还有两个常用的检查: checkArgument()和checkState(). 用法大同小异. 一个用来检查参数, 一个用来检查对象状态. 一个抛IllegalArgumentException, 一个抛IllegalStateException.
Preconditions这些工具函数有一个潜在的问题: 当你写测试同时用测试覆盖工具的时候, 如果你用传统的if-else, 测试覆盖工具会告诉你如果你忘记了测试那个错误情况. 而用了Preconditions, 这些工具就被骗了, 只会傻乎乎地报告100%覆盖.