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:
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.
To obtain a stream from an array, there are plenty of choices. For example, this line of code
produces a stream with the specified elements. The same can also be achieved through:
In fact, Stream.of() uses Arrays.stream() to accomplish the task. Making the detour via a List also results in a 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:
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
uses 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:
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:
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:
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.
How to initialize a Java class
Before we explore Java’s support for class initialization, let’s recap the steps of initializing a Java class. Consider Listing 1.
Listing 1. Initializing class fields to default values
Listing 1 declares class SomeClass . This class declares nine fields of types boolean , byte , char , double , float , int , long , short , and String . When SomeClass is loaded, each field’s bits are set to zero, which you interpret as follows:
The previous class fields were implicitly initialized to zero. However, you can also explicitly initialize class fields by directly assigning values to them, as shown in Listing 2.
Listing 2. Initializing class fields to explicit values
Each assignment’s value must be type-compatible with the class field’s type. Each variable stores the value directly, with the exception of st . Variable st stores a reference to a String object that contains abc .
Referencing class fields
When initializing a class field, it’s legal to initialize it to the value of a previously initialized class field. For example, Listing 3 initializes y to x ‘s value. Both fields are initialized to 2 .
Listing 3. Referencing a previously declared field
However, the reverse is not legal: you cannot initialize a class field to the value of a subsequently declared class field. The Java compiler outputs illegal forward reference when it encounters this scenario. Consider Listing 4.
Listing 4. Attempting to reference a subsequently declared field
The compiler will report illegal forward reference when it encounters static int x = y; . This is because source code is compiled from the top down, and the compiler hasn’t yet seen y . (It would also output this message if y wasn’t explicitly initialized.)