Quiz yourself: Using core functional interfaces (intermediate)

See if you know how to use the predicate, consumer, function, and supplier interfaces.

June 8, 2020 | Download a PDF of this article
More quiz questions available here

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. The “intermediate” and “advanced” designations refer to the exams rather than to the questions, although in almost all cases, “advanced” questions will be harder. 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.

The objective in this Java SE 11 quiz is to use core functional interfaces.

Given the following code fragment:


public static void main(String[] args) throws InterruptedException,
                                              ExecutionException {
  var cf = CompletableFuture.supplyAsync(() -> "Java");
  cf = cf.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + "11"));
  System.out.print(cf.get());
}

Which functional interface instance is passed into the thenCompose method? Choose one.

A. java.lang.Runnable
B. java.util.concurrent.Callable
C. java.util.function.Function
D. java.util.function.BiConsumer
E. java.util.function.Supplier
 

Answer. At first sight, this question appears to require knowledge of the CompletableFuture API, but in fact it does not. In the exam, you might see a question that includes unfamiliar APIs that you’re sure aren’t addressed by the objectives. If you’re right about that, there’s a good chance the question doesn’t actually hinge on those specific APIs and can be answered without knowledge of them. This is such a question.

However, the exam objectives do require you to know about the main functional interfaces in Java 11, and the objective for this question falls under that umbrella. Even though there are many functional interfaces, it’s probably sufficient to be familiar with the general forms and to watch out for some permutations:

  • Supplier takes no arguments and returns something.
  • Consumer takes one or more arguments and returns void.
  • Function takes one or more arguments and returns something.
  • Predicate takes one or more arguments and returns a primitive boolean.
  • Operator variants (Binary and Unary) constrain the arguments and return types to be the same.
  • The Bi prefix implies two arguments instead of one.
  • The prefixes Int, Long, Double, ToInt, ToLong, and ToDouble imply arguments or return types that are primitive.

Those rules cover 40 of the 43 interfaces defined in the java.util.function package.

There are three more interfaces you should probably be familiar with:

  • Runnable, which takes no arguments and returns void
  • Callable, which takes no arguments, returns a generic type, and throws exceptions
  • Comparator, which takes two arguments of the same type and returns a primitive int

Let’s investigate what is passed in the thenCompose method:


s -> CompletableFuture.supplyAsync(() -> s + "11")

You can determine several things about this lambda simply by inspecting the question’s code:

  • You can see that on the left of the -> is a single variable s. So, you know the lambda takes a single argument. It’s fine that no parentheses surround the argument because it’s a single argument that carries neither an explicit type nor the var pseudotype.
  • The body is a simple expression. The body lacks curly braces and the return keyword, which are optional for so-called “expression lambdas.”
  • The returned type is not void. You know this because the same method call (CompletableFuture.supplyAsync) is used on the previous line in assigning a value to variable cf.

So, you know you have a lambda that takes one parameter. Let’s consider the options.

Option A suggests a Runnable but the abstract method of Runnable has this form:


public abstract void run();

That is, it accepts no parameters and returns nothing. Because it accepts zero parameters, it cannot be the target type of the lambda, and option A is incorrect.

Option B suggests a Callable and option E suggests a Supplier. However, the forms of the abstract methods in these interfaces both take zero arguments, as shown below.

The Callable interface has this form:


public interface Callable<V> {
  V call() throws Exception;
}

And the Supplier interface has this form:


public interface Supplier<T> {
  T get();
}

Because of this, you can reject both option B and option E as incorrect.

Option D suggests a BiConsumer, but as the name suggests, that would require two arguments:


public interface BiConsumer<T, U> {
  void accept(T t, U u);
}

Because BiConsumer requires two arguments, option D must be incorrect.

Option C suggests a Function. This interface declares an abstract method that takes one argument, like this:


public interface Function<T, U>  {
  U apply(T t);
}

Because the argument count of this method matches the requirement, option C is correct. And, yes, probably unsurprisingly, the code prints Java11 to the console.

It’s interesting to consider whether you could glean any information from the returned expression of the lambda. Is it safe to suppose that the method this lambda implements must return a value, or could this lambda possibly implement, for example, the Consumer interface’s accept method, which returns void? You know the method shown returns a non-void type, but Java allows function-returned values to be ignored. A very simple example of this is the add method in the interface List. Most of the time, you use the add method and ignore the returned value:


List<String> ls = new ArrayList<>();
ls.add("Text"); // boolean return ignored

But in fact, the add method returns a boolean value.

The option to ignore the returned value from a method call existed in C and C++ and was adopted by Java because it’s quite useful. In the case of the add method on a List, it’s unlikely to ever return false, and ignoring the returned value is the norm.

You might wonder why the returned value even exists. This is because the add method implements add in the Collection and indicates whether the addition was successful. In the case of a Set, the add method returns false if the Set already contained the offered object.

But regardless of the reasons for why this behavior is allowed, it is legal and because of this, the lambda originally described in this question could in fact be assigned to a Consumer (though not to a BiConsumer) without causing errors.

The correct answer is option C.

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.

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.

Share this Page