10 Java 8 Lambda Interview Questions and Answers
Prepare for your next interview with this guide on Java 8 Lambda expressions, featuring common questions and detailed answers to enhance your understanding.
Prepare for your next interview with this guide on Java 8 Lambda expressions, featuring common questions and detailed answers to enhance your understanding.
Java 8 introduced a host of new features, with Lambda expressions being one of the most significant. Lambda expressions enable functional programming in Java, allowing developers to write more concise and flexible code. This feature has revolutionized how Java developers approach tasks such as iteration, filtering, and mapping, making code more readable and maintainable.
This article provides a curated selection of interview questions focused on Java 8 Lambda expressions. By working through these questions and their detailed answers, you will gain a deeper understanding of how to effectively utilize Lambda expressions in real-world scenarios, enhancing your readiness for technical interviews.
In Java 8, lambda expressions offer a concise way to represent single-method interfaces. They are useful for defining small code snippets that can be passed as arguments. Here’s an example of a lambda expression that takes two integers and returns their sum:
BinaryOperator<Integer> sum = (a, b) -> a + b;
In this example, BinaryOperator<Integer>
is a functional interface that takes two integers and returns an integer. The lambda expression (a, b) -> a + b
performs the addition.
To filter a list of strings that start with a specific letter using a lambda expression and the Stream API, you can use the filter
method. This method takes a predicate as an argument, which can be represented using a lambda expression.
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class LambdaExample { public static void main(String[] args) { List<String> strings = Arrays.asList("apple", "banana", "avocado", "cherry", "apricot"); char specificLetter = 'a'; List<String> filteredStrings = strings.stream() .filter(s -> s.startsWith(String.valueOf(specificLetter))) .collect(Collectors.toList()); System.out.println(filteredStrings); } }
In this example, the filter
method retains only those strings that start with the specified letter.
A predicate in Java 8 is a functional interface that takes a single argument and returns a boolean. To create a predicate that checks if a number is even, you can use a lambda expression.
Example:
import java.util.function.Predicate; public class EvenNumberPredicate { public static void main(String[] args) { Predicate<Integer> isEven = number -> number % 2 == 0; System.out.println(isEven.test(4)); // true System.out.println(isEven.test(7)); // false } }
The Supplier functional interface represents a supplier of results. It has a single abstract method get()
that returns a result. To create a supplier that returns a random integer, use the Random class.
Example:
import java.util.Random; import java.util.function.Supplier; public class RandomIntegerSupplier { public static void main(String[] args) { Supplier<Integer> randomIntSupplier = () -> new Random().nextInt(); System.out.println(randomIntSupplier.get()); } }
In this example, the lambda expression calls new Random().nextInt()
to generate a random integer.
To create a function that converts a string to uppercase, use the Function
interface. This interface represents a function that takes one argument and produces a result.
Example:
import java.util.function.Function; public class Main { public static void main(String[] args) { Function<String, String> toUpperCase = (s) -> s.toUpperCase(); String result = toUpperCase.apply("hello world"); System.out.println(result); // Output: HELLO WORLD } }
The BiFunction interface takes two arguments and returns a result. To implement a BiFunction that concatenates two strings, use a lambda expression.
Example:
import java.util.function.BiFunction; public class Main { public static void main(String[] args) { BiFunction<String, String, String> concatenate = (str1, str2) -> str1 + str2; String result = concatenate.apply("Hello, ", "world!"); System.out.println(result); // Output: Hello, world! } }
A functional interface contains exactly one abstract method and can be used with lambda expressions. The @FunctionalInterface
annotation indicates that the interface is intended to be a functional interface.
Example:
@FunctionalInterface interface CustomFunctionalInterface { void execute(String message); } public class LambdaExample { public static void main(String[] args) { CustomFunctionalInterface customFunction = (message) -> System.out.println("Message: " + message); customFunction.execute("Hello, Lambda!"); } }
In this example, CustomFunctionalInterface
is a functional interface with a single abstract method execute
.
The Stream API operations are divided into intermediate and terminal operations. Intermediate operations transform a stream into another stream and are lazy, meaning they do not process elements until a terminal operation is invoked. Examples include filter()
, map()
, and sorted()
. Terminal operations produce a result or a side-effect and mark the end of the stream processing. Examples include forEach()
, collect()
, and reduce()
.
Example:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); // Intermediate operations: filter and map List<String> filteredNames = names.stream() .filter(name -> name.startsWith("A")) .map(String::toUpperCase) .collect(Collectors.toList()); // Terminal operation: collect System.out.println(filteredNames); // Output: [ALICE] } }
In this example, filter
and map
are intermediate operations, while collect
is the terminal operation.
The Optional class handles cases where a value may or may not be present, providing a way to avoid null pointer exceptions. It is useful in method return types where a value might be missing.
Example:
import java.util.Optional; public class OptionalExample { public static void main(String[] args) { Optional<String> optionalValue = getValue(); // Using ifPresent to execute a block of code if the value is present optionalValue.ifPresent(value -> System.out.println("Value is: " + value)); // Using orElse to provide a default value if the value is not present String result = optionalValue.orElse("Default Value"); System.out.println("Result is: " + result); } public static Optional<String> getValue() { // Returning an Optional containing a value return Optional.of("Hello, World!"); // To return an empty Optional, use Optional.empty() // return Optional.empty(); } }
Collectors in the Stream API perform mutable reduction operations on stream elements, typically accumulating them into a collection or performing aggregation operations. The Collectors class provides static methods that return instances of the Collector interface.
Example:
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class CollectorsExample { public static void main(String[] args) { List<String> items = Arrays.asList("apple", "banana", "orange", "apple", "banana", "apple"); // Grouping by item name and counting the occurrences Map<String, Long> itemCount = items.stream() .collect(Collectors.groupingBy(item -> item, Collectors.counting())); System.out.println(itemCount); } }
In this example, the stream of items is grouped by the item name, and the occurrences of each item are counted using the groupingBy
and counting
collectors.