放棄 StringBuilder!Java8的StringJoiner,真香! 爲什麼會新增這樣一個string輔助類? 總結

在閱讀項目代碼時,突然看到了StringJoiner這個類的使用,感覺很有意思。

對實際開發中也有用,原理上是運用了StringBuilder的一個拼接字符串的封裝處理。

爲什麼會新增這樣一個string輔助類?

原有的stringbuilder太死板,不支持分割,如果想讓最終的字符串以逗號隔開,需要這樣寫

StringBuilder sb = new StringBuilder();
IntStream.range(1,10).forEach(i->{
    sb.append(i+"");
    if( i < 10){
        sb.append(",")
    }
});

是不是太死板了,不好用,StringJoiner怎樣寫呢?

StringJoiner sj = new StringJoiner(",");
IntStream.range(1,10).forEach(i->sj.add(i+""));

有哪些平時用的還比較少的功能:

  • setEmptyValue, 默認情況下的emptyValue是前綴加後綴, 用戶可自定義emptyValue
  • merge(StringJoiner other),合併另外一個joiner
  • length, 當前長度,爲空看emptyValue的長度

讓我實現StringJoiner,我會怎麼辦呢?

  • 維護一個List,最後toString的時候join一下就好了。優勢:實現非常方便 缺點:list太浪費空間(擴容時都是按照係數擴容的)
  • 在StringBuilder基礎上改造(jdk實現方式就是以組合的形式增強的StringBuilder

jdk實現的源碼分析

  • 成員變量
private final String prefix;
    private final String delimiter;
    private final String suffix;

    /*
     * StringBuilder value -- at any time, the characters constructed from the
     * prefix, the added element separated by the delimiter, but without the
     * suffix, so that we can more easily add elements without having to jigger
     * the suffix each time.
     */
    private StringBuilder value;

    /*
     * By default, the string consisting of prefix+suffix, returned by
     * toString(), or properties of value, when no elements have yet been added,
     * i.e. when it is empty.  This may be overridden by the user to be some
     * other value including the empty String.
     */
    private String emptyValue;

其實從成員變量的註釋裏就能看出他們的作用和需要注意的點了

  • 構造函數
public StringJoiner(CharSequence delimiter,
                        CharSequence prefix,
                        CharSequence suffix) {
        Objects.requireNonNull(prefix, "The prefix must not be null");
        Objects.requireNonNull(delimiter, "The delimiter must not be null");
        Objects.requireNonNull(suffix, "The suffix must not be null");
        // make defensive copies of arguments
        this.prefix = prefix.toString();
        this.delimiter = delimiter.toString();
        this.suffix = suffix.toString();
        // !!!構造時就直接將emptyValue拼接好了。
        this.emptyValue = this.prefix + this.suffix;
    }

爲什麼要一開始就構造好呢?如果我想直接自定義emptyValue直接用構造函數初始化不是更方便嗎?是因爲絕大多數場景下都不會自定義emptyValue的場景嗎?不對啊,感覺這個場景非常必要啊。。。

  • 添加元素
public StringJoiner add(CharSequence newElement) {
        prepareBuilder().append(newElement);
        return this;
}

private StringBuilder prepareBuilder() {
        // 從構造函數和類變量的聲明可以看出,沒有添加元素前stringbuilder是沒有初始化的
        if (value != null) {
            // 已經有元素存在的情況下,添加元素前先將分隔符添加進去
            value.append(delimiter);
        } else {
            // 沒有元素存在的情況下先把前綴加進去
            value = new StringBuilder().append(prefix);
        }
        return value;
}

可以看出再添加元素的過程中就已經把前綴和分割字符什麼的都處理好了,全部都在stringbuilde中了,唯一沒有處理的就是後綴。 爲什麼?這樣做tostring什麼的時候真的超級方便的有木有。。。。。

  • 關鍵的toString
public String toString() {
    if (value == null) {
        // 這裏如果沒有自定義空值就是前綴+後綴咯。。
        return emptyValue;
    } else {
        // 爲什麼不直接value.toString()+suffix?????
        if (suffix.equals("")) {
            return value.toString();
        } else {
            int initialLength = value.length();
            String result = value.append(suffix).toString();
            // reset value to pre-append initialLength
            value.setLength(initialLength);
            return result;
        }
    }
}

爲什麼不直接value.toString()+suffix?答案在merge方法

  • merge
public StringJoiner merge(StringJoiner other) {
        Objects.requireNonNull(other);
        if (other.value != null) {
            final int length = other.value.length();
            // 下面這段註釋是說避免merge(this)時受影響,爲什麼?
            // lock the length so that we can seize the data to be appended
            // before initiate copying to avoid interference, especially when
            // merge 'this'
            StringBuilder builder = prepareBuilder();
            builder.append(other.value, other.prefix.length(), length);
        }
        return this;
    }

private StringBuilder prepareBuilder() {
        if (value != null) {
            value.append(delimiter);
        } else {
            value = new StringBuilder().append(prefix);
        }
        return value;
    }

merge的思路是用當前的striingBuilder去append other的value(必須去掉前綴),源碼註釋中的merge 'this'問題是什麼呢?prepareBuilder()的時候可能會先append(delimiter),如果other就是this,那麼length其實就多了一個delimiter,此時append還是得以添加前的length爲準。

merge的實現方式決定了toString時不能直接value.append(suffix).toString(),因爲 builder.append(other.value, other.prefix.length(), length);這行代碼,默認加上suffix後這裏的merge的length得減去suffix的length(嗯,看來作者是想得多好多),而且merge時得把另外一個sj的內容append到當前這個sj的suffix之前(想想就麻煩多了~)

  • length
public int length() {
        // Remember that we never actually append the suffix unless we return
        // the full (present) value or some sub-string or length of it, so that
        // we can add on more if we need to.
        return (value != null ? value.length() + suffix.length() :
            emptyValue.length());
    }

沒什麼好說的,記住length不只是add的元素的length,還有前後綴。

總結

  • 基於StringBuilder實現,add時就把prefix和分隔符給加上了,suffix永遠都不加,知道toString和length調用時才加入計算。這樣帶來的merge操作實現的極大便利性!!!!!學到了,真的不錯
  • emptyValue這個一定要構造時就生成嗎?用戶想有自己的默認值還需要先構造實例再注入嗎。。。。這個覺得還是有點奇怪
  • Objects這個工具方法是返回的校驗的值本身,不錯。
public StringJoiner setEmptyValue(CharSequence emptyValue) {
// 注意這個Objects.requireNonNull方法是return的第一個參數。。。
        this.emptyValue = Objects.requireNonNull(emptyValue,
            "The empty value must not be null").toString();
        return this;
}
拓展乾貨閱讀:一線大廠面試題、高併發等主流技術資料

文章來源:頭條——我不禿頭
整理不易,望珍惜!如果本文對你有所幫助!請點擊頭像查看“傻姑個人簡介”閱讀更多技術乾貨文章,幫助大家一起學習成長!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章