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
count methods all in the same question.
To determine what the code does, you must understand several things:
- The behavior of the
- The remainder operator (
- The behavior of
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
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
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
Next, consider how the three methods (
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
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
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
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
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
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
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
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.