From Arrays to Streams and Back with Java 8

Home  >>  Common  >>  From Arrays to Streams and Back with Java 8

From Arrays to Streams and Back with Java 8

On November 16, 2015, Posted by , In Common,Spotlight, By ,,,, , With 4 Comments

Not long ago we upgraded some Eclipse plug-in projects to Java 8. And never looked back since. Among many other things, filtering, mapping, and finding elements in collections has become so much easier and more concise with lambdas and the streams API. Nothing new so far for the most of you, I guess.

But many existing APIs use arrays in arguments and/or return arrays. For an example, consider this fictional but nontheless common method signature:

String[] filterStrings( String... input );

And with it comes the extra effort of obtaining a stream from an array to be able to elegantly filter, map, reduce, etc. the elements. And then getting back an array that can be passed on to the old school APIs.

Forth…

To obtain a stream from an array, there are plenty of choices. For example, this line of code

Stream stream = Stream.of( "a", "b", "c" );

produces a stream with the specified elements. The same can also be achieved through:

Stream stream = Arrays.stream( "a", "b", "c" );

In fact, Stream.of() uses Arrays.stream() to accomplish the task. Making the detour via a List also results in a stream:

Stream stream = Arrays.asList( "a", "b", "c" ).stream();

Where my favorite is stream(), with the Arrays type statically imported because it is short and still distinctive.

… and Back

Once we have a stream, all stream features are available, for example, to filter empty strings from an array of Strings:

Stream.of( "a", "", "b", "", "c", "" ).filter( string -> !string.isEmpty() );

But how to get back an array with the result?

There are collectors for sets and lists and whatnot, but not for simple arrays. This code snippet

List<String> list
  = Stream.of( ... ).filter( ... ).collect( Collectors.toList() );
String[] array = list.toArray( new String[ list.size() ] );

uses toList() to obtain a list of the filtered input and then turns the list into an array in a second step.

See also  Efficient Creation of Eclipse Modules with Maven Archetype

I was almost about to implement a custom array collector to eliminate the extra step. Until I discovered that there is a terminal operation to capture the result of a stream into an array as simple as that:

String[] array = Stream.of( ... ).toArray( size -> new String[ size ] );

toArray() requires a generator, a reference to a method that is able to create an array of the requested size. Here an array of type String is created.

But wait, there is an even simpler way. As mentioned above, the generator is a function that can create an array of a requested size. And the makers of Java 8 were so kind to introduce some syntactic sugar to directly reference an array constructor.

By adding an opening and closing square bracket to a constructor reference, an array constructor reference can be expressed, e.g. Type[]::new. Hence the above line can be rewritten like so:

String[] array = Stream.of( ... ).toArray( String[]::new );

The compiler extends the String[]::new expression to size -> new String[ size ]. And therefore the generated byte code is the same as with the previous approach but I find the latter much more concise.

And moreover, it eliminates the admittedly unlikely but still possible error of getting the size of the generated array wrong. Consider this:

String[] array = Stream.of( "a", "b", "c" ).toArray( size -> new String[ 1 ] );

The created array is obviously too small. Its actual size (one) will never be able to hold the three resulting elements. And thus will end up in an IllegalStateException. When using the array constructor reference, the compiler will ensure to create an appropriately sized array.

See also  Announcing Extras for Eclipse

Of course, there is also a generic toArray() method that returns an array of Objects and can be used if the actual type of the resulting array doesn’t matter.

Concluding from Arrays to Streams and Back

Like my dear colleague Ralf, many programmers prefer collections over arrays in API interfaces. But there are still many ‘old-fashioned’ APIs that require you to deal with arrays. And as it is with APIs, those won’t go away soon.

But whichever way you prefer, or whichever way you are forced to go through existing code, I found it good news that Java 8 provides a decent bridge between the two worlds.

If you have questions, suggestions or would like to share your experiences in this area, please leave a comment.

Rüdiger Herrmann
Follow me
Latest posts by Rüdiger Herrmann (see all)

4 Comments so far:

  1. rangzen says:

    Hello, and how do you deal with primitives ? Thanks

    • Rüdiger Herrmann says:

      Good point! For arrays of int, long, and double the class Arrays provides overloaded stream() methods that return specialized streams (i.e. IntStream for int’s, etc). These streams also offer a typed toArray() method.

      For example:

      int[] numbers = { -1, 0, 1, 2 };
      int[] filteredNumbers = Arrays.stream( numbers ).filter( i -> i > 0 ).toArray();
      

      creates an IntStream, applies the filter and uses its toArray() method to get back an array of int.

      For other primitive arrays you will need to convert them into one of the above mentioned forms.

  2. rangzen says:

    And of course, unfortunaly, I’m struggling with an old API and I’m stuck to primitive float so I keep some ugly loop to convert things…

    Best explanation found : http://stackoverflow.com/questions/22918847/why-are-new-java-util-arrays-methods-in-java-8-not-overloaded-for-all-the-primit/22919112#22919112

  3. Bruno says:

    “Forth…”

    To obtain a stream from an array: Stream stream = Stream.of( “a”, “b”, “c” );

    “a”, “b”, “c” is not an array at all. It’s a parameter list. Two completely distinct beasts.