Interview

15 Java Stream API Interview Questions and Answers

Prepare for your Java interview with this guide on Java Stream API, featuring common questions and detailed answers to enhance your understanding.

Java Stream API is a powerful feature introduced in Java 8 that allows developers to process collections of objects in a functional programming style. It provides a concise and efficient way to perform operations such as filtering, mapping, and reducing on data sets, making code more readable and maintainable. The Stream API is particularly useful for handling large data sets and parallel processing, which can significantly improve performance.

This article offers a curated selection of interview questions focused on the Java Stream API. By working through these questions and their detailed answers, you will gain a deeper understanding of how to leverage this feature effectively, enhancing your problem-solving skills and technical proficiency for your upcoming interview.

Java Stream API Interview Questions and Answers

1. Write a stream operation to filter out even numbers from a list of integers and then square each remaining number.

The Java Stream API offers a functional approach to processing sequences of elements, allowing for operations like filtering, mapping, and reducing. This enables complex data processing tasks to be performed in a readable and concise manner.

To filter out even numbers from a list of integers and square each remaining number, use the following stream operations:

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);
    }
}

In this example, filter excludes even numbers, and map squares the remaining numbers. The collect method gathers the results into a new list.

2. Use a stream to collect all unique words from a list of sentences into a set.

To collect all unique words from a list of sentences into a set, use the Stream API to process the sentences, split them into words, and collect the unique words:

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class UniqueWordsCollector {
    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);
    }
}

3. How would you handle a situation where a stream operation might return an empty result?

When a stream operation might return an empty result, use the Optional class to handle it gracefully. This avoids NullPointerException and allows for a default value or alternative action.

Example:

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        Optional<Integer> maxNumber = numbers.stream()
                                             .filter(n -> n > 10)
                                             .max(Integer::compareTo);

        maxNumber.ifPresentOrElse(
            System.out::println,
            () -> System.out.println("No value found")
        );
    }
}

In this example, the stream filters numbers greater than 10, resulting in an empty stream. The max() operation returns an Optional<Integer>, and ifPresentOrElse is used to print the value if present or “No value found” if not.

4. Implement a custom collector to concatenate strings from a stream with a delimiter.

To implement a custom collector for concatenating strings with a delimiter, define the supplier, accumulator, combiner, and finisher methods:

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;
import java.util.stream.Stream;

public class StringConcatenationCollector implements Collector<String, StringBuilder, String> {
    private final String delimiter;

    public StringConcatenationCollector(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) {
        Stream<String> stream = Stream.of("apple", "banana", "cherry");
        String result = stream.collect(new StringConcatenationCollector(", "));
        System.out.println(result); // Output: apple, banana, cherry
    }
}

5. Generate an infinite stream of random numbers and limit it to 10 elements.

To generate an infinite stream of random numbers and limit it to 10 elements, use Stream.generate with limit:

import java.util.Random;
import java.util.stream.Stream;

public class RandomNumberStream {
    public static void main(String[] args) {
        Random random = new Random();
        Stream.generate(random::nextInt)
              .limit(10)
              .forEach(System.out::println);
    }
}

In this example, Stream.generate(random::nextInt) creates an infinite stream of random integers, and limit(10) truncates it to the first 10 elements.

6. Use a stream to calculate the product of all elements in a list of integers.

To calculate the product of all elements in a list of integers, use the reduce operation:

import java.util.Arrays;
import java.util.List;

public class StreamProductExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        int product = numbers.stream()
                             .reduce(1, (a, b) -> a * b);
        System.out.println("Product of all elements: " + product);
    }
}

In this example, reduce multiplies all elements in the list, with an initial value of 1.

7. How would you handle checked exceptions within a stream pipeline?

To handle checked exceptions within a stream pipeline, wrap the checked exception in an unchecked exception or use a custom functional interface:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExceptionHandling {

    @FunctionalInterface
    public interface CheckedFunction<T, R> {
        R apply(T t) throws Exception;
    }

    public static <T, R> java.util.function.Function<T, R> wrap(CheckedFunction<T, R> function) {
        return t -> {
            try {
                return function.apply(t);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }

    public static void main(String[] args) {
        List<String> data = Arrays.asList("1", "2", "a", "4");

        List<Integer> result = data.stream()
            .map(wrap(s -> Integer.parseInt(s)))
            .collect(Collectors.toList());

        System.out.println(result);
    }
}

In this example, the wrap method handles the checked exception thrown by Integer.parseInt.

8. Demonstrate how to use the peek method to debug a stream pipeline.

The peek method allows you to perform an action on each element of the stream, useful for debugging:

import java.util.Arrays;
import java.util.List;

public class StreamPeekExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        numbers.stream()
               .peek(n -> System.out.println("Original number: " + n))
               .map(n -> n * 2)
               .peek(n -> System.out.println("After map: " + n))
               .filter(n -> n > 5)
               .peek(n -> System.out.println("After filter: " + n))
               .forEach(n -> System.out.println("Final number: " + n));
    }
}

In this example, peek is used to print elements at different stages of the stream pipeline.

9. Create a custom spliterator to split a stream of integers into chunks of a specified size.

A custom Spliterator can split a stream of integers into chunks of a specified size:

import java.util.Spliterator;
import java.util.function.Consumer;

public class ChunkSpliterator implements Spliterator<int[]> {
    private final int[] array;
    private int currentIndex = 0;
    private final int chunkSize;

    public ChunkSpliterator(int[] array, int chunkSize) {
        this.array = array;
        this.chunkSize = chunkSize;
    }

    @Override
    public boolean tryAdvance(Consumer<? super int[]> action) {
        if (currentIndex < array.length) {
            int end = Math.min(currentIndex + chunkSize, array.length);
            int[] chunk = new int[end - currentIndex];
            System.arraycopy(array, currentIndex, chunk, 0, chunk.length);
            action.accept(chunk);
            currentIndex = end;
            return true;
        }
        return false;
    }

    @Override
    public Spliterator<int[]> trySplit() {
        int remainingSize = array.length - currentIndex;
        if (remainingSize <= chunkSize) {
            return null;
        }
        int splitSize = remainingSize / 2;
        int splitEnd = currentIndex + splitSize;
        ChunkSpliterator split = new ChunkSpliterator(array, chunkSize);
        split.currentIndex = splitEnd;
        return split;
    }

    @Override
    public long estimateSize() {
        return (array.length - currentIndex + chunkSize - 1) / chunkSize;
    }

    @Override
    public int characteristics() {
        return ORDERED | SIZED | SUBSIZED;
    }
}

10. Use the Stream.Builder class to construct a stream of strings.

The Stream.Builder class allows for the construction of a stream in a flexible manner:

import java.util.stream.Stream;

public class StreamBuilderExample {
    public static void main(String[] args) {
        Stream<String> stringStream = Stream.<String>builder()
            .add("Hello")
            .add("World")
            .add("Stream")
            .add("Builder")
            .build();

        stringStream.forEach(System.out::println);
    }
}

11. Generate a stream of the first 10 Fibonacci numbers using the iterate method.

To generate the first 10 Fibonacci numbers using the iterate method:

import java.util.stream.Stream;

public class Fibonacci {
    public static void main(String[] args) {
        Stream.iterate(new int[]{0, 1}, fib -> new int[]{fib[1], fib[0] + fib[1]})
              .limit(10)
              .map(fib -> fib[0])
              .forEach(System.out::println);
    }
}

In this example, iterate starts with {0, 1} and updates the pair to the next Fibonacci numbers.

12. Optimize a stream operation that filters, maps, and collects data to minimize processing time.

To optimize a stream operation that filters, maps, and collects data, use parallel streams and ensure efficient chaining of operations:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class StreamOptimization {
    public static void main(String[] args) {
        List<Integer> numbers = IntStream.range(1, 1000000).boxed().collect(Collectors.toList());

        List<Integer> result = numbers.parallelStream()
                                      .filter(n -> n % 2 == 0)
                                      .map(n -> n * 2)
                                      .collect(Collectors.toList());
    }
}

In this example, parallelStream() allows operations to be executed in parallel, leveraging multiple CPU cores.

13. How do you handle null values in a stream pipeline?

Handling null values in a stream pipeline can be done by filtering them out or using default values:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class NullHandlingExample {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("apple", null, "banana", "cherry", null);

        List<String> filteredItems = items.stream()
                                          .filter(item -> item != null)
                                          .collect(Collectors.toList());

        System.out.println(filteredItems); // Output: [apple, banana, cherry]
    }
}

14. Describe how to combine multiple streams into one.

To combine multiple streams into one, use Stream.concat() or Stream.of():

Example using Stream.concat():

import java.util.stream.Stream;

public class CombineStreams {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("A", "B", "C");
        Stream<String> stream2 = Stream.of("D", "E", "F");

        Stream<String> combinedStream = Stream.concat(stream1, stream2);
        combinedStream.forEach(System.out::println);
    }
}

Example using Stream.of():

import java.util.stream.Stream;

public class CombineStreams {
    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("A", "B", "C");
        Stream<String> stream2 = Stream.of("D", "E", "F");
        Stream<String> stream3 = Stream.of("G", "H", "I");

        Stream<String> combinedStream = Stream.of(stream1, stream2, stream3)
                                              .flatMap(s -> s);
        combinedStream.forEach(System.out::println);
    }
}

15. What are some techniques to optimize stream performance?

To optimize stream performance, consider these techniques:

– Use parallel streams judiciously to leverage multiple CPU cores.
– Minimize intermediate operations to reduce overhead.
– Use primitive streams to avoid boxing and unboxing.
– Avoid stateful operations like distinct, sorted, and limit when possible.
– Utilize short-circuiting operations like findFirst, findAny, and anyMatch.
– Reuse streams by creating a new one for each operation to avoid overhead.

Previous

15 Python Testing Interview Questions and Answers

Back to Interview
Next

10 Usability Testing Interview Questions and Answers