Web Development

Essential Java Stream Interview Questions

By Bastien, on December 11, 2023 - 6 min read

Java 8 stream API provides a functional interface that makes data processing easier. Instead of manually looping through a list to perform an operation, you can now chain your operations together to form a pipeline. Specific Java 8 stream interview questions may not always come up during interviews; however, if you learn how to use Java 8 stream API very well, you can perform well in Java coding challenges.

The information provided in this reading is valuable for anyone looking to enhance their understanding of Java 8 features, including job seekers and students. Hope you will find it useful even if you are in search of some professional programming homework help.

Introduction to Java Streams

image 7

Java stream was introduced in Java 8. It offers a set of functions that we can use to process data structures like lists, sets, and arrays. In the traditional approach, you have to manually loop through the entire list, check every single element, and retrieve them yourself. However, in streams, you just give it the list and what you want it to filter on, and then it does all the heavy lifting for you. This shift from the conventional approach allows developers to perform data processing operations more efficiently.

Core Concepts in Java Stream

  • Java streams are not data structures but are mechanisms for data processing
  • Java streams do not modify the original data source; they only produce a result based on what you tell them to do.
  • Intermediate operations in Java streams are executed lazily. This means that operations are not evaluated until a terminal end is reached.

Advanced Java Stream Techniques

You can use Java Stream to perform basic operations like filtering, sorting, and mapping. But that’s not all. There are more advanced operations you can equally perform with Java streams. Here are some advanced Java stream operations.

  • Parallel Streams: parallelStream() method splits stream processing across multiple threads to enhance operation performance.

Here is an example:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
    .parallel() 
    .map(number -> number * number) 
    .reduce(0, (a, b) -> a + b); 

System.out.println("Sum of squared numbers: " + sum);
  • Custom Collectors: Custom collectors customize how stream elements are aggregated and stored. This makes data manipulation flexible.

Example:

import java.util.stream.Collector;
import java.util.stream.Collectors;

class CustomCollector implements Collector<String, List<String>, List<String>> {

    @Override
    public Supplier<List<String>> supplier() {
        return () -> new ArrayList<>();
    }

    @Override
    public BiConsumer<List<String>, String> accumulator() {
        return (list, element) -> {
            if (element.length() % 2 == 0) {
                list.add(element);
            }
        };
    }

    @Override
    public BinaryOperator<List<String>> combiner() {
        return (list1, list2) -> {
            list1.addAll(list2);
            return list1;
        };
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(Characteristics.UNORDERED, Characteristics.NON_DISTINCT));
    }

    @Override
    public Function<List<String>, List<String>> finisher() {
        return list -> list;
    }
}

List<String> words = Arrays.asList("apple", "google", "amazon", "microsoft", "facebook");

List<String> evenLengthWords = words.stream()
    .collect(Collectors.collectingAndThen(
        new CustomCollector(),
        list -> list.stream().sorted().collect(Collectors.toList())
    ));

System.out.println("Even-length words (sorted): " + evenLengthWords);
  • Grouping and Partitioning: groupingBy() and partitioningBy() is used to organize stream elements into groups based on the criteria you specified.

Example:

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

public class ProductExample {

    public static void main(String[] args) {
        List<Product> products = Arrays.asList(
                new Product("MacBook", 1200),
                new Product("iPhone", 500),
                new Product("iPad", 300),
                new Product("AirPod", 250)
        );

       
        Map<ProductType, List<Product>> groupedProducts = products.stream()
                .collect(Collectors.groupingBy(Product::getType));

        System.out.println("Products grouped by type:");
        for (Map.Entry<ProductType, List<Product>> entry : groupedProducts.entrySet()) {
            System.out.println("Type: " + entry.getKey());
            System.out.println("Products:");
            for (Product product : entry.getValue()) {
                System.out.println("    " + product.getName() + " - $" + product.getPrice());
            }
        }

        List<Product> expensiveProducts = products.stream()
                .collect(Collectors.partitioningBy(product -> product.getPrice() >= 500))
                .get(true);

        System.out.println("Products priced above or equal to $500:");
        for (Product product : expensiveProducts) {
            System.out.println("    " + product.getName() + " - $" + product.getPrice());
        }
    }
}

Common Java Stream Interview Scenarios

image 6

You should be well versed during your Java interview preparation because recruiters will specifically assess you with many Java coding challenges. To help you ace your interview, we’ve compiled common Java 8 interview questions and answers.

Q. Briefly explain stream pipelining.

  • Stream pipelining is the chaining of multiple stream operations to process data. It comes in two phases: the Intermediate operation, which is used to filter the data, and the terminal operation, which produces a result. The operations are combined into a pipeline, where the output of one operation serves as the input to the next. At the end of the pipeline, a terminal operation is then applied to obtain the final result.

Q. Differentiate between intermediate and terminal operations on Java stream?

  • Intermediate operations are used to return a stream for further composition, while terminal operations produce results. Once a terminal method is called, no other stream methods can be applied.

Q. What does the term “lazy stream” mean?

  • Lazy stream means that methods on java.util.stream.Stream.of(T…) do not work in the pipeline. They are only executed when a terminal method is called and completed when the needed data is found.

Q. How can you convert an array to Stream?

  • The stream class has a factory method, Stream.of(T …), which you can use to convert an array to a stream. Here’s an example:
String[] languages = {"Apple", "Microsoft", "Google"};
Stream numbers = Stream.of(languages);
numbers.forEach(System.out::println);

Stream API Basics and Operations

Before performing operations on Java stream API, you have to declare it first. You can declare Java stream API using the following syntax.

Stream<T> stream;

“T” in the above syntax represents a class, an object, or a data type. After you have declared the stream,  there are two basic operations you can perform: The intermediate and terminal operations.

Intermediate Operations

The Intermediate operations allow multiple methods to be chained in a row. Here are the various operations performed in this phase.

  • filter(): This operation filters out elements from the stream based on the function you specify. When the filter() method is passed, only elements that fit into the given criteria are retained in the resulting stream.

Example:

List names = Arrays.asList("Netflix","Uber","Spotify");
List result = names.stream().filter(s->s.startsWith("S")).collect(Collectors.toList());
  • sorted(): This operation sorts the elements in either ascending or descending order. However, if no comparator is provided, the natural ordering of the elements would be used.
List names = Arrays.asList("Netflix","Uber","Spotify");
List result = names.stream().sorted().collect(Collectors.toList());
  • map(): This operation transforms each element in the stream. The transformed elements will then form the resulting stream.
List number = Arrays.asList(2,3,4,5);
List square = number.stream().map(x->x*x).collect(Collectors.toList());

Terminal Operations

Terminal operations mark the end of the stream and return the final result. Here are some examples of terminal operations:

  • collect(): This operation collects the elements of the stream into a data structure, such as a list, set, or map.

Example:

List number = Arrays.asList(6,7,8,9,7);
Set square = number.stream().map(x->x*x).collect(Collectors.toSet());
  • forEach(): The forEach() function is used to iterate over each element of the stream and perform a specific action for each element.

Example:

List number = Arrays.asList(6,7,8,9);
number.stream().map(x->x*x).forEach(y->System.out.println(y));
  • reduce(): This operation combines the elements of the stream into a single value.

Example:

List number = Arrays.asList(2,3,4,5);
int even = number.stream().filter(x->x%2==0).reduce(0,(ans,i)-> ans+i);

Handling Data with Java Streams

Here’s a breakdown of how you can handle data with Java streams.

  • Obtain a stream: Create a stream from the list of transactions using the stream() method.
  • Chaining operations: Once you have a stream, you can then apply various operations to it. Common stream operations include
    • filter(): Filters the stream based on a specified condition.
    • sorted(): Sorts the stream using a comparator.
    • map(): Transforms each element in the stream into a new value.
    • collect(): Collects the results of the stream.
  • Executing the pipeline: The final step is to execute the pipeline. This is where you perform all the chained operations and produce the desired result.

Stream Optimization and Best Practices

If you want to optimize your Java stream and get a perfect result, here are the best practices to follow.

  • Select the appropriate data structure for your specific use case. To maintain the order of elements, use a List. To avoid duplicates, use a Set. To map keys to values, opt for a Map.
  • Use lazy evaluation for your operations. This is because operations in the Java stream are not executed until the terminal operation is called. This allows the JVM to optimize the stream pipeline and avoid unnecessary computations.
  • Don’t create and discard streams repeatedly. Doing this can lead to inefficiencies. You should only create streams when they are necessary.
  • Use parallel streams as it will improve the performance of large datasets.

Conclusion

If you have read to this point, it means you’ve successfully expanded your knowledge of Java stream API and have equipped yourself with the expertise to excel in any interview. However, the journey doesn’t end here. Learning is a continuous process, and there’s always more to discover. To expand your knowledge of Java 8 stream API further, you can check out their official documentation.

FAQ

What are the key features of Java Stream API?

  • Java Stream API allows you to streamline your data processing tasks.
  • Stream operations are not executed until the terminal operation is invoked.
  • Stream operations do not modify the source data

How do lambda expressions work with Java Streams?

Lambda expressions provide a concise way to define anonymous functions that can be passed as arguments to stream operations.

Here is an example of lambda expressions for filtering even numbers in a list of numbers.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
        .filter(n -> n % 2 == 0)
        .forEach(System.out::println);

What are some common Java Stream operations used in interviews

  • filter() – Filters elements based on a predicate.
  • map() – Transforms elements using a mapper function.
  • sorted() – Sorts elements based on a comparator.
  • collect() – Collects elements into a collection or other data structure.
  • count() –  Counts the number of elements in a stream.

Bastien