查看原文
其他

放弃 StringBuilder 改用 Java8 的 StringJoiner;真香!

点击关注👉 一行Java 2022-08-09

来源:jianshu.com/p/469fe8fdd3be

在阅读项目代码时,突然看到了StringJoiner这个类的使用,感觉很有意思。

对实际开发中也有用,原理上是运用了StringBuilder的一个拼接字符串的封装处理。

1为什么会新增这样一个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的长度

2让我实现StringJoiner,我会怎么办呢?

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

3jdk实现的源码分析

  • 成员变量

        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,还有前后缀。

4总结

  • 基于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;
    }


END

精品资料,超赞福利,免费领


点击👇名片,关注公众号,回复【  资料  
获取大厂面试资料2T+视频教程10G+电子书
各类精品资料。

注:资料太多,截图为其中部分

最近开发整理了一个用于速刷面试题的小程序;其中收录了上千道常见面试题及答案(包含基础、并发、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、消息队列等多个类型),欢迎您的使用。QQ交流群:912509560



原来理解 AOP 可以这么简单!
MySQL 批量插入,如何不插入重复数据?
自从在 IDEA 中用了热部署神器 JRebel 之后,开发效率提升了 10 倍!
为什么不建议用 equals 判断对象相等?
SQL中 inner join、left join、right join、full join 到底怎么选?详解来了
如何把 Spring Boot 的 Jar 包做成 exe ?超详细教程来了!
CTO:再瞎用 SELECT COUNT( * ) 明天就别来了
别再满屏try catch了,这才是 SpringBoot 异常处理的正确姿势
为什么 StringBuilder 是线程不安全的?

👇👇
👇点击"阅读原文",获取更多资料(持续更新中)

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存