Quiz yourself: Use Java streams to filter, transform, and process data

The average, sum, and count methods are trickier than they might seem.

February 1, 2021

If you have worked on our quiz questions in the past, you know none of them is easy. They model the difficult questions from certification examinations. We write questions for the certification exams, and we intend that the same rules apply: Take words at their face value and trust that the questions are not intended to deceive you but to straightforwardly test your knowledge of the ins and outs of the language.

Given this code fragment:

var is = IntStream.range(1, 5);
var avg = is.filter(i -> i % 5 == 0).average(); // line n1

is = IntStream.range(1, 5);
var sum = is.filter(i -> i % 2 != 0).sum(); // line n2

is = IntStream.range(1, 5);
var count = is.filter(i-> i % 5 == 0).count(); // line n3

System.out.print(avg + " " + sum + " " + count);

Which of the following is the result? Choose one.

A. Compilation fails at line n1.

B. Compilation fails at line n2.

C. Compilation fails at line n3.

D. Compilation succeeds and there’s a runtime exception at line n1.

E. Compilation succeeds and the output is 0.0 4 0.

F. None of the above.

Answer. Since the introduction of the new 1Z0-819 Java SE exam, it’s become normal to have a question that tests many objectives in a single question. This can make the questions seem harder than those in the previous 1Z0-815 and 1Z0-816 exams, which mostly focused on a single topic per question. This Java SE 11 quiz question adopts the new format, because it asks about the average, sum, and count methods all in the same question.

To determine what the code does, you must understand several things:

  • The behavior of the range method
  • The remainder operator (%)
  • The behavior of average, sum, and count

Consider the range method first. It creates a stream of values that starts with the value of the first argument and ends before the second argument. That is, the stream includes the first argument, and it excludes the last. So, in this case, all three streams contain exactly and only the values 1, 2, 3, and 4.

Now, what will you get if you process those four values using the % operator, as shown in this example? For each of these values, the “remainder 5” operation will give a result that’s exactly the same as the input value. That is, 1 % 5 = 1, 2 % 5 = 2, and so on. Because none of the four items is exactly divisible by 5, there is always a nonzero remainder. Thus, the first and third streams are both empty after the filter operation.

The second stream is filtered so it contains only the items that are not exactly divisible by 2. That is, it will contain the values 1 and 3.

Next, consider how the three methods (average, sum, and count) return their values. An important consideration is that these methods can be called on a stream that’s empty. Indeed, in this example, that happens in two of the three situations.

The signatures of these three methods are

  • OptionalDouble average()
  • int sum()
  • long count()

For an empty stream, a sum is zero, and the count is also zero. However, since an average is computed as the sum divided by the count, an empty stream cannot provide a defined value for average because you can’t divide by zero.

As a result, the sum and count methods can return simple primitive values, but the average method returns an OptionalDouble. In this way, the average method is able to distinguish the return of a valid average value from the absence of any such value. Because the average method reports an undefined average in this way and does not throw an exception, you can determine that option D is incorrect.

These nuggets of information fit together to lead to a result. All of the syntax and the method invocations are valid, so compilation does not fail. That tells you that options A, B, and C are all incorrect.

The value returned by the average method must be an empty OptionalDouble since the stream was empty. The result of the sum method will be 4 (being the sum of 1 and 3, which are the two values to get past that particular filter), and the count will be zero. Consequently, the output will be this:

OptionalDouble.empty 4 0

The result of converting the empty OptionalDouble to text is the message OptionalDouble.empty. However, even if you didn’t know this, you should still be able to rule out option E as incorrect, since you know that the result is definitely not an average with a value of zero.

Therefore, the remaining answer, option F, must be correct since the actual behavior does not match any of the other specific options.

Here are four final things to be aware of:

  • Java provides primitive variants for int, long, and double in streams, Optionals, and the functional interfaces to allow the avoidance of unnecessary boxing and unboxing.
  • The primitive stream types also provide additional terminal operations (including average and sum, as used in this question). These methods make good sense in primitive numeric streams but would not be applicable to a stream containing arbitrary objects.
  • This question deliberately used the var pseudotype for the declaration of the avg, sum, and count variables. If the avg variable had instead been declared as having the double type, the code would have failed to compile—and option A would have been correct.
  • Streams are stateful in the same way that an Iterator is, because both keep track of progress through the data. Consequently, once every data item has been taken from a stream, that stream has reached its end and cannot be used again. Therefore, it’s necessary for a new stream to be created for each of the three computations that lead to the average, sum, and count. If the new streams were not created, a runtime exception would have been thrown at line n2.

Conclusion: The correct answer is option F.

Mikalai Zaikin

Mikalai Zaikin is a lead Java developer at IBA IT Park in Minsk, Belarus. During his career, he has helped Oracle with development of Java certification exams, and he has been a technical reviewer of several Java certification books, including three editions of the famous Sun Certified Programmer for Java study guides by Kathy Sierra and Bert Bates.

Simon Roberts

Simon Roberts joined Sun Microsystems in time to teach Sun’s first Java classes in the UK. He created the Sun Certified Java Programmer and Sun Certified Java Developer exams. He wrote several Java certification guides and is currently a freelance educator who publishes recorded and live video training through Pearson InformIT (available direct and through the O’Reilly Safari Books Online service). He remains involved with Oracle’s Java certification projects.

Share this Page