Java StringJoiner

为什么会新增这样一个String辅助类?

原有的StringBuilder太死板,不支持分割,如果想让最终的字符串以逗号隔开,需要这样写

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

是不是太死板了,不好用,StringJoiner怎样写呢?

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

有哪些平时用的还比较少的功能:

  • setEmptyValue,默认情况下的emptyValue是前缀加后缀, 用户可自定义emptyValue
  • merge(StringJoiner other),合并另外一个joiner
  • length,当前长度

    自己怎么实现StringJoiner

    维护一个List,最后toString的时候join一下就好了优势:实现非常方便缺点:list太浪费空间(扩容时都是按照系数扩容的)在StringBuilder基础上改造(jdk实现方式就是以组合的形式增强的StringBuilder

    jdk实现的源码分析

    成员变量

    ```java private final String prefix; private final String delimiter; private final String suffix;

/*

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

private StringBuilder value;

/*

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

private String emptyValue;

  1. 其实从成员变量的注释里就能看出他们的作用和需要注意的点了。
  2. <a name="tr4jW"></a>
  3. ### 构造函数
  4. ```java
  5. public StringJoiner(CharSequence delimiter,
  6. CharSequence prefix,
  7. CharSequence suffix) {
  8. Objects.requireNonNull(prefix, "The prefix must not be null");
  9. Objects.requireNonNull(delimiter, "The delimiter must not be null");
  10. Objects.requireNonNull(suffix, "The suffix must not be null");
  11. // make defensive copies of arguments
  12. this.prefix = prefix.toString();
  13. this.delimiter = delimiter.toString();
  14. this.suffix = suffix.toString();
  15. // !!!构造时就直接将emptyValue拼接好了。
  16. this.emptyValue = this.prefix + this.suffix;
  17. }

为什么要一开始就构造好呢?如果想直接自定义emptyValue直接用构造函数初始化不是更方便。

添加元素

  1. public StringJoiner add(CharSequence newElement) {
  2. prepareBuilder().append(newElement);
  3. return this;
  4. }
  5. private StringBuilder prepareBuilder() {
  6. // 从构造函数和类变量的声明可以看出,没有添加元素前stringbuilder是没有初始化的
  7. if (value != null) {
  8. // 已经有元素存在的情况下,添加元素前先将分隔符添加进去
  9. value.append(delimiter);
  10. } else {
  11. // 没有元素存在的情况下先把前缀加进去
  12. value = new StringBuilder().append(prefix);
  13. }
  14. return value;
  15. }

可以看出再添加元素的过程中就已经把前缀和分割字符什么的都处理好了,全部都在stringbuilde中了,唯一没有处理的就是后缀。为什么?这样做toString什么的时候真的超级方便。

关键的toString

  1. public String toString() {
  2. if (value == null) {
  3. // 这里如果没有自定义空值就是前缀+后缀咯。。
  4. return emptyValue;
  5. } else {
  6. // 为什么不直接value.toString()+suffix?????
  7. if (suffix.equals("")) {
  8. return value.toString();
  9. } else {
  10. int initialLength = value.length();
  11. String result = value.append(suffix).toString();
  12. // reset value to pre-append initialLength
  13. value.setLength(initialLength);
  14. return result;
  15. }
  16. }
  17. }

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

merge

  1. public StringJoiner merge(StringJoiner other) {
  2. Objects.requireNonNull(other);
  3. if (other.value != null) {
  4. final int length = other.value.length();
  5. // 下面这段注释是说避免merge(this)时受影响,为什么?
  6. // lock the length so that we can seize the data to be appended
  7. // before initiate copying to avoid interference, especially when
  8. // merge 'this'
  9. StringBuilder builder = prepareBuilder();
  10. builder.append(other.value, other.prefix.length(), length);
  11. }
  12. return this;
  13. }
  14. private StringBuilder prepareBuilder() {
  15. if (value != null) {
  16. value.append(delimiter);
  17. } else {
  18. value = new StringBuilder().append(prefix);
  19. }
  20. return value;
  21. }

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

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

length不只是add的元素的length,还有前后缀。

总结

基于StringBuilder实现,add时就把prefix和分隔符给加上了,suffix永远都不加,知道toStringlength调用时才加入计算。这样带来的merge操作实现的极大便利性!
emptyValue这个一定要构造时就生成吗?用户想有自己的默认值还需要先构造实例再注入吗。这个觉得还是有点奇怪。
Objects这个工具方法是返回的校验的值本身。

  1. public StringJoiner setEmptyValue(CharSequence emptyValue) {
  2. // 注意这个Objects.requireNonNull方法是return的第一个参数。。。
  3. this.emptyValue = Objects.requireNonNull(emptyValue,
  4. "The empty value must not be null").toString();
  5. return this;
  6. }