Interview questions on Java 8 new features – frequently asked in 2018

Java 8 ships with several new features and enhancements but the most significant are Streams, Functional Interface, Optional class, Lambda Expressions, Default Methods, Method References etc.

I curated below list of frequently asked interview questions from technical interviewers in my connections.

Q.1) What is a Predicate?

Predicates are boolean-valued functions of one argument. The interface contains various default methods for composing predicates to complex logical terms (and, or, negate)

Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("hello");              // true
predicate.negate().test("hello");     // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
Q.2) What is a Function in functional interfaces ?

Functions accept one argument and produce a result. Default methods can be used to chain multiple functions together (compose, andThen).

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"
Q.3) What is a Supplier in Java 8 Functional Interfaces?

Suppliers produce a result of a given generic type. Unlike Functions, Suppliers don’t accept arguments.

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person
Q.4) What is a Consumer in Java 8?

Consumers represent operations to be performed on a single input argument. i.e accept the argument and do some processing on it.

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Q.5) What is a Comparator? and how can we use in Java 8

Comparators are well known from older versions of Java. But in Java 8 it supports lambda syntax like below thanks to functional interface feature.

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("William", "Doe");
Person p2 = new Person("Alice", "Jonas");

comparator.compare(p1, p2);              
comparator.reversed().compare(p1, p2);   
Q.6) What is method reference in Java 8 ?

Java 8 enables us to pass references of methods or constructors via the :: keyword. We can pass all the below references

  • regular methods
  • static methods
  • constructors

Example:

// Static Method
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");

// instance method
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);

// Constructor ref
ArrayList<String> list = ArrayList::new;
list.add("Hello");
Q.7) What is Optional class is Java? How it is useful

Optionals are not functional interfaces, but nifty utility to prevent NullPointerException. Optional is a simple container for a value which may be null or non-null. Think of a method which may return a non-null result but sometimes return nothing. Instead of returning null you return an Optional in Java 8.

Optional<String> optional = Optional.of("result");

optional.isPresent();           // true
optional.get();                 // "result"
optional.orElse("fallback");    // "result"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "r"
Q.8) What is Stream in Java8?

A java.util.Stream represents a sequence of elements on which one or more operations can be performed. Stream operations are either intermediate or terminal.

While terminal operations return a result of a certain type, intermediate operations return the stream itself so you can chain multiple method calls in a row.

Streams are created on a source, e.g. a java.util.Collection like lists or sets (maps are not supported). Stream operations can either be executed sequentially or parallely.

Let’s first look how sequential streams work. First we create a sample source in form of a list of strings:

List<String> stringCollection = new ArrayList<>();

stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

Now we can create a stream by calling stringCollection.stream() method.

Stream<String> stramObj = stringCollection.stream();
Q.9) Difference between sequential and parallel streams?

Streams can be either sequential or parallel. Operations on sequential streams are performed on a single thread while operations on parallel streams are performed concurrently on multiple threads.

Q.10) Give an example of both sequential and parallel streams

First lets create a large collection of random elements, and we can use stream() or parallelStream() methods to sort it.

int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
    UUID uuid = UUID.randomUUID();
    values.add(uuid.toString());
}

Sequential Sort : In the below example the stream will be processed by single thread.

long count = values.stream().sorted().count();
System.out.println(count);

Parallel Sort : In the below example the stream will launch multiple threads internally and performs the sorting.

long count = values.parallelStream().sorted().count();

System.out.println(count);
Q.11) What is a Filter operation in Java? Explain with an example

Filter accepts a predicate to filter all elements of the stream. This operation is intermediate which enables us to call another stream operation (forEach) on the result.

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"));
Q.12) How to use forEach() method in streams?

ForEach accepts a consumer to be executed for each element in the filtered stream. ForEach is a terminal operation. It’s void, so we cannot call another stream operation.

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);
Q.13) Does Java streams support Sort operation?

Sorted is an intermediate operation which returns a sorted view of the stream. The elements are sorted in natural order unless you pass a custom Comparator.

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);

Remember that sorted does only create a sorted view of the stream without manipulating the ordering of the backed collection. The ordering of stringCollection is untouched

Q.14) What is map operation in Streams?

The intermediate operation map converts each element into another object via the given function. The following example converts each string into an upper-cased string. But you can also use map to transform each object into another type. The generic type of the resulting stream depends on the generic type of the function you pass to map.

stringCollection
    .stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);
Q.15) How can you check whether an element exist in the given stream?

Stream supports various matching operations, which can be used to check whether a certain predicate matches the stream. All of those operations are terminal and return a boolean result.

Example:

boolean anyStartsWithA =
    stringCollection
        .stream()
        .anyMatch((s) -> s.equals("hello"));

System.out.println(anyStartsWithA);      // true if 'hello' exists in the stringCollection

along with above anyMatch operation Streams support few other operations as well, such as allMatch(), noneMatch etc.

//1. allMatch() operation
boolean allStartsWithA =
    stringCollection
        .stream()
        .allMatch((s) -> s.startsWith("a"));

System.out.println(allStartsWithA);      // true if all the entries in the stream starts with 'a'


//2. noneMatch() operation
boolean noneStartsWithZ =
    stringCollection
        .stream()
        .noneMatch((s) -> s.startsWith("z"));

System.out.println(noneStartsWithZ);      // 'true' if none of the entries in the list starts with 'z'
Q.16) is there any way to get number of elements in the stream?

Yes, we can use count is a terminal operation for returning the number of elements in the stream as a long. This is very useful when a sequence of operations used on stream.

For example, below code returns the number of elements in the stringCollection which starts with ‘b’

long startsWithB =
    stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();

System.out.println(startsWithB);
Q.17) What is reduce operation ?

This terminal operation performs a reduction on the elements of the stream with the given function. The result is an Optional holding the reduced value.

Optional<String> reduced =
    stringCollection
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "@" + s2);

reduced.ifPresent(System.out::println);
// "str1@str2@str3..."
Q.18) What is Lambda expression in Java 8 ? and how to use it

Java 8 introduced new and shorter syntax to replace anonymous objects feature from its previous versions.

For example, the the below code

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});

can be written as

Collections.sort(names, (String a, String b) -> b.compareTo(a));

As you can see the code is much shorter and easier to read. But it gets even shorter.

names.sort((a, b) -> b.compareTo(a));

List now has a sort method. Also the java compiler is aware of the parameter types so you can skip them as well.

Q.19) What is functional Interface in Java 8?

A functional interface must contain exactly one abstract method declaration. Each lambda expression of that type will be matched to this abstract method. Since default methods are not abstract you’re free to add default methods to your functional interface.

We can use arbitrary interfaces as lambda expressions as long as the interface only contains one abstract method. To ensure that the interface meet the requirements, you should add the @FunctionalInterface annotation. The compiler is aware of this annotation and throws a compiler error as soon as you try to add a second abstract method declaration to the interface.

Example:

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

Keep in mind that the code is also valid if the @FunctionalInterface annotation would be omitted.

Q.20) Give examples of well known old interfaces extended to support functional interfaces in Java8?

The JDK 1.8 API contains many built-in functional interfaces. Some of them are well known from older versions of Java like Comparator or Runnable. Those existing interfaces are extended to enable Lambda support via the @FunctionalInterfaceannotation.

Example:

1. Comparator

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

2. Runnable

// Lambda Runnable
Runnable task1 = () -> { System.out.println("Task #1 is running"); };

// start the thread
new Thread(task1).start();