发布时间:2023-04-10 11:00
在我们的开发中经常会用到Guava中的一些功能。但是我们所使用到的只是Guava API中的小的可怜的一个子集。我们大家一起来发掘一下Guava中更多的一些功能。
这是在我们代码中出现频率比较高的一个功能。经常需要将几个字符串,或者字符串数组、列表之类的东西,拼接成一个以指定符号分隔各个元素的字符串,比如要将一个用List保存的字符集合拼起来作为SQL语句的条件,在知道Joiner之前我们会这样做。
ArrayList conditions = new ArrayList();
conditions.add("condition1");
conditions.add("condition2");
conditions.add("condition3");
private String buildCondition(ArrayList conditions) {
StringBuilder sb = new StringBuilder();
for (String condition : conditions) {
sb.append(condition);
sb.append(" or ");
}
int index = sb.lastIndexOf(" or ");
return index > 0 ? sb.substring(0, index) : sb.toString();
} // condition1 or condition2 or condition3
基本上会手写循环去实现,代码瞬间变得丑陋起来。并且循环完了还得删除最后一个多余的or。
使用Guava工具,我们能够轻而易举的完成字符串拼接这一简单任务。借助 Joiner 类,代码瞬间变得优雅起来。
Joiner.on(" or ").join(conditions);
被拼接的对象集,可以是硬编码的少数几个对象,可以是实现了 Iterable 接口的集合,也可以是迭代器对象。
除了返回一个拼接过的字符串,Joiner 还可以在实现了 Appendable 接口的对象所维护的内容的末尾,追加字符串拼接的结果。
StringBuilder sb = new StringBuilder("result:");
Joiner.on("#").appendTo(sb, 1, 2, 3);
System.out.println(sb); //result:1#2#3
我们看下面这个例子:
Joiner.on("#").join(1, null, 3)
如果传入的对象中包含空指针,会直接抛出空指针异常。Joiner 提供了两个方法,让我们能够优雅的处理待拼接集合中的空指针。
如果我们希望忽略空指针,那么可以调用 skipNulls方法,得到一个会跳过空指针的 Joiner 实例。如果希望将空指针变为某个指定的值,那么可以调用 useForNull 方法,指定用来替换空指针的字符串。
Joiner.on("#").skipNulls().join(1, null, 3); //1#3
Joiner.on("#").useForNull("").join(1, null, 3); //1##3
MapJoiner 是 Joiner 的内部静态类,用于帮助将 Map 对象拼接成字符串。
Map test = new HashMap();
test.put(1, 2);
test.put(3, 4);
Joiner.on("#").withKeyValueSeparator("=").join(test); //1=2#3=4
withKeyValueSeparator 方法指定了键与值的分隔符,同时返回一个 MapJoiner 实例。
Joiner.on("#").withKeyValueSeparator("=").join(ImmutableMap.of(1, 2, 3, 4)); //1=2#3=4
源码来自Guava 18.0。Joiner类的源码一共458行。大部分都是注释。
Joiner 只能通过 Joiner.on 函数来初始化,它的构造方法是私有的。
/**
* Returns a joiner which automatically places {@code separator} between consecutive elements.
*/
public static Joiner on(String separator) {
return new Joiner(separator);
}
/**
* Returns a joiner which automatically places {@code separator} between consecutive elements.
*/
public static Joiner on(char separator) {
return new Joiner(String.valueOf(separator));
}
整个 Joiner 类最核心的函数莫过于 appendTo(A, Iterator>),一切的字符串拼接操作,最后都会调用到这个函数。这就是所谓的全功能函数,其他的一切 appendTo 只不过是它的重载,一切的join不过是它和它的重载的封装。
/**
* Appends the string representation of each of {@code parts}, using the previously configured
* separator between each, to {@code appendable}.
*
* @since 11.0
*/
public A appendTo(A appendable, Iterator> parts) throws IOException {
checkNotNull(appendable);
if (parts.hasNext()) {
appendable.append(toString(parts.next()));
while (parts.hasNext()) {
appendable.append(separator);
appendable.append(toString(parts.next()));
}
}
return appendable;
}
这段代码的第一个技巧是使用 if 和 while 来实现了比较优雅的分隔符拼接,避免了在末尾插入分隔符的尴尬;第二个技巧是使用了自定义的 toString 方法而不是 Object#toString 来将对象序列化成字符串,为后续的各种空指针保护开了方便之门。
来看一个比较有意思的 appendTo 重载。
public final StringBuilder appendTo(StringBuilder builder, Iterator> parts) {
try {
this.appendTo((Appendable)builder, (Iterator)parts);
return builder;
} catch (IOException var4) {
throw new AssertionError(var4);
}
}
在 Appendable 接口中,append 方法是会抛出 IOException 的。然而 StringBuilder 虽然实现了 Appendable,但是它覆盖实现的 append 方法却是不抛出 IOException 的。于是就出现了明知不可能抛异常,却又不得不去捕获异常的尴尬。
这里的异常处理手法十分机智,异常变量命名为 impossible,我们一看就明白这里是不会抛出 IOException 的。但是如果 catch 块里面什么都不做又好像不合适,于是抛出一个 AssertionError,表示对于这里不抛异常的断言失败了。
另一个比较有意思的 appendTo 重载是关于可变长参数。
public final A appendTo(A appendable, @Nullable Object first, @Nullable Object second, Object... rest) throws IOException {
return this.appendTo((Appendable)appendable, (Iterable)iterable(first, second, rest));
}
注意到这里的 iterable 方法,它把两个变量和一个数组变成了一个实现了Iterable 接口的集合,非常精妙的实现!
private static Iterable
要想看明白这段代码,需要熟悉AbstractList类中迭代器的实现。迭代器内部维护着一个游标,cursor。迭代器的两大关键操作,hasNext 判断是否还有没遍历的元素,next 获取下一个元素,它们的实现是这样的。
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
hasNext 中关键的函数调用是size方法,获取集合的大小。next 方法中关键的函数调用是get方法,获取第 i 个元素。Guava 的实现返回了一个被覆盖了 size 和 get 方法的 AbstractList,巧妙的复用了由编译器生成的数组,避免了新建列表和增加元素的开销。
MapJoiner 实现为 Joiner 的一个静态内部类,它的构造函数和 Joiner 一样也是私有,只能通过 withKeyValueSeparator来生成实例。类似地,MapJoiner 也实现了 appendTo 方法和一系列的重载,还用 join 方法对 appendTo 做了封装。
MapJoiner 整个实现和 Joiner 大同小异,在实现中大量使用 Joiner 的 toString 方法来保证空指针保护行为和初始化时的语义一致。
mysql复习资料_2018计算机二级MYSQL复习资料:将数据装入数据库表
SpringBoot整合 shiro + jwt,并会话共享
Spring生态中如何使用 Druid & Druid 如何初始化数据库连接池
Python在各领域技术应用(地球科学、气象、机器学习、海洋等)
crypto-js-wasm: 当crypto-js遇上WebAssembly
一文带你弄懂C++中的ANSI、Unicode和UTF8三种字符编码
[ROC-RK3568-PC] [Firefly-Android] 10min带你了解ADC的使用