preface

C: In our daily development, string concatenation is a frequently used API. The simplest is “concatenation by + sign”, but it is also the worst in terms of performance and efficiency.

To do this, we usually use StringBuffer or StringBuilder for string concatenation, which is not a big problem in itself, but there are some concatenation scenarios where using StringBuffer or StringBuilder is a bit low-end.

// Need to implement the string concatenation of SQL statement in query
// SELECT * FROM XX WHERE xx IN (1, 3, 5);

// The column phenotype data needs to be concatenated IN the IN query section
List<Integer> values = Arrays.asList(1.3.5);

// Create a StringBuilder and store the prefix (
StringBuilder sb = new StringBuilder("(");

// Iterate over the list data and append the delimiter,
for (int i = 0; i < values.size(); i++) {
	sb.append(values.get(i));
	if(i ! = values.size() -1) {
		sb.append(","); }}// append suffix)
sb.append(")");
System.out.println(sb); / / (1,3,5)
Copy the code

Of course, the usage scenario is going to be so troublesome and you say goodbye, we check the teacher will introduce a StringBuilder based development, used to simplify need separator to string concatenation scenario API: StringJoiner.

Introduction to the

The StringJoiner class, an API added to Java 8, is built on StringBuilder and is used to implement scenarios where strings are concatenated by delimiters.

The String class also added two statically overloaded methods in Java 8: JOIN (CharSequence delimiter, CharSequence… Elements) : String, join(CharSequence delimiter,Iterable<? Extends CharSequence> Elements) : String, and the implementation of these two methods uses A StringJoiner.

API introduction and use

A constructor

StringJoiner has two constructors. The first constructor requires a delimiter, prefix, and suffix in sequence. The second construct requires only a delimiter, with no prefix and suffix (prefix and suffix default to empty strings).

StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
StringJoiner(CharSequence delimiter)
Copy the code

The use effect is as follows:

StringJoiner sj = new StringJoiner(","."(".")");
StringJoiner sj2 = new StringJoiner(",");
Copy the code

The add method

Once the object is created, the main thing we use is StringJoiner’s Add method, which appends elements to be joined.

add(CharSequence newElement) : StringJoiner
Copy the code

Let’s take a look at two examples to see how StringJoiner works, and first let’s use StringJoiner to gracefully address the requirements in the introduction.

List<Integer> values = Arrays.asList(1.3.5);

// Create a StringJoiner object specifying the delimiter, prefix, and suffix
StringJoiner sj = new StringJoiner(","."(".")");

// Iterates over the list data and appends the element to StringJoiner
for (Integer value : values) {
	sj.add(value.toString());
}

System.out.println(sj); / / (1,3,5)
Copy the code

Requirement: there are 3 element values, respectively: zhang SAN, Li si, Wang Wu, splice them, request finally get a string is zhang SAN, Li si, Wang wu.

// Specify the delimiter
StringJoiner sj = new StringJoiner(",");

// Add elements
sj.add("Zhang").add("Bill").add("Fifty");

// Convert to a string
String result = sj.toString();
System.out.println(result); // Zhang SAN, Li Si, Wang Wu
Copy the code

The merge method

If we need to merge the contents of two StringJoiners, we can use the merge method.

merge(StringJoiner other) : StringJoiner
Copy the code

The use effect is as follows:

StringJoiner sj = new StringJoiner(",");
sj.add("Zhang").add("Bill").add("Fifty"); // Zhang SAN, Li Si, Wang Wu

StringJoiner sj2 = new StringJoiner("-"."["."]");
sj2.add("Daisy").add("Cropland 7").add("Tortoise"); // [Zhao Liu, Tian Qi, Wang Ba]

// who calls will be the main merge
// sj.merge(sj2);
// System.out.println(sj); // Zhang SAN, Li Si, Wang Wu, Zhao Liu - Tian Qi - Wang Ba

sj2.merge(sj);
System.out.println(sj2); // [Zhao Liu tian Qi Wang Ba Zhang SAN, Li Si, Wang Wu]
Copy the code

The source code parsing

Don’t be afraid. The source code for StringJoiner is only a few hundred lines. You can read it. In order to make it easier to understand, Teacher Cha has made some adjustments to the order of methods in the source code posted below.

First of all, when We use StringJoiner, we call the constructor to create the object, and The StringJoiner takes the constructor argument to initialize its prefix, delimiter, and suffix, and then concatenates the prefix and suffix to initialize the emptyValue property. This attribute is used to indicate that no element has been added (null).


Later, when we execute the Add method to add elements, there are two different processing situations.

If adding the first element, StringJoiner initializes the internal StringBuilder and appends the prefix to the first element with StringBuilder.

If the first element is not added, the StringBuilder inside StringJoiner appends the delimiter and the element in turn.


There are also two different things that happen when we call the toString method to get the string stored in StringJoiner.

If there is no element added, StringJoiner will just return us the emptyValue property value.

If an element has been added, The StringJoiner determines whether the suffix is an empty string. If the suffix is an empty string, it simply converts the internal StringBuilder to a string. But if the suffix is not an empty string, we append the suffix element to StringBuilder and return it as a string. And then, interestingly enough, StringJoiner allows us to add elements after we call toString. It removes the suffix that StringBuilder just added.


What about other methods like merge? Read it yourself.

public final class StringJoiner {
    
    / / prefix
    private final String prefix;
    / / delimiter
    private final String delimiter;
    / / the suffix
    private final String suffix;

    // StringJoiner essentially uses StringBuilder to concatenate strings
    private StringBuilder value;

    / / null
    private String emptyValue;

	// Create StringJoiner with no prefix or suffix and only delimiters specified
    public StringJoiner(CharSequence delimiter) {
        this(delimiter, ""."");
    }

    // Specify the delimiter, prefix, and suffix in sequence to create StringJoiner
    public StringJoiner(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {
        // The parameter is null
        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");
        
        // Convert the argument to a string and assign it to the corresponding property
        this.prefix = prefix.toString();
        this.delimiter = delimiter.toString();
        this.suffix = suffix.toString();
        // Set the default value for null: prefix + suffix
        this.emptyValue = this.prefix + this.suffix;
    }

    // Append elements with StringBuilder
    public StringJoiner add(CharSequence newElement) {
        prepareBuilder().append(newElement);
        return this;
    }
    
    // Preprocess the internal StringBuilder
    // The first time the method is called, initialize StringBuilder and append the prefix
    // The second and subsequent calls to the method append the delimiter
    private StringBuilder prepareBuilder(a) {
        if(value ! =null) {
            value.append(delimiter);
        } else {
            value = new StringBuilder().append(prefix);
        }
        return value;
    }
    
    // Convert The StringJoiner content to a string
    @Override
    public String toString(a) {
        // If StringBuilder is null, no element has been added
        // Returns the default null value
        if (value == null) {
            return emptyValue;
        } else {
            // If StringBuilder is not null, the element has been added
            // Check whether the suffix is an empty string
            if (suffix.equals("")) {
                // Simply return the string contents of the StringBuilder
                return value.toString();
            } else {
                // If the suffix is not an empty string
                // Gets the string length of the current StringBuilder
                int initialLength = value.length();
                // StringBuilder concatenates suffixes and converts them to strings
                String result = value.append(suffix).toString();
                // Remove the suffix of the StringBuilder concatenation so that elements can be added later
                value.setLength(initialLength);
                returnresult; }}}// Merges other StringJoiner content
    public StringJoiner merge(StringJoiner other) {
        Objects.requireNonNull(other);
        if(other.value ! =null) {
            final int length = other.value.length();
            // 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;
    }

    // Gets StringJoiner length
    public int length(a) {
        // If StringBuilder is not empty, StringBuilder length + suffix length is StringJoiner length
        // Suffixes are appended only when the toString() method is called. This is done because concatenating suffixes in advance makes it impossible to append new elements
        return(value ! =null ? value.length() + suffix.length() :
                emptyValue.length());
    }
    
    // Set a null value
    public StringJoiner setEmptyValue(CharSequence emptyValue) {
        this.emptyValue = Objects.requireNonNull(emptyValue,
            "The empty value must not be null").toString();
        return this; }}Copy the code

The join method of String

How’s that? Does StringJoiner work ok? But don’t worry! Wait! Java 8 not only provides StringJoiner, but it also provides two new methods in the String class that are implemented internally through StringJoiner. Take a look.

use

// Concatenates the elements of the argument into a string by specifying delimiters
String message = String.join("-"."Java"."is"."cool");
System.out.println(message); // Java-is-cool
Copy the code
// Concatenates the elements in the collection into a string using the specified delimiter
List<String> strings1 = new LinkedList<>();
strings1.add("Java"); strings1.add("is");
strings1.add("cool");
String message1 = String.join("", strings1);
System.out.println(message1); // Java is cool

Set<String> strings2 = new LinkedHashSet<>();
strings2.add("Java"); strings2.add("is");
strings2.add("very"); strings2.add("cool");
String message2 = String.join("-", strings2);
System.out.println(message2); // Java-is-very-cool
Copy the code

The source code parsing

The two new static methods added to the String class have very simple internal implementation code. They both take advantage of the single-argument construction of the delimiter that StringJoiner provides and then add elements to The StringJoiner by iterating through the variable arguments or collections that are passed in. Finally, StringJoiner is converted to a string and returned.

public final class String
    implements java.io.Serializable.Comparable<String>, CharSequence {
    
    // Concatenates a list of strings by specifying delimiters
    public static String join(CharSequence delimiter, CharSequence... elements) {
        // The parameter is null
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(elements);
        // Create a StringJoiner object and specify the delimiter
        StringJoiner joiner = new StringJoiner(delimiter);
        // Iterate over the array of variable arguments and add elements to StringJoiner
        for (CharSequence cs: elements) {
            joiner.add(cs);
        }
        // Convert StringJoiner to a string
        return joiner.toString();
    }

    // Concatenate all elements in the collection by specifying delimiters
    public static String join(CharSequence delimiter, Iterable
        elements) {
        // The parameter is null
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(elements);
        // Create a StringJoiner object and specify the delimiter
        StringJoiner joiner = new StringJoiner(delimiter);
        // Iterate over the collection and add the element to StringJoiner
        for (CharSequence cs: elements) {
            joiner.add(cs);
        }
        // Convert StringJoiner to a string
        returnjoiner.toString(); }}Copy the code

Afterword.

C: Well, that’s the end of StringJoiner, and when we concatenate strings in the future, we can think a little bit more about whether we need to concatenate separators in our current scenario, so we can use StringJoiner instead.