Here's a solution with Stream API which doesn't entail an additional memory allocation, which inevitably happens it you're using String and StringBuilder (because even with Java 8 it's not possible to instantiate a String without making an intermediate copy of the data, and StringBuilder will give you access to it's underlying array instead it gives you copy, and more over since Java 9 both String and StringBuilder are backed by byte[] arrays and not arrays of character).
Firstly, it makes sense to calculate the size of the resulting array (as has been already mentioned by @Maarten Bodewes and @Michael in their answers), which is a pretty fast operation because we are not processing the data of these arrays but only requesting the length of each of them.
And then in order to construct the resulting array we can make use of the collector which accumulates stream elements into the underlying char[] array and then hands it out when all the stream elements has been processes without any intermediate transformations and allocating additional memory.
All functions of the collector need to be stateless and changes should happen only inside its mutable container. Hence, we need a mutable container wraps a char[] array, but it should not have a strong encapsulation like StringBuilder, i.e. allowing access to its underlying array. And we can achieve that with CharBuffer.
So basically below the same idea that was introduced in the answer by @Maarten Bodewes fully implemented with streams.
CharBuffer.allocate(length) under the hood will instantiate char[] of the given length, and CharBuffer.array() will return the same array without generating an additional copy.
public static void main(String[] args) {
List<char[]> listOfCharArrays =
List.of(new char[]{'a', 'b', 'c'},
new char[]{'d', 'e', 'f'},
new char[]{'g', 'h', 'i'});
char[] charArray = listOfCharArrays.stream()
.collect(Collectors.collectingAndThen(
Collectors.summingInt(arr -> arr.length), // calculating the total length of the arrays in the list
length -> listOfCharArrays.stream().collect(
Collector.of(
() -> CharBuffer.allocate(length), // mutable container of the collector
CharBuffer::put, // accumulating stream elements inside the container
CharBuffer::put, // merging the two containers with partial results (runs only when stream is being executed in parallel)
CharBuffer::array // finisher function performs the final transformation
))
));
System.out.println(Arrays.toString(charArray));
}
Output:
[a, b, c, d, e, f, g, h, i]