Новая поза для сращивания строк — StringJoiner.

Java
Новая поза для сращивания строк — StringJoiner.

Это 4-й день моего участия в Gengwen Challenge, смотрите подробности мероприятия:Обновить вызов

предисловие

Знаете ли вы, сколько способов использовать собственный JDK для объединения строк? Все, о чем мы можем думать, это StringBuilder, StringBuffer, String.concat и соединение с +. Есть ли другие? Далее поговорим об использовании нового класса для объединения строк в Java 8, который называется StringJoiner.

Обзор

StringJoiner — это класс в пакете java.util, который создает строку, разделенную разделителем, и может начинаться с предоставленной строки префикса и заканчиваться предоставленной строкой суффикса. Хотя мы можем использовать StringBuilder или StringBuffer для реализации этих функций, методы, предоставляемые StringJoiner, проще и не требуют написания большого количества кода.

Конструктор

StringJoiner имеет всего 2 конструктора. Конструктор очень прост, это инициализация строк-разделителей, префиксов и суффиксов.

    public StringJoiner(CharSequence delimiter) {
        this(delimiter, "", "");//默认前缀和后缀为"",重载调用
    }

    public StringJoiner(CharSequence delimiter,
                        CharSequence prefix,
                        CharSequence suffix) {
        //间隔符,前缀和后缀判断是否为null,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");
        //成员变量赋值
        this.prefix = prefix.toString();
        this.delimiter = delimiter.toString();
        this.suffix = suffix.toString();
        //空值被设置为只有前后缀
        this.emptyValue = this.prefix + this.suffix;
    }

использование

Использование StringJoiner на самом деле очень просто. Давайте посмотрим на использование конкатенации строк StringJoiner.

public class StringJoinerTest {
    public static void main(String[] args) {
        StringJoiner sj = new StringJoiner("hello");
        sj.add("Java 8 ");
        sj.add("Java 11");
        System.out.println(sj.toString());
        StringJoiner sj1 = new StringJoiner(":","[","]");
        sj1.add("Java8").add("Java11").add("Java15");
        System.out.println(sj1.toString());
    }
}

Выходной результат:

Java 8 helloJava 11
[Java8:Java11:Java15]

注意:

  1. Когда мы используем StringJoiner (разделитель CharSequence) для инициализации StringJoiner, этот разделитель на самом деле является разделителем, а не начальным значением строки.
  2. Второй и третий параметры StringJoiner(разделитель CharSequence, префикс CharSequence, суффикс CharSequence) являются префиксом и суффиксом объединенной строки соответственно.

Анализ исходного кода

Исходный код в JDK8

  public StringJoiner setEmptyValue(CharSequence emptyValue) {
        this.emptyValue = Objects.requireNonNull(emptyValue,
            "The empty value must not be null").toString();
        return this;
    }

    @Override
    public String toString() {
        if (value == null) {
            return emptyValue;
        } else {
            if (suffix.equals("")) {
                return value.toString();
            } else {
                int initialLength = value.length();
                String result = value.append(suffix).toString();
                value.setLength(initialLength);
                return result;
            }
        }
    }

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

    public StringJoiner merge(StringJoiner other) {
        Objects.requireNonNull(other);
        if (other.value != null) {
            final int length = other.value.length();
            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;
    }


    public int length() {
        return (value != null ? value.length() + suffix.length() :
                emptyValue.length());
    }

Исходный код в JDK11

public final class StringJoiner {
    private String[] elts;
    
    @Override
    public String toString() {
        final String[] elts = this.elts;
        if (elts == null && emptyValue != null) {
            return emptyValue;
        }
        final int size = this.size;
        final int addLen = prefix.length() + suffix.length();
        if (addLen == 0) {
            compactElts();
            return size == 0 ? "" : elts[0];
        }
        final String delimiter = this.delimiter;
        final char[] chars = new char[len + addLen];
        int k = getChars(prefix, chars, 0);
        if (size > 0) {
            k += getChars(elts[0], chars, k);
            for (int i = 1; i < size; i++) {
                k += getChars(delimiter, chars, k);
                k += getChars(elts[i], chars, k);
            }
        }
        k += getChars(suffix, chars, k);
        return new String(chars);
    }
    public StringJoiner add(CharSequence newElement) {
        final String elt = String.valueOf(newElement);
        if (elts == null) {
            elts = new String[8];
        } else {
            if (size == elts.length)
                elts = Arrays.copyOf(elts, 2 * size);
            len += delimiter.length();
        }
        len += elt.length();
        elts[size++] = elt;
        return this;
    }
}

Сравните реализацию метода добавления исходного кода StringJoiner в JDK8 и JDK11:

  1. StringJoiner в JDK8 реализован через StringBuilder;

  2. StringJoiner в JDK11 заключается в том, чтобы поместить строки для объединения в массив строк.Когда используется метод toString(), это реальный процесс объединения строк.

Итак, вопрос в том, что, поскольку JDK 8 уже использовал StringBuilder для реализации, почему JDK11 должен измениться на String[] для кэширования всех строк, подлежащих сращиванию. Это потребует оптимизации базовой JVM.

Теперь, когда у нас уже есть StringBuilder, зачем нам собирать StringJoiner? В чем его преимущества? Давайте выясним причину. Мы видим, что Collectors#joining помечен в блоке комментариев кода.

image-20210620082312132.png

Давайте посмотрим, что именно делает Collectors#joining?

    public static Collector<CharSequence, ?, String> joining() {
        return new CollectorImpl<CharSequence, StringBuilder, String>(
                StringBuilder::new, StringBuilder::append,
                (r1, r2) -> { r1.append(r2); return r1; },
                StringBuilder::toString, CH_NOID);
    }

    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);
    }

Оказывается, Stream в Java 8 реализован с помощью StringJoiner. В этот момент мы можем подумать, а почему бы не использовать StringBuilder для его реализации? На примере видно, что если StringBuilder строит сплайсинг, он должен быть простым без префикса и суффикса, но когда требуется сплайсинг других префиксов и суффиксов, это становится очень сложным.

Таким образом, статус StringJoiner в Java 8 нельзя заменить на StringBuilder.

Суммировать

В этой статье представлен StringJoiner, класс объединения строк, предоставляемый Java 8. StringJoiner в JDK 8 реализован StringBuilder, поэтому его производительность аналогична StringBuilder, а также он не является потокобезопасным. Он был оптимизирован в JDK 11 для проксирования StringBuilder через String[] .

Как в ежедневном процессе разработки мы выбираем класс конкатенации строк?

  1. Простая конкатенация строк, просто используйте + напрямую.
  2. Сращивание строк требуется в таких сценариях, как циклы for, и сначала можно использовать StringBuilder.
  3. Соединение строк требуется в сценариях, где используются Java Stream и лямбда-выражение, и сначала можно использовать StringJoiner.