Lambda Expressions and Functional Interfaces practice questions

From OCP Java SE 17 (1Z0-829) · 18 questions on this topic

Lambda Expressions and Functional Interfaces practice questions from OCP Java SE 17 (1Z0-829). This pack has 18 questions tagged Lambda Expressions and Functional Interfaces, drawn from its timed mock exams. 8 of them are worked through in full below — the question, every option, why each is right or wrong, and the explanation.

Worked examples for Lambda Expressions and Functional Interfaces

  1. Question 1

    What does this print? ```java import java.util.function.*; public class Main { public static void main(String[] args) { Predicate<String> nonEmpty = s -> !s.isEmpty(); Predicate<String> tiny = s -> s.length() < 4; Predicate<String> p = nonEmpty.and(tiny).negate(); System.out.print(p.test("abc") + " " + p.test("")); } } ```

    1. A. false trueCorrect answer

      The predicate is NOT(nonEmpty AND tiny). For "abc" both conditions hold so the and is true and negate flips it to false; for "" nonEmpty is false so the and short-circuits to false and negate flips it to true, giving false true.

    2. B. true false

      true false is the un-negated nonEmpty.and(tiny); the trailing negate() flips both results.

    3. C. true true

      "abc" satisfies both conditions, so the negated result for it cannot be true.

    4. D. false false

      "" fails nonEmpty, so the negated result for it is true, not false.

    Explanation

    The composed predicate is the negation of (nonEmpty AND tiny), and negate() applies to the whole chain built so far, not just the last clause. A three-letter non-empty string satisfies both parts, so the conjunction is true and its negation is false; the empty string fails the non-empty test, which short-circuits the conjunction to false, so its negation is true. Like &&, Predicate.and short-circuits, so the second predicate is skipped once the first yields false.

  2. Question 2

    Which method reference form matches String::length used as a Function<String,Integer>?

    1. A. A bound instance-method reference to a specific String

      A bound reference fixes the receiver when the reference is created, for example myString::length, and needs no argument for the receiver; String::length supplies no receiver, so it is not bound.

    2. B. An unbound instance-method reference: the receiver becomes the function's argumentCorrect answer

      String::length names an instance method without supplying a receiver, so as a Function<String,Integer> the single argument becomes the receiver, equivalent to s -> s.length(): this is the unbound kind, an instance method of an arbitrary object of a particular type.

    3. C. A constructor reference

      A constructor reference uses ::new, for example ArrayList::new, which is not what String::length is.

    4. D. A static-method reference

      A static-method reference names a static method, for example Integer::parseInt; length is an instance method, not static.

    Explanation

    When a method reference names an instance method on a type rather than on a specific object, no receiver is bound at creation time, so the functional interface supplies the receiver as its first argument. Used where a Function is expected, the single input plays the role of the object the method is invoked on, exactly as s -> s.length() does. A useful check: this unbound form needs one more leading parameter, the receiver, than the method's own parameter list.

  3. Question 3

    What does this print? ```java import java.util.function.*; public class Main { public static void main(String[] args) { Function<Integer,Integer> f = x -> x + 1; Function<Integer,Integer> g = x -> x * 2; System.out.println(f.compose(g).apply(3)); } } ```

    1. A. 7Correct answer

      compose applies the argument function first, then the receiver to its result: 3 * 2 = 6, then 6 + 1 = 7.

    2. B. 8

      8 is what andThen would give: receiver first (3 + 1 = 4), then argument (4 * 2 = 8), the opposite order.

    3. C. 4

      4 is the increment applied to 3 alone, ignoring the doubling function.

    4. D. 6

      6 is the doubling applied to 3 alone, ignoring the increment.

    Explanation

    compose runs the argument function before the receiver function, so the doubling happens first and the increment is applied to its result: three doubled is six, plus one is seven. This is the reverse of andThen. Because the two operations do not commute, the two chaining directions produce different answers.

  4. Question 4

    Two Consumer<String> instances are chained with andThen. What does this print? ```java import java.util.function.*; public class Main { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); Consumer<String> shout = s -> sb.append(s.toUpperCase()); Consumer<String> count = s -> sb.append(s.length()); Consumer<String> both = shout.andThen(count); both.accept("hi"); System.out.println(sb); } } ```

    1. A. 2HI

      Reverses the order, treating andThen like compose; for Consumer the receiver always runs first, so shout (HI) precedes count (2).

    2. B. HI2Correct answer

      Consumer.andThen hands the same input to both consumers in order, so shout appends HI then count appends the length 2, giving HI2.

    3. C. Compilation fails because Consumer returns void, so its result cannot be fed to another Consumer

      Assumes composition needs a value to pass along; Consumer.andThen fans the same input out to both consumers rather than piping a result, so it compiles and runs.

    4. D. HI

      Assumes andThen replaces the tail or a void consumer cannot be chained, so only shout runs; both consumers always run.

    Explanation

    Trace: `Consumer.andThen` does not pipe a result anywhere — a Consumer has no result. It returns a composed Consumer that hands the SAME input to both consumers, the receiver first and the argument second. So `both.accept("hi")` calls `shout` with "hi", appending `HI`, then calls `count` with the very same "hi", appending its length `2`. The builder holds `HI2`. Why the others are wrong: `2HI` reverses the order, treating andThen like compose; for Consumer the receiver always runs first. `HI` assumes andThen replaces the chain's tail or that a void-returning consumer cannot be chained, so only the first one runs. Both consumers always run. `Compilation fails because Consumer returns void...` encodes the belief that composition requires a value to pass along. `Function.andThen` does feed a result forward, but `Consumer.andThen` fans the same input out to both — it compiles and runs. Exam tip: distinguish the two andThen contracts. `Function.andThen(g)` = g(f(x)) — a pipeline. `Consumer.andThen(c)` = f(x); c(x) — a broadcast, same argument to each, in source order. Also note `sb` is captured legally: it is never reassigned, so it is effectively final, and MUTATING a captured object is always allowed.

  5. Question 5

    Lambdas are collected from an enhanced for loop and from a basic for loop, then invoked. What is the result of compiling and running this program? ```java import java.util.*; import java.util.function.*; public class Main { public static void main(String[] args) { List<Supplier<String>> parts = new ArrayList<>(); for (String s : List.of("a", "b")) { parts.add(() -> s); } for (int i = 0; i < 2; i++) { parts.add(() -> String.valueOf(i)); } parts.forEach(p -> System.out.print(p.get())); } } ```

    1. A. ab22

      Wrong: this assumes the lambda reads i live at invocation and sees the final value 2 twice. Locals are copied at capture, not read live - but the code never gets that far because the capture is illegal.

    2. B. Compilation fails: the basic for loop's i is not effectively final, while the enhanced-for variable s isCorrect answer

      Correct: the enhanced for loop creates a fresh, effectively final s each iteration (capturable), but the basic for loop's single i is mutated by i++, so capturing it is rejected with a not-effectively-final error.

    3. C. Compilation fails: neither s nor i may be captured, because each loop reassigns its variable on every iteration

      Wrong: this over-applies the rule to the enhanced-for variable. Deleting the basic for loop leaves a program that compiles and prints ab, which proves s alone is fine to capture.

    4. D. ab01

      Wrong: this assumes the basic for loop gives each iteration its own index, capturing 0 then 1. Per-iteration freshness is a property of the enhanced for loop only, and the code does not compile in any case.

    Explanation

    Trace: the two loops declare their variables very differently. The enhanced for loop creates a FRESH `s` on each iteration and never reassigns it, so `s` is effectively final and `() -> s` captures legally. The basic for loop declares ONE `i` and updates it with `i++`, so `i` is neither final nor effectively final and cannot be captured. javac rejects only that second lambda: "local variables referenced from a lambda expression must be final or effectively final", pointing at `i`. One error, so nothing runs. Why the others are wrong: `ab01` assumes the basic for loop also gives each iteration its own index, capturing 0 then 1. That per-iteration freshness is a property of the enhanced for loop only. `ab22` assumes the lambda reads `i` live at invocation time and so sees its final value 2 twice. Locals are copied at capture, not read live — but the code never gets that far, because the capture itself is illegal. `Compilation fails: neither s nor i...` over-applies the rule to the enhanced-for variable. Deleting the basic for loop leaves a program that compiles and prints `ab`, which proves `s` alone is fine. Exam tip: the classic fix is to copy the index into a fresh local inside the loop body — `int copy = i;` then capture `copy`, which is effectively final per iteration. Recognise the shape: capturing a loop variable is legal for `for (T x : coll)` and for a lambda's own parameters, and illegal for the mutating index of a basic for loop.

  6. Question 6

    A BinaryOperator is built with maxBy and applied to two pairs, the second of which is a tie. What does this print? ```java import java.util.Comparator; import java.util.function.*; public class Main { public static void main(String[] args) { BinaryOperator<String> pick = BinaryOperator.maxBy(Comparator.comparing(String::length)); System.out.println(pick.apply("beta", "gamma") + " " + pick.apply("alpha", "delta")); } } ```

    1. A. gamma delta

      Wrong: this gets the longer-string rule right but assumes a tie falls through to the second argument. The >= in maxBy means a tie is a win for the left operand, so alpha wins.

    2. B. gamma alphaCorrect answer

      Correct: maxBy picks the longer string (gamma over beta), and because it tests compare >= 0 it keeps the first argument (alpha) on the alpha/delta length tie.

    3. C. beta alpha

      Wrong: this is what minBy with the same comparator returns - it picks the shorter string (beta) though it too keeps the first on a tie.

    4. D. beta delta

      Wrong: this combines both errors - shortest wins (beta) and the tie goes to the second argument (delta).

    Explanation

    Trace: `BinaryOperator.maxBy(cmp)` returns the operator `(a, b) -> cmp.compare(a, b) >= 0 ? a : b`. The comparator ranks by length. First pair: "beta" is 4, "gamma" is 5, so compare is negative and the second argument `gamma` wins. Second pair: "alpha" and "delta" are both 5, so compare returns 0 — and because the test is `>= 0`, a tie keeps the FIRST argument, `alpha`. Output: `gamma alpha`. Why the others are wrong: `gamma delta` gets the longer-string rule right but assumes a tie falls through to the second argument. The `>=` in maxBy means a tie is a win for the left operand. `beta alpha` is what `BinaryOperator.minBy` with the same comparator actually returns — it picks the shorter string, and on a tie also keeps the first. `beta delta` combines both errors: shortest wins, and ties go to the second argument. Exam tip: `maxBy` and `minBy` are static factories on BinaryOperator that turn a Comparator into a two-argument reducer — and neither ever returns a "neither" or a merged value; the result is always one of the two inputs. On equal operands both return the first argument, because maxBy tests `>= 0` and minBy tests `<= 0`. That stability is why they are safe to hand to Stream.reduce.

  7. Question 7

    Which two statements about the core java.util.function interfaces are correct? (Choose two.)

    1. A. Supplier<T>'s single abstract method is get(), which takes no argumentsCorrect answer

      Supplier<T> declares T get() with no input and produces a value, so this statement is correct.

    2. B. Consumer<T>'s accept method returns the value it was given

      Consumer<T>.accept(T) returns void; a consumer uses the value for a side effect and hands nothing back, so this statement is wrong.

    3. C. Predicate<T>'s single abstract method is named test and returns booleanCorrect answer

      Predicate<T> declares boolean test(T t), taking a value and answering yes or no, so this statement is correct.

    4. D. Function<T,R> declares the abstract method accept(T)

      Function<T,R>'s abstract method is R apply(T t); accept belongs to Consumer and BiConsumer, so this statement is wrong.

    Explanation

    Each core functional interface has a fixed single-abstract-method name and signature: a supplier takes nothing and returns a value via get, a predicate takes a value and returns a boolean via test, a consumer takes a value and returns void via accept, and a function maps an input to an output via apply. The correct statements are those that pair the right method name and shape with their interface. Exam distractors typically swap a method name onto the wrong interface or claim the wrong return type.

  8. Question 8

    A constructor reference and a reference to Integer.toString are combined. What is the result of compiling and running this program? ```java import java.util.function.*; public class Main { public static void main(String[] args) { Supplier<StringBuilder> s = StringBuilder::new; Function<Integer, String> f = Integer::toString; System.out.println(s.get().append(f.apply(4)).append(f.apply(2))); } } ```

    1. A. 42

      This is what the program would print if the method reference were unambiguous (for example written with an explicit lambda); the actual reference does not compile, so nothing runs.

    2. B. 24

      Assumes the two digits are appended in the wrong order and that the code runs; the code does not compile at all.

    3. C. Compilation failsCorrect answer

      Correct — the type-qualified reference matches both the static conversion and the instance method, so it is ambiguous and rejected by the compiler (JLS 17 §15.13.1).

    4. D. Throws NullPointerException

      Assumes a null builder; the constructor reference yields a real empty StringBuilder, and in any case the ambiguity is a compile error, so there is no runtime.

    Explanation

    For a type-qualified method reference the compiler searches both for a matching static method and for an instance method that uses the first parameter as the receiver. When a method name is available in both interpretations — here as a static conversion and as an instance method on the same type — the reference is ambiguous and rejected at compile time. The constructor reference elsewhere in the code is perfectly valid and produces a real object.

Practise all 18 Lambda Expressions and Functional Interfaces questions

OCP Java SE 17 has the full set, inside timed mock exams that mirror real exam conditions — every question with a worked explanation.

Open OCP Java SE 17

Other topics in this pack