Top 40 Java 8 Interview Questions & Answers 2023

In this article, we will delve into the world of Java 8, unraveling a compilation of crucial Java 8 interview questions and offering comprehensive answers to equip you for success in your Java 8 interviews.

Table of Contents

Java 8 Interview Questions

What are the prominent features introduced in Java 8?

This is the most frequently asked Java 8 interview questions.

Java 8 introduced several notable features that enhanced the language’s capabilities. Some of the key features of Java 8 include:

  1. Lambda Expressions: The addition of lambda expressions allowed developers to write more concise and functional-style code.
  2. Stream API: The Stream API provided a powerful and efficient way to process collections of data in a functional programming style.
  3. Default Methods: Default methods allowed interfaces to have method implementations, reducing the need for explicit implementation in implementing classes.
  4. Optional: The Optional class introduced a type that represents an optional value, reducing the occurrence of null pointer exceptions.
  5. Functional Interfaces: Java 8 introduced functional interfaces, which are interfaces with a single abstract method, enabling the use of lambda expressions.
  6. Method References: Method references allowed developers to refer to methods by their names, simplifying code and improving readability.
  7. Date and Time API: The new Date and Time API provided a more comprehensive and flexible way to handle date and time operations.
  8. CompletableFuture: CompletableFuture enhanced the handling of asynchronous programming and provided improved support for concurrent operations.
  9. Parallel Streams: Java 8 introduced the concept of parallel streams, enabling efficient parallel processing of data.
  10. Nashorn JavaScript Engine: Java 8 included the Nashorn JavaScript engine, allowing developers to execute JavaScript code within Java applications.

What are functional interfaces in Java 8?

This is the one of the most frequently asked Java 8 interview questions.

Functional interfaces in Java are specialized interfaces with a single abstract method. They serve as the foundation for lambda expressions and provide target types for them. Examples of functional interfaces in Java 8 include Runnable, Comparator, and Predicate.

What are the functional interfaces introduced in the java.util.function package in Java 8?

Java 8 introduced several functional interfaces in the java.util.function package, including Predicate, Function, Consumer, Supplier, BiPredicate, BiFunction, and BiConsumer. These interfaces provide the foundation for functional programming in Java 8 and allow for the use of lambda expressions and method references.

How do you build a custom Functional interface in Java 8?

In Java 8, you can create a custom functional interface by defining an interface with a single abstract method. This indicates a specific behavior or functionality. By annotating the interface with @FunctionalInterface, you enforce the presence of only one abstract method, treating it as a functional interface.

Here’s an example of creating a custom functional interface in Java 8:

@FunctionalInterface
interface MyFunctionalInterface {
    void performAction();
}

public class Main {
    public static void main(String[] args) {
        // Using a lambda expression to implement the abstract method of the functional interface
        MyFunctionalInterface myFunction = () -> System.out.println("Performing custom action");
        myFunction.performAction(); // Output: Performing custom action
    }
}

In the code above, we define a custom functional interface called MyFunctionalInterface with a single abstract method. By annotating the interface with @FunctionalInterface, we indicate its functional nature.

In the main method, we create an instance of MyFunctionalInterface using a lambda expression to implement performAction(). Invoking performAction() on the functional interface executes the lambda expression, producing the output “Performing custom action”.

What are lambda expressions in Java 8?

This is the quiet frequently asked Java 8 interview questions.

Lambda expressions are anonymous functions that allow you to treat functionality as a method argument or code as data. They provide a concise way to write code by eliminating the need for boilerplate code and reducing verbosity. Lambda expressions consist of parameters, an arrow token (->), and a body. They can be used wherever a functional interface is expected.

How does Java 8 support functional programming?

Java 8 introduced functional interfaces, lambda expressions, and the Stream API, which are key components of functional programming. Functional interfaces enable the use of lambda expressions, allowing developers to write more concise and expressive code. The Stream API provides a powerful way to process collections and perform operations such as filtering, mapping, and reducing in a functional style.

How can you use the Stream API in Java 8?

Among the Java 8 interview questions, this is considered one of the most important ones.

The Stream API in Java 8 allows developers to perform complex operations on collections in a functional and declarative manner. It provides methods such as filter(), map(), reduce(), and collect() to manipulate and process data. Streams in Java 8 can be created from different sources such as collections, arrays, or I/O channels.They support sequential as well as parallel execution, enabling efficient processing of large datasets.

Explain the default methods in interfaces introduced in Java 8.

Default methods in interfaces allow the addition of new methods to existing interfaces without breaking the backward compatibility of implementing classes. Default methods provide default implementations, which can be overridden by implementing classes if necessary.

What are static methods in interfaces in Java 8?

Starting from Java 8, interfaces can have static methods. The purpose of static methods in interfaces is to provide utility methods or helper functions that are related to the interface’s functionality. These methods can be invoked directly on the interface itself without the need for an implementing class instance.

What is the Optional class in Java 8?

The Optional class in Java provides a flexible container for holding values that can be either present or absent. It was introduced in Java 8 to help eliminate null pointer exceptions. Optional provides methods like isPresent(), get(), orElse(), and more, to safely handle situations where a value may be absent.

How does Java 8’s method reference feature simplify code and enhance readability?

In Java 8, method reference is a concise syntax for referring to methods or constructors without immediate execution. It simplifies passing methods or constructors as arguments, promoting code reuse and readability.

Method reference differs from lambda expressions by directly pointing to existing methods or constructors, unlike anonymous functions created by lambda expressions. This distinction enables the reuse of established code, enhancing readability and encouraging code reuse.

Java 8 introduces four types of method references:

  1. Reference to a static method: ClassName::staticMethodName
  2. Reference to an instance method of a particular object: objectReference::instanceMethodName
  3. Reference to an instance method of an arbitrary object of a particular type: ClassName::instanceMethodName
  4. Reference to a constructor: ClassName::new

Here’s a sample code snippet that demonstrates the use of method reference in Java 8:

import java.util.ArrayList;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();

        names.add("John");
        names.add("Alice");
        names.add("Bob");
        names.add("Emily");

        // Method reference to static method
        names.forEach(System.out::println);

        // Method reference to instance method of a particular object
        names.forEach(String::toUpperCase);

        // Method reference to instance method of an arbitrary object of a particular type
        names.sort(String::compareToIgnoreCase);

        // Method reference to constructor
        names.stream()
             .map(String::new)
             .forEach(System.out::println);
    }
}

In this example, we have a list of names. We demonstrate different types of method references on the names list:

  1. Reference to a static method: System.out::println is a method reference to the static method println of the System.out object. It is used in the forEach method to print each element of the list.
  2. Reference to an instance method of a particular object: String::toUpperCase is a method reference to the instance method toUpperCase of the String class. It is used in the forEach method to convert each name to uppercase.
  3. Reference to an instance method of an arbitrary object of a particular type: String::compareToIgnoreCase is a method reference to the instance method compareToIgnoreCase of the String class. It is used in the sort method to sort the names in a case-insensitive manner.
  4. Reference to a constructor: String::new is a method reference to the constructor of the String class. It is used in the map method to create new String objects from the existing names.

What sets apart a consumer from a supplier in Java 8?

A consumer represents an operation that accepts a single input argument and returns no result, while a supplier represents an operation that supplies a result of a given type. In other words, a consumer consumes data, while a supplier provides data.

Here are some sample code snippets showcasing the usage of Consumer and Supplier interfaces in Java:

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class ConsumerExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Jane", "Adam", "Eva");

        // Example 1: Using a lambda expression
        Consumer<String> printName = (name) -> System.out.println(name);
        names.forEach(printName);

        // Example 2: Using a method reference
        Consumer<String> printUpperCase = System.out::println;
        names.forEach(printUpperCase.andThen(String::toUpperCase));
    }
}
import java.util.function.Supplier;

public class SupplierExample {
    public static void main(String[] args) {
        // Example 1: Using a lambda expression
        Supplier<String> randomStringSupplier = () -> "Hello, World!";
        System.out.println(randomStringSupplier.get());

        // Example 2: Using a method reference
        Supplier<Double> randomNumberSupplier = Math::random;
        System.out.println(randomNumberSupplier.get());
    }
}

In the ConsumerExample class, we showcase using a Consumer interface to consume elements from a list. The first example prints names with a lambda expression, and the second example prints names in uppercase with a method reference.

In the SupplierExample class, we demonstrate using a Supplier interface to supply values. The first example supplies a constant string, and the second example supplies a random number using a method reference to Math.random().

What is the purpose of the Predicate interface in Java 8?

The Predicate interface in Java 8 represents a boolean-valued function that takes an argument and returns true or false. It is commonly used for filtering and conditional checks in functional programming. The Predicate interface provides methods like test() and negate() for performing logical operations on predicates.

Here’s a sample code snippet showcasing the usage of the Predicate interface in Java:

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

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

        // Example 1: Using a lambda expression
        Predicate<Integer> isEven = (number) -> number % 2 == 0;
        List<Integer> evenNumbers = filter(numbers, isEven);
        System.out.println("Even numbers: " + evenNumbers);

        // Example 2: Using a method reference
        Predicate<Integer> isGreaterThanFive = PredicateExample::checkGreaterThanFive;
        List<Integer> greaterThanFiveNumbers = filter(numbers, isGreaterThanFive);
        System.out.println("Numbers greater than five: " + greaterThanFiveNumbers);
    }

    private static List<Integer> filter(List<Integer> numbers, Predicate<Integer> predicate) {
        // Filter the numbers based on the given predicate
        return numbers.stream()
                .filter(predicate)
                .toList();
    }

    private static boolean checkGreaterThanFive(Integer number) {
        return number > 5;
    }
}

In the PredicateExample class, we demonstrate using a Predicate interface to filter elements from a list. The first example checks if a number is even with a lambda expression, while the second example checks if a number is greater than five using a method reference.

By employing different predicates in the filter method, you can customize the filtering behavior, resulting in concise and expressive code for working with collections or streams.

What is the purpose of the Function interface in Java 8?

The Function interface in Java 8 represents a type-to-type transformation function. It facilitates converting data from one form to another. With methods like apply() and compose(), the Function interface enables operations on functions.

Here’s a Java code snippet showcasing the usage of the Function interface:

import java.util.function.Function;

public class FunctionExample {
    public static void main(String[] args) {
        // Example 1: Convert string to integer
        Function<String, Integer> strToInt = Integer::parseInt;
        int number = strToInt.apply("123");
        System.out.println("Number: " + number);

        // Example 2: Transform a string to uppercase
        Function<String, String> toUpperCase = String::toUpperCase;
        String result = toUpperCase.apply("hello");
        System.out.println("Uppercase string: " + result);
    }
}

In the FunctionExample class, we demonstrate how to use the Function interface in two different scenarios.

In the first example, we create a Function called strToInt that converts a String to an Integer using the Integer.parseInt method. We then apply this function to the string “123” and store the result in the number variable.

In the second example, we create a Function called toUpperCase that transforms a String to uppercase using the String.toUpperCase method. We apply this function to the string “hello” and store the result in the result variable.

What is the purpose of the BiFunction interface in Java 8?

The BiFunction interface in Java 8 defines a function that accepts two arguments of different types and generates a result of a different type. It is commonly used for operations that require two inputs. The BiFunction interface provides methods like apply() and andThen() for performing operations on bifunctions.

Here’s a Java code snippet showcasing the usage of the BiFunction interface:

import java.util.function.BiFunction;

public class BiFunctionExample {
    public static void main(String[] args) {
        // Example 1: Concatenate two strings
        BiFunction<String, String, String> concat = (s1, s2) -> s1 + s2;
        String result = concat.apply("Hello, ", "World!");
        System.out.println("Concatenated string: " + result);

        // Example 2: Sum two integers
        BiFunction<Integer, Integer, Integer> sum = (a, b) -> a + b;
        int total = sum.apply(5, 3);
        System.out.println("Sum: " + total);
    }
}

In the BiFunctionExample class, we demonstrate how to use the BiFunction interface in two different scenarios.

In the first example, we create a BiFunction called concat that concatenates two strings by using the + operator. We then apply this function to the strings “Hello, ” and “World!” and store the result in the result variable.

In the second example, we create a BiFunction called sum that calculates the sum of two integers by using the + operator. We apply this function to the integers 5 and 3 and store the result in the total variable.

What are the benefits of using the new Date and Time API in Java 8?

The new Date and Time API in Java 8 provides a more comprehensive and flexible approach to date and time manipulation. It offers improved clarity, immutability, and thread-safety. The API also includes useful features like support for time zones, periods, durations, and parsing/formatting capabilities.

How does Java 8 handle concurrency with the CompletableFuture class?

CompletableFuture is a feature introduced in Java 8 to simplify asynchronous programming and handle complex concurrent operations. It represents a future result that can be completed asynchronously. CompletableFuture provides a wide range of methods for combining, composing, and handling the results of asynchronous computations.

What is the purpose of the java.util.concurrent.ConcurrentHashMap class in Java 8?

This particular question tends to come up frequently in Java 8 interviews

The ConcurrentHashMap class in Java 8 is a thread-safe implementation of the Map interface. It provides concurrent access to its elements, allowing multiple threads to read and modify the map concurrently without explicit synchronization. This class is designed for high concurrency and provides improved performance in multi-threaded environments.

What are parallel streams in Java 8 ?

Parallel streams in Java 8 allow developers to process collections concurrently, taking advantage of multiple cores and processors. Parallel streams divide the workload into multiple tasks that can be executed simultaneously, improving performance for operations that can be parallelized.

Here’s a Java code snippet showcasing the usage of parallel streams in Java:

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

        // Sequential Stream
        System.out.println("Sequential Stream:");
        numbers.stream()
                .forEach(System.out::println);

        // Parallel Stream
        System.out.println("Parallel Stream:");
        numbers.parallelStream()
                .forEach(System.out::println);
    }
}

In this example, we have a list of numbers. We showcase the usage of parallel streams by converting the stream to a parallel stream with the parallelStream() method. Processing the elements concurrently in parallel can potentially enhance performance when handling large datasets.

During execution, please note that the order of output may vary between sequential and parallel streams. This discrepancy occurs because elements are processed concurrently in parallel streams.

Explain the difference between an ordered stream and an unordered stream.

An ordered stream maintains the order of the elements as they were in the source, while an unordered stream does not guarantee any specific order. Ordered streams are useful when you rely on the order of elements, such as in sequential processing or when using certain stream operations like findFirst().

What are terminal and non-terminal stream methods in Java 8?

In Java 8, the Stream API introduced two types of methods: terminal and non-terminal (intermediate) methods.
Non-Terminal (Intermediate) Methods:
Non-terminal methods are intermediate operations that can be applied to a stream to transform or filter its elements. These methods return a new stream as the result, allowing for chaining of multiple intermediate operations. Non-terminal methods do not cause the stream to be executed immediately; they are lazy evaluated. Some examples of non-terminal methods include filter(), map(), distinct(), sorted(), and limit().
For example:

List numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream stream = numbers.stream().filter(n -> n % 2 == 0).map(n -> n * 2);

In the above code snippet, filter() and map() are non-terminal operations. They create a new stream that filters even numbers and maps them to their doubled values, but no actual processing takes place until a terminal operation is invoked.
Terminal Methods:
Terminal methods are operations that mark the end of a stream pipeline and trigger the execution of the stream. When a terminal method is called, the stream processes all the elements in the pipeline and produces a result or performs an action. Terminal methods are eager and cause the stream to be consumed. Examples of terminal methods include forEach(), count(), collect(), reduce(), and findFirst().For example:

List names = Arrays.asList("John", "Jane", "Adam", "Eve");
long count = names.stream().filter(name -> name.length() > 3).count();

In the above code, filter() is a non-terminal operation, and count() is a terminal operation. The count() operation triggers the processing of elements in the stream, filters the names with a length greater than 3, and returns the count of matching elements.

Terminal operations are necessary to produce a result or perform an action on the elements of the stream. Once a terminal operation is invoked, the stream is consumed, and no further stream operations can be applied.

How is the forEach() method used in the Stream API?

The forEach() method is used to perform an action for each element of a stream. It takes a consumer as an argument and applies the consumer’s action to each element of the stream.

What is the purpose of the filter() method in the Stream API?

The filter() method is used to filter elements from a stream based on a given predicate. It takes a predicate as an argument and returns a new stream consisting of the elements that satisfy the predicate.

How is the map() method used in the Stream API?

The map() method is used to transform each element of a stream into another object by applying a given function. It takes a function as an argument and returns a new stream consisting of the transformed elements.

How is the distinct() method used in the Stream API?

The distinct() method is used to eliminate duplicate elements from a stream. It returns a new stream containing only the distinct elements based on their equals() method.

What is the purpose of the sorted() method in the Stream API?

The sorted() method is used to sort the elements of a stream in natural order or using a custom comparator. It returns a new stream containing the sorted elements.

What is the purpose of the flatMap() method in the Stream API?

The flatMap() method in the Stream API transforms each element of a stream into a new stream and then concatenates all the resulting streams into a single stream. It is particularly useful when working with nested collections or when you want to flatten a stream of streams.

What is the purpose of the peek() method in the Stream API?

The peek() method in the Stream API allows you to perform an action on each element of the stream without modifying its contents. It can be useful for debugging or logging purposes when you want to observe the elements as they flow through the stream pipeline.

How is the limit() method used in the Stream API?

The limit() method is used to reduce the size of a stream to a specified maximum number of elements. It returns a new stream that is truncated to the specified size.

What is the purpose of the skip() method in the Stream API?

The skip() method is used to skip a specified number of elements from the beginning of a stream. It returns a new stream that discards the skipped elements.

What is the purpose of the reduce() method in the Stream API?

The reduce() method in the Stream API performs a reduction operation on the elements of a stream to produce a single value. It allows you to combine the elements of a stream using a binary operator and an initial value, resulting in a reduced value.

How is the collect() method used in the Stream API?

The collect() method is used to accumulate the elements of a stream into a collection or a summary result. It takes a Collector as an argument, which specifies the mechanism to collect elements into a result container.

What are max() and min() methods in the Stream API?

The min() and max() methods in the Stream API are used to find the minimum and maximum values from a stream, respectively.

Java Sample Code for all Stream methods

Here’s a Java code snippet showcasing the usage of all the important methods of the Stream API in Java:

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

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

        // forEach - prints each element
        numbers.stream()
                .forEach(System.out::println);

        // filter - filters even numbers
        List<Integer> evenNumbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());
        System.out.println("Even numbers: " + evenNumbers);

        // map - squares each number
        List<Integer> squaredNumbers = numbers.stream()
                .map(n -> n * n)
                .collect(Collectors.toList());
        System.out.println("Squared numbers: " + squaredNumbers);

        // distinct - removes duplicates
        List<Integer> distinctNumbers = numbers.stream()
                .distinct()
                .collect(Collectors.toList());
        System.out.println("Distinct numbers: " + distinctNumbers);

        // max - finds the maximum number
        Optional<Integer> maxNumber = numbers.stream()
                .max(Integer::compareTo);
        System.out.println("Max number: " + maxNumber.orElse(null));

        // min - finds the minimum number
        Optional<Integer> minNumber = numbers.stream()
                .min(Integer::compareTo);
        System.out.println("Min number: " + minNumber.orElse(null));

        // peek - prints each element before and after mapping
        List<Integer> peekedNumbers = numbers.stream()
                .peek(n -> System.out.println("Before mapping: " + n))
                .map(n -> n * n)
                .peek(n -> System.out.println("After mapping: " + n))
                .collect(Collectors.toList());

        // limit - limits the number of elements
        List<Integer> limitedNumbers = numbers.stream()
                .limit(5)
                .collect(Collectors.toList());
        System.out.println("Limited numbers: " + limitedNumbers);

        // reduce - sums all the numbers
        int sum = numbers.stream()
                .reduce(0, Integer::sum);
        System.out.println("Sum: " + sum);

        // skip - skips the first 5 numbers
        List<Integer> skippedNumbers = numbers.stream()
                .skip(5)
                .collect(Collectors.toList());
        System.out.println("Skipped numbers: " + skippedNumbers);

        // flatMap - flattens nested lists
        List<List<Integer>> nestedLists = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4, 5));
        List<Integer> flattenedList = nestedLists.stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());
        System.out.println("Flattened list: " + flattenedList);

        // sorted - sorts the numbers in ascending order
        List<Integer> sortedNumbers = numbers.stream()
                .sorted()
                .collect(Collectors.toList());
        System.out.println("Sorted numbers: " + sortedNumbers);

        // collect - collects the elements into a new list
        List<Integer> collectedNumbers = numbers.stream()
                .collect(Collectors.toList());
        System.out.println("Collected numbers: " + collectedNumbers);
    }
}

This example demonstrates the usage of important methods like forEach, filter, map, distinct, max, min, peek, limit, reduce, skip, flatMap, sorted, and collect from the Stream API in Java. Each method

What is the purpose of the IntStream and LongStream interfaces in Java 8?

The IntStream and LongStream interfaces in Java 8 are specialized stream interfaces for working with streams of int and long primitives, respectively. They provide additional methods optimized for handling primitive values, such as sum(), average(), and range().

Here are some Java code examples showcasing the usage of IntStream and LongStream:

import java.util.stream.IntStream;

public class IntStreamExample {
    public static void main(String[] args) {
        // Create an IntStream from 1 to 5 (inclusive)
        IntStream intStream = IntStream.rangeClosed(1, 5);

        // Print the elements of the IntStream
        intStream.forEach(System.out::println);
    }
}

In this example, we create an IntStream using the rangeClosed method, which generates a stream of integers from the starting value to the ending value (inclusive). The forEach method is used to print each element of the IntStream.

import java.util.stream.LongStream;

public class LongStreamExample {
    public static void main(String[] args) {
        // Create a LongStream from 1 to 5 (inclusive)
        LongStream longStream = LongStream.rangeClosed(1, 5);

        // Calculate the sum of the LongStream elements
        long sum = longStream.sum();

        // Print the sum
        System.out.println("Sum: " + sum);
    }
}

In this example, we create a LongStream using the rangeClosed method, similar to the IntStream example. We then use the sum method to calculate the sum of the LongStream elements. Finally, we print the sum to the console.

What are the different types of references supported by Java 8’s Garbage Collector?

Java 8’s Garbage Collector supports four types of references: Strong References, Soft References, Weak References, and Phantom References. Each type of reference has its own characteristics and determines how objects are handled during garbage collection.

How does the Java 8 Stream API handle infinite streams?

The Java 8 Stream API supports infinite streams through operations like generate() and iterate(). The generate() method allows you to generate an infinite stream by providing a Supplier for the elements. The iterate() method enables you to iterate over a value and produce an infinite stream based on a function.

How does the Collectors class simplify data collection in Java 8?

The java.util.stream.Collectors class in Java 8 provides a set of predefined collectors that simplify the process of collecting data from a stream. Collectors offer methods like toList(), toSet(), toMap(), and groupingBy() that allow for concise and efficient data collection and aggregation operations.

Here’s a Java code snippet showcasing the usage of some important methods of the Collectors class in Java 8, such as groupingBy, partitioningBy, counting, and mapping:

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> fruits = Arrays.asList("Apple", "Banana", "Cherry", "Date", "Apple", "Banana");

        // Example 1: Grouping fruits by their length
        Map<Integer, List<String>> groupedByLength = fruits.stream()
                .collect(Collectors.groupingBy(String::length));
        System.out.println("Grouped by length: " + groupedByLength);

        // Example 2: Partitioning fruits into two groups: odd length and even length
        Map<Boolean, List<String>> partitionedByLength = fruits.stream()
                .collect(Collectors.partitioningBy(fruit -> fruit.length() % 2 == 0));
        System.out.println("Partitioned by length: " + partitionedByLength);

        // Example 3: Counting the occurrences of each fruit
        Map<String, Long> fruitCounts = fruits.stream()
                .collect(Collectors.groupingBy(fruit -> fruit, Collectors.counting()));
        System.out.println("Fruit counts: " + fruitCounts);

        // Example 4: Mapping fruit names to their lengths
        List<Integer> fruitLengths = fruits.stream()
                .collect(Collectors.mapping(String::length, Collectors.toList()));
        System.out.println("Fruit lengths: " + fruitLengths);
    }
}

In the CollectorsExample class, we showcase the usage of the Collectors class for various operations on a stream of fruits.

In the first example, we use groupingBy to group the fruits by length, resulting in a map with length keys and corresponding fruit lists.

In the second example, partitioningBy is used to divide the fruits into odd-length and even-length groups, creating a map with boolean keys and fruit lists.

In the third example, groupingBy is combined with counting to count the occurrences of each fruit, yielding a map with fruit names as keys and their respective counts.

In the fourth example, mapping is used to transform fruit names into their lengths, resulting in a list of integers representing fruit lengths.

Can you demonstrate the usage of Java 8’s StringJoiner to concatenate multiple strings?

With the introduction of Java 8, developers have been provided with a convenient way to combine multiple strings using the StringJoiner class. Let’s explore how this can be achieved:

import java.util.StringJoiner;

public class StringJoinerExample {
    public static void main(String[] args) {
        StringJoiner joiner = new StringJoiner(", "); // Create a StringJoiner with the delimiter ', '

        // Add strings to the joiner
        joiner.add("Hello");
        joiner.add("World");
        joiner.add("!");

        // Concatenate the strings with the delimiter
        String result = joiner.toString();

        System.out.println(result); // Output: Hello, World, !
    }
}

In the above example, we use a StringJoiner object with a delimiter of “, ” to concatenate the strings “Hello”, “World”, and “!”. By calling toString(), we obtain the joined string with the specified delimiter.

StringJoiner in Java 8 simplifies string concatenation, offering a cleaner and concise approach. It proves especially handy when combining multiple strings or dynamically concatenating strings.

Java 8 Interview Questions

What is Metaspace in Java 8?

Metaspace is a memory space introduced in Java 8 to replace the PermGen (Permanent Generation) space for storing class metadata. It is a part of the Java HotSpot VM’s runtime data area and is used to store information about classes, methods, fields, and bytecode.

Unlike PermGen, which had a fixed size and required manual tuning, Metaspace dynamically adjusts its size based on the application’s needs. It utilizes native memory instead of Java heap memory for storing class metadata, which helps avoid issues related to PermGen space limitations.

Metaspace also benefits from automatic garbage collection, allowing unused class metadata to be reclaimed, preventing memory leaks and reducing the likelihood of encountering OutOfMemoryErrors.

Overall, Metaspace in Java 8 offers improved memory management, flexibility, and eliminates the need for manual tuning of the PermGen space.

Conclusion

To sum up, the advent of Java 8 brought forth significant features and improvements that have revolutionized the coding practices of developers. The primary objective of this article was to equip you with a comprehensive compilation of Java 8 interview questions and answers, empowering you to excel in your Java 8 interviews.

By familiarizing yourself with these Java 8 interview questions and answers, you’ll gain a solid foundation to showcase your knowledge and expertise in Java 8 during interviews.

Leave a Reply

Your email address will not be published. Required fields are marked *