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