Suppose I have a text represented as a collection of lines of words. I want to join words in a line with a space, and join lines with a newline:
class Word {
String value;
}
public static String toString(List <List <Word>> lines) {
return lines.stream().map(
l -> l.stream().map(w -> w.value).collect(Collectors.joining(" "))
).collect(Collectors.joining("\n"));
}
This works fine, but I end up creating an intermediate String object for each line. Is there a nice concise way of doing the same without the overhead?
You can use
public static String toString(List<List<Word>> lines) {
return lines.stream()
.map(l -> l.stream()
.map(w -> w.value)
.collect(() -> new StringJoiner(" "),
StringJoiner::add,
StringJoiner::merge))
.collect(() -> new StringJoiner("\n"),
StringJoiner::merge,
StringJoiner::merge).toString();
}
The inner collect
basically does what Collectors.joining(" ")
does, but omits the final StringJoiner.toString()
step.
Then, the outer collect
differs from an ordinary Collectors.joining("\n")
in that it accepts StringJoiner
as an input and combines them using merge
. This relies on a documented behavior:
If the other
StringJoiner
is using a different delimiter, then elements from the otherStringJoiner
are concatenated with that delimiter and the result is appended to thisStringJoiner
as a single element.
This is done internally on the StringBuilder
/character data level without creating a String
instance while retaining the intended semantic.