使用Guava处理Strings
不管你更喜欢使用哪种语言,程序员都必须和字符串打交道,有时候这是无趣的并且容易出错的。 有的时候,我们需要从文件或者数据库的表中读取数据,并格式化这些数据。 或者为了满足业务需求我们需要排序等操作,幸运的是,Guava给我们提供一些非常有用的类,可以帮助我们处理Strings类更加容易.这些类是:
- CharMatcher
- Charsets
- Strings
下面让我们看一下在我们的代码中怎么样是使用这些类。
使用Charsets类
在Java平台下有个6个标准的字符集,下面是和字符集相关的非常常见的例子:byte[] bytes = someString.getBytes();
但是上面的这个代码片段是有问题的,在获取byte的时候没有指定字符集编码,那么默认就会去系统的默认编码,但是有可能系统默认的编码不是你想要的编码。因此一般的最佳实践是按照如下的方式获取byte数据:
`try{
bytes = "foobarbaz".getBytes("UTF-8");
}catch (UnsupportedEncodingException e){
//This really can't happen UTF-8 must be supported
}`
上面的例子中依然有两个错误的地方:
- UTF-8 在Java平台上是肯定会被支持的,所以基本上是不可能会抛出UnsupportedEncodingException异常的。
- 我们是用'UTF-8'这种方式指定编码的,有可能会出现输入错误的情况,例如不小心输入成'UF-8'.
上面的这两个问题正是CharSets可以解决。 Charsets类 对于这6个字符集提供了一个 final的静态的引用。 使用Charsets,上面的那段代码可以简化为如下:byte[] bytes2 = "foobarbaz".getBytes(Charsets.UTF_8);
不过在Java7中已经提供了类似的功能,在Java 7中的类名是: StandardCharsets.
使用Stings类
Strings提供了一些工具方法区处理字符串,你是否写过下面的代码:
`StringBuilder builder = new StringBuilder("foo");
char c = 'x';
for(int i=0; i<3; i++){
builder.append(c);
}
return builder.toString();`
在上面的这段代码中,我们使用了6行代码完成了这么一个简单的功能,现在利用Strings 我们可以简单的用一行代码就可以完成:Strings.padEnd("foo",6,'x');
这里面有个重要的提示,这里面的6不是指将x循环的增加6遍,而是指将'foo'补充成6位长度。 如果这时候 'fooxxxx'已经是6位长度了,那么这段代码将不起任何作用. 同样 有一个'padStart'方法,它的作用和'padEnd'是相似的,只不过是在字符串前面补充指定的字符。补充到指定的长度。
在处理null值方面,Strings有三个比较好用的方法:
- nullToEmpty 这个方法获取一个字符串参数,如果这个参数不为null或长度大于0,那么直接返回该参数,否则返回"" 空字符串
- emptyToNull 这个方法和nulltoEmpty差不多,只不过如果参数是null或则长度不大于0 那么会返回 null
- isNullOrEmpty 当传入的参数书null或则长度为0 那么这个方法的返回值就是 true。
最佳实践: 在讲String作为参数传递时,最好先使用nullToEmpty对字符串做一下处理。
使用CharMatcher类
Charmatcher 类提供了处理各种characters的方法,能够非常容易的格式化处理文本。 下面的例子展示的是,使用CharMatcher将一个多行的字符串转换成以' '分隔的单行字符串CharMatcher.BREAKING_WHITESPACE.replaceFrom(stringWithLinebreaks,' ');
replaceFrom方法不仅可以接受一个char类型参数还可以接受一个charsequence参数。
为了将连续的多个 tab或者空格 压缩成一个空格,我们可以使用下面的代码:
`@Test
public void testRemoveWhiteSpace(){
String tabsAndSpaces = "String with spaces and
tabs";
String expected = "String with spaces and tabs";
String scrubbed = CharMatcher.WHITESPACE.
collapseFrom(tabsAndSpaces,' ');
assertThat(scrubbed,is(expected));
}`
上面的test中,我们看到我用一行代码就将多个tab及多个空变成一个空格。 但是上面的这个例子只是在特殊的情况下有效,如果我们想将字符串前面的空格都去掉,只保留字符中间的一个空格呢? 尽管需求这么变态,使用 trimAndCollapseFrom方法就可以实现,示例如下:
`@Test
public void testTrimRemoveWhiteSpace(){
String tabsAndSpaces = " String with spaces and
tabs";
String expected = "String with spaces and tabs";
String scrubbed = CharMatcher.WHITESPACE.
trimAndCollapseFrom(tabsAndSpaces,' ');
assertThat(scrubbed,is(expected));
}`
上面的例子我们依然讨论了如何将多个tab或空格变成一个空格,现在让我们来看一下其他的使用方式,
- 将符合条件的字符提取出来,example:
`@Test
public void testRetainFrom(){
String lettersAndNumbers = ""foo989yxbar234"";
String expected = ""989234"";
String retained = CharMatcher.JAVA_DIGIT.
retainFrom(lettersAndNumbers);
assertThat(expected,is(retained));
}`
上面的例子中,我们将所有的数字都提取了出来,仅仅是使用了一行代码哦!
- 在讲其他的类的使用之前,我们来看一个大招,example:
`String lettersAndNumbers = "fo o989yxbar234";
String expected = " 989234";
CharMatcher.JAVA_DIGIT.or(CharMatcher.WHITESPACE).retainFrom(lettersAndNumbers);`
上面的例子中既可以提取数字也可以提取空白字符
由此可见,CharMatcher在处理Java字符串方面是非常好用并且非常好用的方法。
使用 Preconditions类
Preconditions类里面包含了一系列的静态方法,用来验证我们期望的值。 Preconfitions 是非常重要,因为他可以保证我们的预期和实际发生的是一致的。 一旦预期和实际运行的记过不一致,我们立马就可以得到反馈,因此使用preconditions 可以确保我们的代码在我们预期的情况下运行着。 另外对于debug也是非常有好处的。
- 在使用preconfitions 之前我们一定写过下面的代码:
`if(someObj == null){
throw new IllegalArgumentException(" someObj must
not be null");
}`
使用preconfitions 后,我们可以如下的方法进行检查:checkNotNull(someObj,"someObj must not be null");
其中preconfitions 是可以静态导入的 static imports
下面我们来看一个例子:
`public class PreconditionExample {
private String label;
private int[] values = new int[5];
private int currentIndex;
public PreconditionExample(String label) {
//returns value of object if not null
this.label = checkNotNull(label,"Label can''t be null");
}
public void updateCurrentIndexValue(int index, int valueToSet) {
//Check index valid first
this.currentIndex = checkElementIndex(index, values.length,
"Index out of bounds for values");
//Validate valueToSet
checkArgument(valueToSet <= 100,"Value can't be more than
100");
values[this.currentIndex] = valueToSet;
}
public void doOperation(){
checkState(validateObjectState(),"Can't perform operation");
}
private boolean validateObjectState(){
return this.label.equalsIgnoreCase("open") && values[this.
currentIndex]==10;
}
}`
从上面的例子中,我们总结一下这个几个方法的使用:
- checkNotNull(T object,Object message) : 如果object是null 那么就会抛出一个NullPointException异常,否则返回这个Object.
- checkElementIndex(int index,int size,Object message): 这个方法中 index 参数是你想要访问的位置,size 是array,list,或者string的长度, 如果 index的范围大于size 那么将会抛出 IndexOutOfBoundexception 否则返回index的值
- checkArgument(Boolean expression,Object message): 根据expression表达式算出来的值做判断,如果值为false那么就会抛出IllegalArgumentException异常
- checkState(Boolean expression,Object message): 作用和checkArgument一样,代码实现也是一样,使用场景不一致,checkArgument 一般用来检查输入参数
使用Object utilities类
这一个小章节中,我们将覆盖 空值检查,生成 toString,hashCode 等方法。 我们还将学习一个新的类帮助我们从实现Comparable接口痛苦摆脱出来。
使用 toString方法
toString 方法在我们debug或者查日志的时候是十分方便的,但是写一个toString方法却是一个比较无趣的事情,但是使用Objects的toStringHelper方法你可以很方便的实现,下面我们看一个例子:
`public class Book implements Comparable {
private Person author;
private String title;
private String publisher;
private String isbn;
private double price;
....
public String toString() {
return Objects.toStringHelper(this)
.omitNullValues()
.add("title", title)
.add("author", author)
.add("publisher", publisher)
.add("price",price)
.add("isbn", isbn).toString();
}
`
让我们来探究一下这段代码的意思
- 首先我们传递一个Book的引用,创建一个ToStringHelper对象
- 调用omitNullValues 排除属性是空值的
- 分别调用 add 方法将 'label' 和对应的值
不过貌似这个还没有 apache 下面的 ToStringBuilder好用
检查空值
firstNonNull 方法接受两个参数,如果参数是空,就返回设置的默认值,否则指定的第一次参数,如果传入的两个参数都是null 那么就会抛出NullPointException异常
生成HashCode 方法
方法很简单,如下:
`Objects.hashCode(Object...);
`
实现CompareTo接口
我们任然使用上面的Book类,下面是一个实现CompareTo接口典型实现:
`public int compareTo(Book o) {
int result = this.title.compareTo(o.getTitle());
if (result != 0) {
return result;
}
result = this.author.compareTo(o.getAuthor());
if (result != 0) {
return result;
}
result = this.publisher.compareTo(o.getPublisher());
if(result !=0 ) {
return result;
}
return this.isbn.compareTo(o.getIsbn());
}`
下面我们来看一下使用ComparisonChain的方式实现compareTo接口:
`public int compareTo(Book o) {
return ComparisonChain.start()
.compare(this.title, o.getTitle())
.compare(this.author, o.getAuthor())
.compare(this.publisher, o.getPublisher())
.compare(this.isbn, o.getIsbn())
.compare(this.price, o.getPrice())
.result();
}`
上面的这个例子中,使用ComparisonChain的方式更加简单,也更加易读,只要其中的一个比较不为0,那么就直接返回。
总结
在这一章中我们已经介绍了很多。
- 我们学习了怎样使用Joiner,Splitter,MapJioner,MapSplitter,Charsets,CharMatcher,Strings 让我们在处理格式化的字符串时更加容易
- 我们学习了使用Preconfitions类让我们的代码更加强壮,学习使用了 Objects类的 toString,hashCode 方法让我们的debug更加容易,我们还学习了ComparisonChain类,让我们实现CompareTo方式更加容易
- 在下一章,我们将学习怎样使用guava的函数式变成,让我们的代码更加清晰和强壮。我们将涉及两个接口: Function 和 Predicate 接口