Создание StringBuilder с использованием лучшей практики цикла - PullRequest
3 голосов
/ 03 июня 2019

Интересно, эффективнее ли этот код, использующий StringBuilder

или он все еще создает множество временных строк, объединяя "" с текущим элементом?

если да, можете ли вы предложить лучший код?

public String toString() {
    StringBuilder out = new StringBuilder();
    for (long item : someListOfNumbers) {
        out.append( " " + item);
    }
    return out.toString();
}

Ответы [ 4 ]

4 голосов
/ 03 июня 2019

Для этого конкретного случая использования вы, вероятно, захотите использовать StringJoiner, если только ваша цель не состоит в том, чтобы в результате было пробел.

public String toString() {
    StringJoiner joiner = new StringJoiner(" ");
    someListOfNumbers.forEach(l -> joiner.add(Long.toString(l)));
    return joiner.toString();
}

или используйте Collectors.joining():

public String toString() {
    returns someListOfNumbers.stream()
        .map(String::valueOf)
        .collect(Collectors.joining(" "));
}

StringBuilder будет более эффективным при построении текста, но можно утверждать, что приведенное выше более читабельно.Определенно следует избегать оператора + при использовании StringBuilder и использовать вместо него методы, подобные append().

Если вы посмотрите на этот или этот или другие ответы, вы увидите, что конкатенация строк дополнительно оптимизируется компилятором javac, например, если константы конкатенируютсяс оператором + компилятор может вставить результирующую константу в байт-код.

3 голосов
/ 03 июня 2019
out.append( " " + item);

В вышеприведенном утверждении здесь возникает проблема " " + item, так как она объединяет строку с длинным.Поскольку строка неизменна, оператор + для строки создает новую копию строки в памяти.Оптимальным решением может быть использование

out.append(' ').append(item);

или даже

out.append(" ").append(item);

В обоих вышеописанных сценариях StringBuilder является изменяемым, поэтому для сохранения новой конкатенированной строки не требуется дополнительного места в памяти.Следовательно, это более оптимально.

1 голос
/ 03 июня 2019

Современный способ оставил бы манипулирование строителем строк библиотечным методом:

public String toString() {
    return someListOfNumbers.stream()
            .map(Number::toString)
            .collect(Collectors.joining(" "));
}

Я предположил, что someListOfNumbers - это List<Long>.Мой метод не дает точно такой же результат, как ваш: у вашего результата есть пробел, который я пропустил.Если вам нужно это место, вы можете либо добавить его самостоятельно, либо использовать:

            .collect(Collectors.joining(" ", " ", ""));

Трех аргумент joining ожидает разделитель, префикс и суффикс в указанном порядке (я даю пустую строку как суффикс).

Примечание: я хотел использовать .map(Long::toString), но он не компилируется, потому что он неоднозначен.Здесь toString может относиться либо к no-arg Long.toString, либо к статическому one-arg Long.toString(long).Я решил это, ссылаясь на него через суперкласс Long: Number::toString.Другое решение было бы .map(String::valueOf), как в ответе Кароля Доубеки.

0 голосов
/ 03 июня 2019

Чтобы избавиться от конечного или начального нуля, вы должны проверить индекс элемента, который в данный момент обрабатывается в цикле.

Вот три примера, которые строят String, которому потом нечего обрезать:

public String toString() {
    StringBuilder out = new StringBuilder();
    // iterate in a classic for-loop checking for item at index 0
    for (int i = 0; i < someListOfNumbers.size(); i++) {
        long item = someListOfNumbers.get(i);
        if (i == 0) {
            // in this case, avoid a leading zero
            out.append(item);
        } else {
            out.append(" ").append(item);
        }
    }
    return out.toString();
}

public String toString() {
    StringBuilder out = new StringBuilder();
    // iterate in a classic for-loop checking for item at last index
    for (int i = 0; i < someListOfNumbers.size(); i++) {
        long item = someListOfNumbers.get(i);
        if (i == someListOfNumbers.size() - 1) {
            out.append(item).append(" ");
        } else {
            // this case avoids a trailing zero
            out.append(item);
        }
    }
    return out.toString();
}

public String toString() {
    StringBuilder out = new StringBuilder();
    // add the first element and iterate from 1 to end afterwards 
    // (this requires a check for an empty list)
    out.append(someListOfNumbers.get(0));
    for (int i = 1; i < someListOfNumbers.size(); i++) {
        out.append(" ").append(someListOfNumbers.get(i));
    }
    return out.toString();
}

Вы даже можете использовать свой собственный метод, но с расширенным for -loop, вам придется использовать indexOf и затем выполнить проверку. Я бы этого не хотел, но это ваше решение.

...