guava之Joiner
2019-10-16 本文已影响0人
生活有方有圆
Joiner相当于spliter的反操作,可以将数组或者集合等可遍历的数据转换成使用分隔符连接的字符串
JoinerAPI介绍
public static Joiner on(char separator); //构造一个使用给定分隔符的Joiner对象
public final String join(Iterable<?> parts); //使用给定数据结合事先指定的分隔符,生成连接后的字符串
public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts);//添加给定的可遍历数据到给定的StringBuilder
Joiner源码
其实Joiner除了上述介绍的api之外,还有很多重载api和其他的性质的api
首先介绍下join及其重载
public final String join(Iterable<?> parts) {
return join(parts.iterator());
}
public final String join(Iterator<?> parts) {
return appendTo(new StringBuilder(), parts).toString();
}
public final String join(Object[] parts) {
return join(Arrays.asList(parts));
}
public final String join(@Nullable Object first, @Nullable Object second, Object... rest) {
return join(iterable(first, second, rest));
}
咱们看到join有4个重载函数,其中可传入集合、数组、还有一个个单独的对象。
但他们最终都转换为接受迭代器的重载函数
这里咱们重点看下String join(@Nullable Object first, @Nullable Object second, Object... rest)函数
其中使用的一个构造Iterable的方法
private static Iterable<Object> iterable(
final Object first, final Object second, final Object[] rest) {
checkNotNull(rest);
return new AbstractList<Object>() {
@Override
public int size() {
return rest.length + 2;
}
@Override
public Object get(int index) {
switch (index) {
case 0:
return first;
case 1:
return second;
default:
return rest[index - 2];
}
}
};
}
有趣点地方是:
1、这个函数在参数中列出了first、second等来说明这个是一个按顺序输入来进行连接的。
2、返回了一个匿名类AbstractList,很好奇为什么返回的是AbstractList
因为AbstractList是ArrayList和linkedList的祖类
上面看到join其最终的实现是通过调用appendTo,下面来分析下appendTo
@CanIgnoreReturnValue
public final StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) {
return appendTo(builder, parts.iterator());
}
@CanIgnoreReturnValue
public final StringBuilder appendTo(StringBuilder builder, Iterator<?> parts) {
try {
appendTo((Appendable) builder, parts);
} catch (IOException impossible) {
throw new AssertionError(impossible);
}
return builder;
}
@CanIgnoreReturnValue
public final StringBuilder appendTo(StringBuilder builder, Object[] parts) {
return appendTo(builder, Arrays.asList(parts));
}
@CanIgnoreReturnValue
public final StringBuilder appendTo(
StringBuilder builder, @Nullable Object first, @Nullable Object second, Object... rest) {
return appendTo(builder, iterable(first, second, rest));
}
看到上面这个4个重载函数,其实重载的思路跟上面join的是一样的。也是把可遍历的对象全部列举重载。
但是咱们发现,上面4个重载中还是没有最终的实现
因为最终调用的是另外4个带范型的重载
@CanIgnoreReturnValue
public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts) throws IOException {
return appendTo(appendable, parts.iterator());
}
@CanIgnoreReturnValue
public <A extends Appendable> 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;
}
@CanIgnoreReturnValue
public final <A extends Appendable> A appendTo(A appendable, Object[] parts) throws IOException {
return appendTo(appendable, Arrays.asList(parts));
}
@CanIgnoreReturnValue
public final <A extends Appendable> A appendTo(
A appendable, @Nullable Object first, @Nullable Object second, Object... rest)
throws IOException {
return appendTo(appendable, iterable(first, second, rest));
}
这回终于看到具体的实现内容了,其实这个算法也是比较简单。就是对象拼接一个分隔符,因为添加一个对象再添加分割符会导致最后多一个分隔符,要去掉。
所以这个作者先添加一个对象,然后顺序是先分隔符,再对象这样的顺序。
从这里意识到有时候思想也要从多方向出发。实在没有其他方向,就按照外国人思路方向进行
仔细观察上面的代码,会发现
1、为什么appendTo要多加一组StringBuilder的重载呢?
大家仔细观察,会发现appendTo(StringBuilder)的4个重载,返回的值是StringBuilder类型的。
那么join最终是如何取出其自身的字符串呢,就是通过appendTo(StringBuilder).toString方法来的
2、为什么public <A extends Appendable> A appendTo(A appendable, Iterable<?> parts)和public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts)两个函数不用finial进行修饰呢
函数加了finial就不能被子类重写了,作者已经定下规矩,或者说类的框架是定下来了的,重载的是具体的算法。
数据流转后,最终的实现就是在appendTo(A appendable, Iterable<?> parts)或者appendTo(A appendable, Iterator<?> parts)
*3、为什么appendTo(A)等4个重载必须要实现了Appendable接口的类做参数呢
虽然Appendable接口就3个重载函数,看着没实现什么东西,但是其一是Joiner没有自己实现AppendTo的具体方法,而是希望第一个参数自己实现,Joiner只是帮忙添加下分隔符separator。
其二可能是一种规范吧,为什么java定义那么多接口来实现,而不是自己用的时候,随便叫个abc名字,然后让用户自己去实现接口。
在java的世界里,Appendable接口大家去查都知道是做添加用的,很多地方也都用了这个接口,所以接口也算是一种规范吧,而不是说为什么这个接口就3个方法,还要去用这个接口
Joiner还提供了对Null的处理
public Joiner useForNull(final String nullText) {
checkNotNull(nullText);
return new Joiner(this) {
@Override
CharSequence toString(@Nullable Object part) {
return (part == null) ? nullText : Joiner.this.toString(part);
}
@Override
public Joiner useForNull(String nullText) {
throw new UnsupportedOperationException("already specified useForNull");
}
@Override
public Joiner skipNulls() {
throw new UnsupportedOperationException("already specified useForNull");
}
};
}
/**
* Returns a joiner with the same behavior as this joiner, except automatically skipping over any
* provided null elements.
*/
public Joiner skipNulls() {
return new Joiner(this) {
@Override
public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
checkNotNull(appendable, "appendable");
checkNotNull(parts, "parts");
while (parts.hasNext()) {
Object part = parts.next();
if (part != null) {
appendable.append(Joiner.this.toString(part));
break;
}
}
while (parts.hasNext()) {
Object part = parts.next();
if (part != null) {
appendable.append(separator);
appendable.append(Joiner.this.toString(part));
}
}
return appendable;
}
从上面我们看到了useForNull是用其他字符来替代null的,skipNulls是忽略null。
skipNulls比较容易理解,使用匿名类重载了最终的实现方法appendTo(A appendable, Iterator<?> parts)。首先加一个对象,然后再依次使用分隔符加对象的顺序进行连接。不同的是添加了null的过滤操作。
useForNull这个函数也是比较严谨的。
这个方法使用匿名类重写了三个函数,toString、useForNull、skipNulls。
toString是最终替换的切入点。
因为Joiner是链式操作的,也就是说我们也可以把他设置好,然后返回给别人用,那么这个时候,如果已经设置useForNull,自然不希望别人再修改咱们的配置了
Joiner还提供了Map的处理
public MapJoiner withKeyValueSeparator(char keyValueSeparator) {
return withKeyValueSeparator(String.valueOf(keyValueSeparator));
}
public MapJoiner withKeyValueSeparator(String keyValueSeparator) {
return new MapJoiner(this, keyValueSeparator);
}
public static final class MapJoiner {
private final Joiner joiner;
private final String keyValueSeparator;
private MapJoiner(Joiner joiner, String keyValueSeparator) {
this.joiner = joiner; // only "this" is ever passed, so don't checkNotNull
this.keyValueSeparator = checkNotNull(keyValueSeparator);
}
@CanIgnoreReturnValue
public <A extends Appendable> A appendTo(A appendable, Map<?, ?> map) throws IOException {
return appendTo(appendable, map.entrySet());
}
@CanIgnoreReturnValue
public StringBuilder appendTo(StringBuilder builder, Map<?, ?> map) {
return appendTo(builder, map.entrySet());
}
@Beta
@CanIgnoreReturnValue
public <A extends Appendable> A appendTo(A appendable, Iterable<? extends Entry<?, ?>> entries)
throws IOException {
return appendTo(appendable, entries.iterator());
}
@Beta
@CanIgnoreReturnValue
public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts)
throws IOException {
checkNotNull(appendable);
if (parts.hasNext()) {
Entry<?, ?> entry = parts.next();
appendable.append(joiner.toString(entry.getKey()));
appendable.append(keyValueSeparator);
appendable.append(joiner.toString(entry.getValue()));
while (parts.hasNext()) {
appendable.append(joiner.separator);
Entry<?, ?> e = parts.next();
appendable.append(joiner.toString(e.getKey()));
appendable.append(keyValueSeparator);
appendable.append(joiner.toString(e.getValue()));
}
}
return appendable;
}
@Beta
@CanIgnoreReturnValue
public StringBuilder appendTo(StringBuilder builder, Iterable<? extends Entry<?, ?>> entries) {
return appendTo(builder, entries.iterator());
}
@Beta
@CanIgnoreReturnValue
public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) {
try {
appendTo((Appendable) builder, entries);
} catch (IOException impossible) {
throw new AssertionError(impossible);
}
return builder;
}
public String join(Map<?, ?> map) {
return join(map.entrySet());
}
@Beta
public String join(Iterable<? extends Entry<?, ?>> entries) {
return join(entries.iterator());
}
@Beta
public String join(Iterator<? extends Entry<?, ?>> entries) {
return appendTo(new StringBuilder(), entries).toString();
}
public MapJoiner useForNull(String nullText) {
return new MapJoiner(joiner.useForNull(nullText), keyValueSeparator);
}
}
从这个类的实现来看,他是在Joiner的外面再套了一个和Joiner一样的类,只是用了Joiner类的null处理和toString等工具方法。基本是自己实现了map的处理