java走马观花续

另外一对我经常需要用到的, 是 ResourcesFiles.

一般来说, 如果我有一大块的文本, 或者properties文件, xml, 我会选择把它们存到一个文本文件里, 放到jar里. 然后在运行时, 把这个文件当作资源读出. 这样做比直接存到文件系统里面的好处, 在于它对部署方式不敏感, 不管我的代码如何部署, 我只需要从ClassLoader找到我要的文件就好了.

我可以直接用ClassLoader来寻找我的资源, 象这样:
Java代码 复制代码  收藏代码
  1. URL url = getClass().getClassLoader().getResource("mypackage/myfile.txt");   
  2. if (url == null) {   
  3.   throw new IOException("mypackage/myfile.txt 没找到!");   
  4. }   
  5. ...  
URL url = getClass().getClassLoader().getResource("mypackage/myfile.txt");
if (url == null) {
  throw new IOException("mypackage/myfile.txt 没找到!");
}
...

把要找的资源名包括在错误信息中很重要. 很多时候, 或者是部署的问题, 或者是程序错误, getResource()会返回null. 如果你不包括这个资源名, 甚至不做这个null检查就直接用这个url变量, 程序会抛出异常, 但是查找错误相当不方便, 无谓浪费时间.

我也可以用Resources来更方便地做同样的事:
Java代码 复制代码  收藏代码
  1. URL url = Resources.getResource("mypackage/myfile.txt");  
URL url = Resources.getResource("mypackage/myfile.txt");

Resources.getResource()自动包括了错误检查.

得到了这个URL, 我就可以从里面读出内容. 如果它是一个文本文件, 我可以用Resources.toString():
Java代码 复制代码  收藏代码
  1. String content = Resources.toString(url);  
String content = Resources.toString(url);


或者如果我需要按行读出:
Java代码 复制代码  收藏代码
  1. String[] lines = Resources.readLines(url, Charsets.UTF_8);  
String[] lines = Resources.readLines(url, Charsets.UTF_8);


这里, 要给 com.google.common.base.Charsets做个广告. 它提供了一些标准的所有平台都支持的Charset常量, 非常非常有用!

而如果是一个二进制文件, 我可以用Resources.toByteArray():
Java代码 复制代码  收藏代码
  1. byte[] content = Resources.toByteArray(url);  
byte[] content = Resources.toByteArray(url);


相比之下, Files相对不是那么常用. 但是有时候当你需要操作文件的时候, 它提供的工具函数还是很顶事的. 比如, 你可以同样地从文本或二进制文件读取字符串或者字节:
Java代码 复制代码  收藏代码
  1. String content = Files.toString(textFile, Charsets.UTF_8);   
  2. byte[] byteArray = Files.toByteArray(binaryFile);  
String content = Files.toString(textFile, Charsets.UTF_8);
byte[] byteArray = Files.toByteArray(binaryFile);

可以向文件里写内容:
Java代码 复制代码  收藏代码
  1. Files.write(content, textFile, Charsets.ASCII);   
  2. Files.write(byteArray, binaryFile);  
Files.write(content, textFile, Charsets.ASCII);
Files.write(byteArray, binaryFile);

也可以拷贝文件:
Java代码 复制代码  收藏代码
  1. Files.copy(fromFile, toFile);  
Files.copy(fromFile, toFile);

具体的大家看文档吧, 应该很简单地.


用瓜娃以前, 每当遇到把一串东西用逗号分割打印出来的需求, 俺都有点烦. 这算是挺简单无聊的活, 但是每次写起来那代码总是觉得象56k猫拨号上网那么让人磨牙:

Java代码 复制代码  收藏代码
  1. StringBuilder builder = new StringBuilder();   
  2. int first = true;   
  3. for (String s : strings) {   
  4.   if (first) {   
  5.     first = false;   
  6.   } else {   
  7.     builder.append(',');   
  8.   }   
  9.   builder.append(s);   
  10. }   
  11. 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用起来就顺手顺气多了. 不多废话, 看代码:

Java代码 复制代码  收藏代码
  1. String joined = Joiner.on(',').join(strings);  
String joined = Joiner.on(',').join(strings);


要是需要对null做特殊处理, 比如打印"NA":
Java代码 复制代码  收藏代码
  1. String joined = Joiner.on(',').useForNull("NA").join(strings);  
String joined = Joiner.on(',').useForNull("NA").join(strings);


或者干脆把null滤掉:
Java代码 复制代码  收藏代码
  1. String joined = Joiner.on(',').skipNulls().join(strings);  
String joined = Joiner.on(',').skipNulls().join(strings);


还有一些其它的定制功能, 自己看javadoc吧.

如果你所有的不是一个对象数组或者Iterable, 而是一个int[], Joiner不提供直接支持. 但是瓜娃有另外一个相当有用的包: com.google.common.primitives. 这里用到的是一个int数组, 所以咱们来看看Ints是否有啥能使的. 哈, 找到拉! (废话, 当然拉, 你事先就知道的嘛!)
Java代码 复制代码  收藏代码
  1. 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, 而且一般你看着一个它像是做什么的, 它就是做的那个.
比如:

Java代码 复制代码  收藏代码
  1. for (String word : Splitter.on(',').split("ajoo,so,handsome!")) {   
  2.   System.out.println(word);   
  3. }   
  4. // 打印出 ajoo so handsome!  
for (String word : Splitter.on(',').split("ajoo,so,handsome!")) {
  System.out.println(word);
}
// 打印出 ajoo so handsome!


对了, Splitter还支持正则表达式.


有时候我们不可避免地要实现Comparator, 好做排序之类的事情.

要比较两个整数的时候, 我一度曾经这么写:

Java代码 复制代码  收藏代码
  1. return a - b;  
return a - b;


多简单啊! 如果a比b大, 无疑这个东西返回正数了.

可惜啊, 现实永远比理想残酷. java的整数不是数学中的整数, 它可能溢出地!
Java代码 复制代码  收藏代码
  1. int a = -2000000000;   
  2. int b =  2000000000;   
  3. System.out.println(a - b);   
  4. // prints "294967296"  
int a = -2000000000;
int b =  2000000000;
System.out.println(a - b);
// prints "294967296"


正确的写法是:
Java代码 复制代码  收藏代码
  1. if (a > b) {   
  2.   return 1;   
  3. else if (a < b) {   
  4.   return -1;   
  5. else {   
  6.   return 0;   
  7. }  
if (a > b) {
  return 1;
} else if (a < b) {
  return -1;
} else {
  return 0;
}

但是, 太麻烦了哇! 好吧, 好吧, 我知道java是一门罗唆的语言艺术, 讲究如何如何啥的, 可是, 可是, 太麻烦了哇! 太麻烦了哇!

在guava里, 对所有原始类型都提供了比较的工具函数来避免这个麻烦. 比如对long, 可以用Longs.compare():
Java代码 复制代码  收藏代码
  1. return Longs.compare(a, b);  
return Longs.compare(a, b);


其它, 自然还有Ints, Shorts, Floats, Doubles等等, 就不骗字数了.

下面看一个简单的model类:
Java代码 复制代码  收藏代码
  1. class Person {   
  2.   final String firstName;   
  3.   final String lastName;   
  4.   final int age;   
  5. }  
class Person {
  final String firstName;
  final String lastName;
  final int age;
}


下面我来实现一个Comparator, 按照名字然后年龄排序:
Java代码 复制代码  收藏代码
  1. class PersonComparator implements Comparator<Person> {   
  2.   @Override public int compare(Person p1, Person p2) {   
  3.     int result = p1.firstName.compareTo(p2.firstName);   
  4.     if (result != 0) {   
  5.       return result;   
  6.     }   
  7.     result = p1.lastName.compareTo(p2.lastName);   
  8.     if (result != 0) {   
  9.       return result;   
  10.     }   
  11.     return Ints.compare(p1.age, p2.age);   
  12.   }   
  13. }  
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>来比较:
Java代码 复制代码  收藏代码
  1. Ordering.natural().lexicographical().compare(   
  2.     Arrays.asList(p1.firstName, p1.lastName, p1.age),   
  3.     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:
Java代码 复制代码  收藏代码
  1. class PersonComparator implements Comparator<Person> {   
  2.   @Override public int compare(Person p1, Person p2) {   
  3.     return ComparisonChain.start()   
  4.         .compare(p1.firstName, p2.firstName)   
  5.         .compare(p1.lastName, p2.lastName)   
  6.         .compare(p1.age, p2.age)   
  7.         .result();   
  8.   }   
  9. }  
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列到后面, 我就可以写:

Java代码 复制代码  收藏代码
  1. class PersonOrdering extends Ordering<Person> {   
  2.   @Override public int compare(Person p1, Person p2) {   
  3.     return ComparisonChain.start()   
  4.         .compare(p1.firstName, p2.firstName)   
  5.         .compare(p1.lastName, p2.lastName, Ordering.<Person>natural().nullsLast())   
  6.         .compare(p1.age, p2.age)   
  7.         .result();   
  8.   }   
  9. }  
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, 错误就要延后到可能很久以后才会发现了.

最直观的检查就是:
Java代码 复制代码  收藏代码
  1. if (girl == null) {   
  2.   throw new NullPointerException("谁这么缺德, 给我一个山寨美眉呀?!");   
  3. }  
if (girl == null) {
  throw new NullPointerException("谁这么缺德, 给我一个山寨美眉呀?!");
}


但是这有点繁琐, 瓜娃有一个工具, 叫 Preconditions.

用它, 上面的代码可以简化成:
Java代码 复制代码  收藏代码
  1. Preconditions.checkNotNull(girl, "谁这么缺德, 给我一个山寨美眉呀?!");  
Preconditions.checkNotNull(girl, "谁这么缺德, 给我一个山寨美眉呀?!");


Preconditions还有两个常用的检查: checkArgument()和checkState(). 用法大同小异. 一个用来检查参数, 一个用来检查对象状态. 一个抛IllegalArgumentException, 一个抛IllegalStateException.

Preconditions这些工具函数有一个潜在的问题: 当你写测试同时用测试覆盖工具的时候, 如果你用传统的if-else, 测试覆盖工具会告诉你如果你忘记了测试那个错误情况. 而用了Preconditions, 这些工具就被骗了, 只会傻乎乎地报告100%覆盖.





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值