Java streams 23. ToArray

Terminal operations either return values of other types or do not return anything at all (produce just side effects). They do not allow other operations to be applied and close the stream.

In this post we will cover two terminal operations:

Object[]  toArray(). Returns an array of values emitted by the stream.

A[]  toArray(IntFunction<A[]> generator). Returns an array of values emitted by the stream, using the provided generator function to allocate the returned array, as well as any additional arrays that might be required for a partitioned execution or for resizing.

This sentence “as well as any additional arrays that might be required for a partitioned execution or for resizing” in the last definition above looks quite enigmatic. I tried to come up with an example that would show the need for that. I also searched the net and did not find any such example. It was mentioned though that the earlier implementations may require creating some intermediate buffers. That’s all I can say about it.

We will discuss what is IntFinction and see some examples of it in the corresponding section below. 



toArray()

The toArray() usage is straightforward:

   
  Object[] res = Stream.of("a", "b", "c")
        .map(String::toUpperCase)
        .toArray();
  System.out.print(Arrays.toString(res));   //prints: [A, B, C]
   

It just collects the emitted stream values into an array of the Object class objects, no matter whether the stream is parallel or not. The only limitation is that it has to be finite. Otherwise, the processing never ends.



findAny(IntFunction)

The toArray(IntFunction<A[]>) comes handy when you need the resulting array to be of a certain type (not just Object[]).

The only purpose of the IntFunction<A[]> is to receive the size of the resulting array and return the array A[] of the requested size. For example:

   
  IntFunction<String[]> f = i -> new String[i];
  System.out.print(Arrays.toString(f.apply(3)));  
                              //prints: [null, null, null]

Let us look closer at the IntFunction<R> definition. It is a functional interface that has only one abstract method R apply(int value). In the case of the A[] toArray<IntFunction<A[]> generator) method, the integer passed into the apply() method is the size of the resulting array A[]. This means that the passed in IntFunction<A> must return A[value] array:  

   
  IntFunction<String[]> f = i -> new String[i];
  String[] res = Stream.of("a", "b", "c")
        .map(String::toUpperCase)
        .toArray(f);
  System.out.print(Arrays.toString(res));   //prints: [A, B, C]
       

In the above example, we created a stream of String values, converted each of them to upper case, and collected all the resulting values in an array String[]. The size of the array was determined automatically (and passed to the toArray() operation) by the Stream implementation.

We can inline the function definition as follows:

   
  String[] res = Stream.of("a", "b", "c")
        .map(String::toUpperCase)
        .toArray(i -> new String[i]);
  System.out.print(Arrays.toString(res));   //prints: [A, B, C]
   

Or we can use an even more compact version by using the method reference:

     
  String[] res = Stream.of("a", "b", "c")
        .map(String::toUpperCase)
        .toArray(String[]::new);
  System.out.print(Arrays.toString(res));   //prints: [A, B, C]
  


More examples

The following are slightly more complicated examples that demonstrate toArray() and toArray(IntFunction<A[]>) usage and, we hope, allow for a deeper understanding of their applicability.

We are going to use the class Person:

   
  class Person {
      private String name;

      public Person(String name) {
        this.name = name;
      }

      public String getName() { return name; }

      @Override
      public String toString() {
        return "Person{" + name + "}";
      }
  }
  

And the following is the factory method that creates an object of the class Person by using a letter as an input: 

   
  Person createPerson(String s){
      switch (s){
         case "a":
            return new Person("Alex");
         case "b":
            return new Person("Bernie");
         case "c":
            return new Person("Carol");
         default:
            return new Person("Zoi");
      }
  }
  

Using the class Person and the above factory method, we can convert the stream of String objects into an array Person[] as follows:

   
  Person[] res = Stream.of("a", "b", "c")
        .map(s -> createPerson(s))
        .toArray(Person[]::new);
  System.out.print(Arrays.toString(res));   
    //prints: [Person{Alex}, Person{Bernie}, Person{Carol}]
  

To make the code more interesting, we can convert (map) each Person object to the length of the value of the name property, which results in Integer[] array:

   
  Integer[] res = Stream.of("a", "b", "c")
        .map(s -> createPerson(s))
        .map(p -> p.getName().length())
        .toArray(Integer[]::new);
  System.out.print(Arrays.toString(res));   //prints: [4, 6, 5]
  

The two map() operators in the above example can be combined in one as follows:

   
  Integer[] res = Stream.of("a", "b", "c")
        .map(s -> createPerson(s).getName().length())
        .toArray(Integer[]::new);
  System.out.print(Arrays.toString(res));   //prints: [4, 6, 5]
  

Or if you need a primitive array int[], an operator mapToInt() can be added that converts each Integer value to int

  
  int[] res = Stream.of("a", "b", "c")
        .map(s -> createPerson(s).getName().length())
        .mapToInt(i -> i)
        .toArray();
  System.out.print(Arrays.toString(res));   //prints: [4, 6, 5]
   

And, again, we can make the processing pipeline more compact by combining operators  map() and mapToInt() as follows: 

   
  int[] res = Stream.of("a", "b", "c")
        .mapToInt(s -> createPerson(s).getName().length())
        .toArray();
  System.out.print(Arrays.toString(res));   //prints: [4, 6, 5]
  

In the next post, we will continue discussing terminal operations and discuss the most powerful (and complicated) operator reduce() that has the following overloaded versions:

--  Optional<T> reduce(BinaryOperator<T> accumulator);

--   T reduce(T identity, BinaryOperator<T> accumulator);

--   U reduce(U identity, BiFunction<U,T,U> accumulator, BinaryOperator<U> combiner);

As you can see, it is primed for providing the programmer with full control over the parallel processing and combining the results. You will probably use it very rarely because its specialized version (operator collect()) covers the vast majority of the processing needs. We will cover the operator collect() in the subsequent post. 

See other posts on Java 8 streams and posts on other topics.
You can also use navigation pages for Java stream related blogs:
— Java 8 streams blog titles
— Create stream
— Stream operations
— Stream operation collect()
The source code of all the code examples is here in GitHub

, ,

Send your comments using the link Contact or in response to my newsletter.
If you do not receive the newsletter, subscribe via link Subscribe under Contact.

Powered by WordPress. Designed by Woo Themes