天天看點

【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 方法不就是格式化額度意思嗎。