Guava原始碼淺析——Joiner

窗邊的扁豆發表於2018-03-08

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判斷的問題。

相關文章