Java Stream toArray

Introduction

The Java stream toArray methods, which we learn in this post, allow us to collect/convert a stream into an array.

Java Stream toArray

There are two overloaded toArray methods in the Stream interface in Java. The toArray()is a terminal operation which returns an array having the elements of the stream.

Simple toArray on stream

This is the simpler of the two overloads which has the below shown signature.

Object[] toArray();

It collects the stream elements into an array. The type of the returned array is Object[] and hence this is not very useful as we lose the type of the elements.

Java Stream toArray using a generator function

This overload of toArray takes a generator function, which is an IntFunction. An IntFunction is a function which takes a primitive int argument and returns some result.

public interface IntFunction<R> {
    R apply(int value);
}

The toArray method shown below takes an IntFunction and returns an array having the elements of the stream. It uses the passed generator function to create the array. This includes any additional arrays that might be needed (say for partitioned execution or for resizing).

<A> A[] toArray(IntFunction<A[]> generator);

where A is the type of the elements of the stream.

It passes the size of the desired array to the generator function (int argument) and it is supposed to create and return an array of that size.

Java Stream toArray – A simple example

Let us see a simple example of using the toArray method of the stream. Shown below, we have a stream of strings and we call the toArray on it to create an Object[].

Stream<String> stream = Stream.of("a", "b", "c");
Object[] arr = stream.toArray();
System.out.println(Arrays.toString(arr)); //[a, b, c]

The final output array has the elements of the stream, but its type is Object[] and not a String[].

Using a generator function

To get the array as String[], let us use the generator function. 

Stream<String> stream = Stream.of("a", "b", "c");
System.out.println(Arrays.toString(stream.toArray(len -> {
    System.out.println(len);
    return new String[len];
})));

I’ve made it detailed to print the int primitive value passed as argument. Basically, it has to create and return a String[] of length len. The stream pipeline implementation will determine the size of the target desired array. In this case, it knows that the stream has three elements and hence it calls the passed generator function with value as 3. Running the above code prints,

3
[a, b, c]

More simply, we can write it as,

System.out.println(Arrays.toString(stream.toArray(String[]::new)));

Where String[]::new is the method reference of the lambda expression len -> new String[len].

Stream with filter

As an example, if the stream has a filter operation, even then the stream internals will determine the target array size correctly. For example, from a stream of integers, let us filter only even numbers and collect into an array.

Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
System.out.println(Arrays.toString(integerStream
        .filter(i -> i % 2 == 0)
        .toArray(len -> {
            System.out.println(len);
            return new Integer[len];
        })));

Since only two values will pass the filter predicate, the final array will be of size 2. The stream pipeline correctly passes 2 to the toArray generator function. The above code prints,

2
[2, 4]

Array size lesser or greater than the generator function argument

If we create an array whose size is less than the argument value passed to the generator function, then it throws IndexOutOfBoundsException.

On the other hand, if we create a bigger array, it ignores the other elements. In the below example, we create an Integer[] of size len + 2. In the resultant array, the last two elements will remain as null.

System.out.println(Arrays.toString(integerStream
        .filter(i -> i % 2 == 0)
        .toArray(len -> {
            System.out.println(len);
            return new Integer[len + 2];
        })));

Prints,

2
[2, 4, null, null]

Java Stream toArray to create a 2D array

Let us look at creating a two-dimensional array using toArray. We have a List<String> shown below. Let us parse each line and convert it to a 2D character array (Character[][]).

List<String> lines = List.of(
        "##..#",
        ".#..#",
        "...##"
);
Character[][] grid = lines.stream()
        .map(line -> line.chars()
                .mapToObj(c -> (char) c)
                .toArray(len -> {
                    System.out.println("Length of one row is " + len);
                    return new Character[len]; 
                }))
        .toArray(len -> {
            System.out.println("Number of rows in final 2D array is " + len);
            return new Character[len][];
        });

First, we create a stream from the list and for each row (line), we call the chars() method on the string. This returns an IntStream where each entry corresponds to the ASCII value of the character. We map it to a character and this returns a Stream<Character> (Note: This involves boxing to convert from primitive char to the Character instance).

Then, we call the toArray and pass a generator function. As before, I’ve made it a block lambda expression with print statements for a better understanding. It does this for each row (line) and in this example will have three Character[] as a result of this.

Finally, we call toArray on the map operation (which returns a Character[]). The generator function is similar to before, but we create a 2D array as the individual elements from the previous map step is a Character[]. Note that we have left out the 2nd dimension as that will be determined from the previous toArray where it creates the individual Character[].

Let us print the result grid with its size. 

System.out.println(Arrays.deepToString(grid));

System.out.println(grid.length);
System.out.println(grid[0].length);

Running this, we get,

Number of rows in final 2D array is 3
Length of one row is 5
Length of one row is 5
Length of one row is 5
[[#, #, ., ., #], [., #, ., ., #], [., ., ., #, #]]
3
5

First, it calls the outermost toArray to create the 2D array. Next, it does the internal map operation where the individual rows are created. The number of rows is 3, which is what it passed to the outer toArray’s generator function. The inner toArray’s generator function received 5 as the length, which is the length of each row (i.e., the column size in the overall 2D result array).

Using method references, we can write the above code as,

Character[][] grid = lines.stream()
                .map(line -> line.chars()
                        .mapToObj(c -> (char) c)
                        .toArray(Character[]::new))
                .toArray(Character[][]::new);

Column dimension of 2D array

As I specified, even if we specify the second dimension in our 2D array, it will work. We could have specified a lower or higher number for the columns and either way it will work. How?

Character[][] grid = lines.stream()
        .map(line -> line.chars()
                .mapToObj(c -> (char) c)
                .toArray(len -> {
                    System.out.println("Length of one row is " + len);
                    return new Character[len]; 
                }))
        .toArray(len -> {
            System.out.println("Number of rows in final 2D array is " + len);
            return new Character[len][]; 
            //even return new Character[len][1] or new Character[len][1000] works
        });

This is because it uses the outer toArray generator to create a 2D array reference and this is needed only to determine the number of rows (the first dimension). When it creates the rows in the inner toArray(), it just assigns the reference of that to the corresponding index of the 2D array. This is the equivalent of us doing like,

Character grid[][] = new Character[len][]; //2nd dimension not specified

Character[] row = new Chracter[] {...};
grid[0] = row;
row = new Chracter[] {...};
grid[1] = row;
row = new Chracter[] {...};
grid[2] = row;

If we had specified the second dimension (the column size), be it 1 or 1000, it is wasted space (would be GC’ed later) as it creates a Character[] (row) and then assigns that reference to an index of the Character[][] (grid). The write doesn’t happen character by character like grid[i][j].

This is because a multi-dimensional array in Java is an array of arrays and hence, each row can even have different lengths. (Refer to the stackoverflow post, Changing the column size of 2D array at runtime if you need more details).

Jagged 2D array using toArray

Since each row can have different lengths, let us run the same code but for a different input – one where each line has different lengths).

List<String> lines = List.of(
        "##..#",
        ".#.",
        "...####"
);

Now, we get,

Number of rows in final 2D array is 3
Length of one row is 5
Length of one row is 3
Length of one row is 7
[[#, #, ., ., #], [., #, .], [., ., ., #, #, #, #]]

As before, the last toArray creates a 2D array of size 3 (three rows). But each of the individual rows (Character[]) are of different lengths (5, 3 and 7 in this example).

Stream primitive specializations toArray

There are primitive specialization of streams for int, long and double, namely, IntStreamLongStream and DoubleStream respectively.
They all have one toArray() method to collect the elements of the primitive int/long/double stream into a primitive array (of type int[] or long[] or double[] respectively).

IntStream intStream = IntStream.range(0, 5);
int[] intArr = intStream.toArray();
System.out.println(Arrays.toString(intArr)); //[0, 1, 2, 3, 4]

LongStream longStream = LongStream.range(0, 5);
long[] longArr = longStream.toArray();
System.out.println(Arrays.toString(longArr)); //[0, 1, 2, 3, 4]

DoubleStream doubleStream = DoubleStream.of(1.1, 2.2, 3.3);
double[] doubleArr = doubleStream.toArray();
System.out.println(Arrays.toString(doubleArr)); //[1.1, 2.2, 3.3]

Primitive 2D array

Let us slightly change our previous 2D array input and convert it to a primitive 2D array. In this, we will use the Stream’s toArray with generator function with an IntStream’s toArray. In the input we used, let us change “#” and “.” to 1s and 0s.

List<String> lines = List.of(
        "11001",
        "01001",
        "00011"
);
int[][] intGrid = lines.stream()
        .map(line -> line.chars()
                .map(c -> c - '0') //IntStream
                .toArray()) // int[]
        .toArray(len -> {
            System.out.println("Number of rows in final 2D primitive int[] is " + len);
            return new int[len][];
        });
System.out.println(Arrays.deepToString(intGrid));

From the stream created from the list, for each row, we call chars() as before. But this time, we convert the character to an integer by subtracting ‘0’ (smallest possible ASCII digit). This is a nice trick to convert a char to an int when the character represents an int value (one digit). This step returns an IntStream and we call toArray() on it which gives an int[].

At last, we call toArray() to create an array of arrays (int[][]). Note that the result of map step returns a Stream<int[]>.

Running this, we get,
Number of rows in final 2D primitive int[] is 3
[[1, 1, 0, 0, 1], [0, 1, 0, 0, 1], [0, 0, 0, 1, 1]]

Conclusion

In this post, we learnt about the Java stream toArray method. We learnt how to use it to convert a stream to an array (both 1D and 2D). We also saw it being used on primitive specialization of streams.

Leave a Reply