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 in order 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.
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" );
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();
… 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() ] );
toList() to obtain a list of the filtered input and then turns the list into an array in a second step.
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 );
String::new expression is expanded to
size -> new String[ size ] by the compiler. 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.
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.