天天看点

【Java】 如何优雅的做字符串的拼接

上面有提到,字符串是不可变的,那么字符串的拼接又是怎么去拼接呢?其实这里讲的字符串的拼接就是将两个字符串拼成新的字符串,也就是最后一共三个字符串。

用 + 来拼接,多简单的事呀

这个应该算是最简单的一种方式了,但是很遗憾得玩告诉你,阿里巴巴在他们的规范里面之处不建议在 for 循环里面使用 “+” 进行字符串的拼接。这里的不建议,其实就是不允许的意思,只是人家说的比较委婉而已。事实上,现在还在拿 “+” 来做拼接的应该是比较少了吧。

我在逛阿里开发者社区的时候看到一篇文章

《为什么阿里巴巴不建议在for循环中使用”+”进行字符串拼接》

,里面提到这个 拼接符号 “+” 不是一个运算符重载,Java也并不支持这个所谓的运算符重载。作者提出这是 Java 的一个语法糖。

运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。

语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。

concat

这是 String 里面提供的方法,用法如下:

String strA = "Hello" ;
        String strB = "world" ;
        String concat = strA.concat(",").concat(strB);           

内部实现就是 将字符数组扩容后形成一个新的字符数组 buf , 再将参数 str 加进去。最后再将这个字符数组转成字符串。

public String concat(String str) {
        int otherLen = str.length();
        if (otherLen == 0) {
            return this;
        }
        int len = value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }           

StringBuffer / StringBuilder

这两个应该可以说是一家的孪生兄弟了。做字符串拼接都是 append() 方法:

StringBuilder 里面的 append(String str) 的方法如下: 其实和 concat 方法差不多是吧。

@Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }
    // 这是 super.append()
    public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }           

StringBuffer 里面的 append(String str)的方法如下:

@Override
    public synchronized StringBuffer append(String str) {
        toStringCache = null;
        super.append(str);
        return this;
    }

        public AbstractStringBuilder append(String str) {
        if (str == null)
            return appendNull();
        int len = str.length();
        ensureCapacityInternal(count + len);
        str.getChars(0, len, value, count);
        count += len;
        return this;
    }           

我们可以看到,我们所实现的 append 方法是一样的,唯一的不同就是 StringBuffer 是一个线程安全的拼接。我们可以看一下这两个的类的继承关系。

【Java】 如何优雅的做字符串的拼接

StringUtils.join

这个方法不是Java的,而是Apache的一个方法。方法在 apache.commons 里面。

StringUtils.join(strA, ",", strA)           

这个方法最主要的功能是: 将数组或集合以某拼接符拼接到一起形成新的字符串。

String []list  ={"hello","world"};
StringUtils.join(list, ",")           

在Java 8 里面也有一个 join 方法:

String message = String.join("-", "Java", "is", "cool");

        List<String> strings = new LinkedList<>();
        strings.add("Java");
        strings.add("is");
        strings.add("cool");
        String message = String.join(" ", strings);
        //message returned is: "Java is cool"

        Set<String> strings = new LinkedHashSet<>();
        strings.add("Java");
        strings.add("is");
        strings.add("very");
        strings.add("cool");
        String message = String.join("-", strings);
        //message returned is: "Java-is-very-cool"           

Java8 中的新技能 StringJoiner

刚刚又说到 Java8 里面也有 join 方法,老夫查看第一个 join 方法,找到一个新的类。我们可以看下这个新的 类 StringJoiner.java

public static String join(CharSequence delimiter, CharSequence... elements) {
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(elements);
        // Number of elements not likely worth Arrays.stream overhead.
        StringJoiner joiner = new StringJoiner(delimiter);
        for (CharSequence cs: elements) {
            joiner.add(cs);
        }
        return joiner.toString();
    }           

当我们StringJoiner(CharSequence delimiter)初始化一个StringJoiner的时候,这个delimiter其实是分隔符,并不是可变字符串的初始值。

我们看到这个 joiner.add(cs) ,然后又做了 一个 joiner.toString() . 是不是很像我们的StringBuilder 里面的 append 方法 ? 我们进去看下这个方法。

public StringJoiner add(CharSequence newElement) {
        prepareBuilder().append(newElement);
        return this;
    }           

现在是不是和 StringBuilder 很像?我们再来看这个 prepareBuilder() 是个啥玩意

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

我们看到了,其实他就是一个StringBuilder. 那么他的性能就应当和我们的StringBuilder 不相上下。

在 Java 8 中 引入了这个 StringJoiner , 自然不是平白无故的引入。Java中,我们看到了许多新鲜元素,比如说 Stream .

举个例子:

现在有一个 集合 :

List<String> list = ImmutableList.of("Java" , "is" , "the" , "best" , "language");
           

现在,我需要的形式是这个:

Java is the best language           

+

想要用 + , 那就要遍历,肯定是循环加 “+” , 那我们就直接 pass 了。

String str = "" ;
        for (int i = 0; i < list.size(); i++) {
            str = str.concat(" ").concat(list.get(i)) ;
        }           

StringBuilder

StringBuilder sb = new StringBuilder() ;
        for (int i = 0; i < list.size(); i++) {
            sb.append(list.get(i)).append(" ") ;
        }           

也可以这么写:

String result = list.stream().reduce(new StringBuilder(), (sb, str) -> sb.append(str).append(" "), StringBuilder::append).get();           

当然,这里要是不想用 StringBuilder , 就可以换成 + , 看起来也简单

String result = list.stream().reduce((a,b)->a+" "+b).get();           

StringJoiner

以上的方法,其实很常见,也很简单,现在看看这个怎么用:

String result = list.stream().collect(Collectors.joining(" "));           

就这样。这里的实现如下:

public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
        return joining(delimiter, "", "");
    }

        public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix) {
        return new CollectorImpl<>(
                () -> new StringJoiner(delimiter, prefix, suffix),
                StringJoiner::add, StringJoiner::merge,
                StringJoiner::toString, CH_NOID);
    }           

总结

现在问题来了,以上的这么多方法都好用,怎么选?

  • 不涉及循环的,就是那种很简单的那种拼接,就用 + ,简单方便 ;
  • 涉及到循环的,比如说 for 的,可以考虑使用 StringBuilder , 要求线程安全的就选择 StringBuffer ;
  • 有 List 这种的,StringJoiner 不免一个好的选择。
  • concat 就看你心情了,不想用 + 就用他吧。

其实呢,在字符串额度拼接里面,老夫还用过这两个,应该是用的比较多的:

  • String java = String.format("%s is the beat language", "Java");
  • MessageFormat.format("{0} is the beat language" , "Java") ;

MessageFormat 方法在 java.text 里面,所以呢,他对一些 文本类数据(String)比较友好。曾经在使用这个方法的时候, 我有一个数字,20 000 000 的一个数,经过这个类转成了 20,,000,000 。 而用 String.format() 就没这个问题。具体的看大家选择。这两种方式其实并不能称之为字符串的拼接,顶多就是字符串的格式化。 format 方法不就是格式化额度意思吗。