Join类的源码解析
[TOC]
Join类的基本功能
-
通过Join类,我们可以方便实现将容器中的数据按照自定义的方式拼接成一个字符串,而且这是一种线程安全的方式。
ArrayList
integers = Lists.newArrayList(1,2,3,4);String join = Joiner.on("---").join(integers);System.out.println(join);复制代码
实现原理
-
通过查看Joiner的源码可以发现Join的源码是私有的,也就是不让我们直接去新建对象,而是让我们通过提供的共有静态方法去构建对象:
public static Joiner on(char separator) { return new Joiner(String.valueOf(separator)); }private Joiner(Joiner prototype) { this.separator = prototype.separator; }复制代码
猜测通过这种方法构造对象的主要目的是让使用者明白这里传入的separator参数是用作容器之间额连接符的。
-
在对象中保存了分隔符之后又是如何进行划分的呢?
public final String join(Iterable parts) { return join(parts.iterator()); }public final String join(Iterator parts) { return appendTo(new StringBuilder(), parts).toString(); }public final StringBuilder appendTo(StringBuilder builder, Iterator parts) { try { appendTo((Appendable) builder, parts); } catch (IOException impossible) { throw new AssertionError(impossible); } return builder; }复制代码
上面代码是一种重载的思想,最终是通过appendTo方法进行的字符创拼接,注意到这里传入的第一个参数是一个StringBuilder对象,为了就是保证线程的安全性,防止多线程环境下可能出现的问题。
-
最后在appendTo函数是如何做字符串拼接的?
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; }复制代码
可以看到这里首先检查了一下这个StringBuilder是否为空,如果为空就会抛出异常。再然后就是迭代器的遍历了,注意到这里有一个
toString()
方法,很明显,这里重写了Object类的这个方法:CharSequence toString(Object part) { checkNotNull(part); // checkNotNull for GWT (do not optimize). return (part instanceof CharSequence) ? (CharSequence) part : part.toString(); }复制代码
同样这里做了类型检查,就是为了防止容器中有null对象,其次就是如果part本身就是字符数组了,就不用调用tostring方法了,节约系统资源。
除了第一个元素外,其余每一个元素都是分隔符与元素的拼接,很简单的逻辑。就这样完成了一次拼接操作。
学习的设计模式
-
除了基本的字符串拼接外,Joiner类还包含了一些优秀的设计模式,在前面已经说过,如果容器中包含null对象,那么将会抛出空指针异常,我们可以通过如下方法去避免:
ArrayList
integers = Lists.newArrayList(1,2,3,4,null);String join = Joiner.on("---").skipNulls().join(integers);System.out.println(join);复制代码 这里的skipNulls可以避免凭借那些null值,不过拼接的逻辑在之前已经写好了,我们并没有看到有什么避免空值的方法呀,那到底是如何实现的呢?
public Joiner skipNulls() { return new Joiner(this) { @Override public 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; } @Override public Joiner useForNull(String nullText) { throw new UnsupportedOperationException("already specified skipNulls"); } @Override public MapJoiner withKeyValueSeparator(String kvs) { throw new UnsupportedOperationException("can't use .skipNulls() with maps"); } }; }复制代码
这个方法看上去稍微有点长,其实逻辑是很简单的,就是通过重新返回了一个Joiner对象,这个对象的分隔符和之前的是一样的,不过对于
appendTo
,useForNull
,withKeyValueSeparator
这个几个方法进行了重写,所以最后调用join方法的时候就能够对Null值进行不同的判断了。 -
同样的,Joiner中还有一个替换Null的方法
ArrayList
integers = Lists.newArrayList(1,2,3,4,null);String join = Joiner.on("---").useForNull("XXX").join(integers);System.out.println(join);复制代码 这里的
useForNull
方法和上面的skipNulls
方法很像,都是通过返回重写了方法的Joiner对象实现不同的判断策略,其实这就是一种策略模式的体现,对于不同的实现策略有不同的实现,针对实际应用中的需求应用不同的策略,避免了在实现方法中加入大量case判断的问题。