15 Java Streams Interview Questions and Answers
Prepare for your next Java interview with our guide on Java Streams. Enhance your skills with curated questions and answers for effective data processing.
Prepare for your next Java interview with our guide on Java Streams. Enhance your skills with curated questions and answers for effective data processing.
Java Streams, introduced in Java 8, revolutionized the way developers handle collections and data processing. By providing a functional approach to operations on sequences of elements, Java Streams enable more readable, concise, and efficient code. This powerful feature is essential for modern Java development, allowing for parallel processing and complex data manipulation with ease.
This article offers a curated selection of interview questions focused on Java Streams, designed to help you demonstrate your proficiency and understanding of this critical feature. By familiarizing yourself with these questions and their answers, you’ll be better prepared to showcase your expertise and problem-solving abilities in your next technical interview.
To filter out all even numbers from a list and then map each remaining number to its square using Java Streams, you can use the filter
and map
methods. The filter
method excludes elements that do not match a given predicate, and the map
method transforms each element in the stream.
Example:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class StreamExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<Integer> result = numbers.stream() .filter(n -> n % 2 != 0) .map(n -> n * n) .collect(Collectors.toList()); System.out.println(result); } }
To collect all unique words from a list of sentences into a set, use streams to process collections in a functional style. You can use flatMap
to split sentences into words and collect
to gather them into a set.
import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class UniqueWords { public static void main(String[] args) { List<String> sentences = Arrays.asList( "This is a sentence", "This is another sentence", "And this is yet another sentence" ); Set<String> uniqueWords = sentences.stream() .flatMap(sentence -> Arrays.stream(sentence.split(" "))) .collect(Collectors.toSet()); System.out.println(uniqueWords); } }
To sort a list of employees by their salary in descending order, use the sorted
method with a comparator.
Example:
import java.util.*; import java.util.stream.*; class Employee { private String name; private double salary; public Employee(String name, double salary) { this.name = name; this.salary = salary; } public double getSalary() { return salary; } @Override public String toString() { return "Employee{name='" + name + "', salary=" + salary + "}"; } } public class Main { public static void main(String[] args) { List<Employee> employees = Arrays.asList( new Employee("John", 50000), new Employee("Jane", 60000), new Employee("Doe", 55000) ); List<Employee> sortedEmployees = employees.stream() .sorted(Comparator.comparingDouble(Employee::getSalary).reversed()) .collect(Collectors.toList()); sortedEmployees.forEach(System.out::println); } }
To transform a list of lists of integers into a single list, use the flatMap
method to flatten the nested lists.
Example:
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FlattenList { public static void main(String[] args) { List<List<Integer>> listOfLists = Arrays.asList( Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9) ); List<Integer> flattenedList = listOfLists.stream() .flatMap(List::stream) .collect(Collectors.toList()); System.out.println(flattenedList); } }
To sum all the integers in a list using a stream, use the reduce
method, which combines elements using a binary operator.
Example:
import java.util.Arrays; import java.util.List; public class SumUsingStreams { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream().reduce(0, Integer::sum); System.out.println("Sum: " + sum); } }
To convert a sequential stream to a parallel stream, use the parallel()
method. This can be beneficial for large datasets or computationally intensive operations, but be aware of potential overhead and thread safety issues.
Example:
import java.util.Arrays; import java.util.List; public class StreamExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // Sequential Stream numbers.stream() .forEach(System.out::println); // Parallel Stream numbers.stream() .parallel() .forEach(System.out::println); } }
To implement a custom collector that concatenates strings with a delimiter, define the supplier, accumulator, combiner, and finisher methods.
Example:
import java.util.stream.Collector; import java.util.function.Supplier; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.Set; import java.util.HashSet; public class StringConcatenator implements Collector<String, StringBuilder, String> { private final String delimiter; public StringConcatenator(String delimiter) { this.delimiter = delimiter; } @Override public Supplier<StringBuilder> supplier() { return StringBuilder::new; } @Override public BiConsumer<StringBuilder, String> accumulator() { return (sb, s) -> { if (sb.length() > 0) { sb.append(delimiter); } sb.append(s); }; } @Override public BinaryOperator<StringBuilder> combiner() { return (sb1, sb2) -> { if (sb1.length() > 0 && sb2.length() > 0) { sb1.append(delimiter); } sb1.append(sb2); return sb1; }; } @Override public Function<StringBuilder, String> finisher() { return StringBuilder::toString; } @Override public Set<Characteristics> characteristics() { return new HashSet<>(); } public static void main(String[] args) { String result = java.util.Arrays.asList("a", "b", "c") .stream() .collect(new StringConcatenator(", ")); System.out.println(result); // Output: a, b, c } }
To handle checked exceptions within a stream pipeline, use a wrapper method that catches the checked exception and rethrows it as an unchecked exception.
Example:
import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; public class StreamExceptionHandling { public static void main(String[] args) { List<String> data = Arrays.asList("1", "2", "a", "4"); List<Integer> result = data.stream() .map(wrapper(StreamExceptionHandling::parseInt)) .collect(Collectors.toList()); System.out.println(result); } public static Integer parseInt(String s) throws NumberFormatException { return Integer.parseInt(s); } public static <T, R> Function<T, R> wrapper(CheckedFunction<T, R> checkedFunction) { return i -> { try { return checkedFunction.apply(i); } catch (Exception e) { throw new RuntimeException(e); } }; } @FunctionalInterface public interface CheckedFunction<T, R> { R apply(T t) throws Exception; } }
To group a list of people objects by their age, use the Collectors.groupingBy
method.
Example:
import java.util.*; import java.util.stream.Collectors; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public int getAge() { return age; } public String getName() { return name; } } public class Main { public static void main(String[] args) { List<Person> people = Arrays.asList( new Person("Alice", 30), new Person("Bob", 25), new Person("Charlie", 30), new Person("David", 25) ); Map<Integer, List<Person>> groupedByAge = people.stream() .collect(Collectors.groupingBy(Person::getAge)); groupedByAge.forEach((age, persons) -> { System.out.println("Age: " + age); persons.forEach(person -> System.out.println(" - " + person.getName())); }); } }
To partition a list of integers into even and odd numbers, use the partitioningBy
collector.
Example:
import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class PartitionExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); Map<Boolean, List<Integer>> partitioned = numbers.stream() .collect(Collectors.partitioningBy(n -> n % 2 == 0)); List<Integer> evenNumbers = partitioned.get(true); List<Integer> oddNumbers = partitioned.get(false); System.out.println("Even Numbers: " + evenNumbers); System.out.println("Odd Numbers: " + oddNumbers); } }
To concatenate two streams of integers into a single stream, use the Stream.concat
method.
Example:
import java.util.stream.Stream; public class StreamConcatenation { public static void main(String[] args) { Stream<Integer> stream1 = Stream.of(1, 2, 3); Stream<Integer> stream2 = Stream.of(4, 5, 6); Stream<Integer> concatenatedStream = Stream.concat(stream1, stream2); concatenatedStream.forEach(System.out::println); } }
To calculate the average of a list of integers using IntStream, use the average()
method, which returns an OptionalDouble.
Example:
import java.util.Arrays; import java.util.List; import java.util.OptionalDouble; import java.util.stream.IntStream; public class AverageCalculator { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); OptionalDouble average = numbers.stream() .mapToInt(Integer::intValue) .average(); average.ifPresent(System.out::println); } }
Parallel streams can improve performance for certain tasks by leveraging multiple CPU cores. They are useful for CPU-bound operations where the workload can be divided into independent subtasks. However, consider the following:
Example:
import java.util.Arrays; import java.util.List; public class ParallelStreamExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // Using parallel stream int sum = numbers.parallelStream() .mapToInt(Integer::intValue) .sum(); System.out.println("Sum: " + sum); } }
Short-circuiting operations in streams allow processing to terminate early when a condition is met, optimizing performance.
Examples include:
Example:
import java.util.Arrays; import java.util.List; public class ShortCircuitingExample { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // findFirst example Integer first = numbers.stream() .filter(n -> n > 3) .findFirst() .orElse(null); System.out.println("First number greater than 3: " + first); // anyMatch example boolean anyMatch = numbers.stream() .anyMatch(n -> n > 8); System.out.println("Any number greater than 8: " + anyMatch); } }
To combine multiple stream operations efficiently, understand the difference between intermediate and terminal operations. Intermediate operations are lazy and do not process the stream until a terminal operation is invoked, allowing for efficient processing.
Example:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David"); List<String> result = names.stream() .filter(name -> name.startsWith("A")) .map(String::toUpperCase) .collect(Collectors.toList());
In this example, filter
and map
are intermediate operations, and collect
is a terminal operation. By combining these operations in a single pipeline, the stream processes each element only once, reducing overhead.