专栏名称: 窗边的扁豆
后端工程师
目录
相关文章推荐
人生研究所  ·  官宣离婚,她好敢 ·  4 天前  
混沌学园  ·  韧性团队公开课,期待您的到来 ·  6 天前  
51好读  ›  专栏  ›  窗边的扁豆

Guava源码浅析——Joiner

窗边的扁豆  · 掘金  ·  · 2018-03-08 09:07

正文

Guava源码浅析——Joiner

Join类的源码解析

[TOC]

Join类的基本功能

  • 通过Join类,我们可以方便实现将容器中的数据按照自定义的方式拼接成一个字符串,而且这是一种线程安全的方式。

    ArrayList<Integer> 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 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;
      }
    

    可以看到这里首先检查了一下这个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<Integer> 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 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;
          }
    
          @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对象,这个对象的分隔符和之前的是一样的,不过对于appendTouseForNullwithKeyValueSeparator这个几个方法进行了重写,所以最后调用join方法的时候就能够对Null值进行不同的判断了。

  • 同样的,Joiner中还有一个替换Null的方法

    ArrayList<Integer> integers = Lists.newArrayList(1,2,3,4,null);
    String join = Joiner.on("---").useForNull("XXX").join(integers);
    System.out.println(join);
    

    这里的useForNull方法和上面的skipNulls方法很像,都是通过返回重写了方法的Joiner对象实现不同的判断策略,其实这就是一种策略模式的体现,对于不同的实现策略有不同的实现,针对实际应用中的需求应用不同的策略,避免了在实现方法中加入大量case判断的问题。