Java-8 Interview Questions Java-8 Interview Questions

Page content

These Java 8 interview questions are based on my personal interview experience. Keep following this post for regular updates.

Also Read Core Java Interview Questions
Also Read Java Multithreading (Concurrency) Interview Questions

Q1. What New Features Were Added in Java 8?

Java 8 ships with several new features but the most significant are the following:

  • Lambda Expressions − a new language feature allowing treating actions as objects
  • Method References − enable defining Lambda Expressions by referring to methods directly using their names
  • Optional − special wrapper class used for expressing optionality
  • Functional Interface – an interface with maximum one abstract method, implementation can be provided using a Lambda Expression
  • Default methods − give us the ability to add full implementations in interfaces besides abstract methods
  • Nashorn, JavaScript Engine − Java-based engine for executing and evaluating JavaScript code
  • Stream API − a special iterator class that allows processing collections of objects in a functional manner
  • Date API − an improved, immutable JodaTime-inspired Date API

Along with these new features, lots of feature enhancements are done under-the-hood, at both compiler and JVM level.

Q2. What Is a Method Reference?

A method reference is a Java 8 construct that can be used for referencing a method without invoking it using double colon :: operator. It is used for treating methods as Lambda Expressions. They only work as syntactic sugar to reduce the verbosity of some lambdas. See below code, how method reference has reduced the verbosity over lambda expression.

List<String> languages = Arrays.asList("java", "javascript", "css");

// Lambda expression 
languages.forEach(str -> System.out.println(str)); 

// Method Reference
languages.forEach(System.out::println); 

Double colon :: is basically refers to a single method, and this single method can be a

  1. A Static method ClassName::staticMethodName
    e.g. String::valueOf, Integer::parseInteger, Double::parseDouble
  2. An Instance method Object::instanceMethodName
    e.g. String::toString, System.out::println, String::toUpperCase
  3. A Constructor ClassName::new
    e.g. String::new
  4. A Super method super::parentMethodName

Read Double Colon Operators in Java 8 for more details on method reference.

Q3. What Is a Functional Interface?

A Functional Interface is an interface having one and only one abstract method. Though it can have one or more default methods.

Functional interface is annotated with @FunctionalInterface - which is informative and does not affect the semantics.

Examples

Java has some built-in functional interface such as java.lang.Runnable which is having only one run() abstract method.

@FunctionalInterface
public interface Runnable {

    public abstract void run();

}

Another functional interface is java.util.Comparator which is having one compare() abstract method, and many default and static methods.

@FunctionalInterface
public interface Comparator<T> {
  
  int compare(T o1, T o2);

  default Comparator<T> reversed() {
      return Collections.reverseOrder(this);
  }
  ...
  public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
      return Collections.reverseOrder();
  }
  ...
}
Usage

Functional interface can be replaced by Lambda Expression. For instance, instead of:

Thread thread = new Thread(new Runnable() {
    public void run() {
        System.out.println("Hello World!");
    }
});

List<String> a1 = Arrays.asList("equity", "stocks", "gold", "foreign exchange","fixed income", "future");
a1.sort(new Comparator<String>() {
  @Override
  public int compare(String o1, String o2) {
    return o1.compareTo(o2);
  }
});

you could simply do:

Thread thread = new Thread(() -> System.out.println("Hello World!"));

List<String> a1 = Arrays.asList("equity", "stocks", "gold", "foreign exchange","fixed income", "future");
a1.sort((o1, o2) -> o2.compareTo(o1));

Q4. What Is a Default Method and When Do We Use It?

A default method is a method with an implementation – which can be found in an interface.

We can use a default method to add a new functionality to an interface while maintaining backward compatibility with classes that are already implementing the interface:

public interface Vehicle {
    public void move();
    default void hoot() {
        System.out.println("peep!");
    }
}

Usually, when a new abstract method is added to an interface, all implementing classes will break until they implement the new abstract method. In Java 8, this problem has been solved by the use of default method.

For example, Collection interface does not have forEach method declaration. Thus, adding such method would simply break the whole collections API.

Java 8 introduces default method so that Collection interface can have a default implementation of forEach method without requiring the classes implementing this interface to implement the same.

Q5. Will the Following Code Compile?

@FunctionalInterface
public interface Function2<T, U, V> {
    public V apply(T t, U u);
 
    default void count() {
        // increment counter
    }
}

Yes. The code will compile because it follows the functional interface specification of defining only a single abstract method apply. The second method count, is a default method.

Q6. Describe Some of the Functional Interfaces in the Standard Library.

There are a lot of functional interfaces in the java.util.function package, the more common ones include but not limited to:

  • Function – it takes one argument and returns a result
  • Consumer – it takes one argument and returns no result (represents a side effect)
  • Supplier – it takes not argument and returns a result
  • Predicate – it takes one argument and returns a boolean
  • BiFunction – it takes two arguments and returns a result
  • BinaryOperator – it is similar to a BiFunction, taking two arguments and returning a result. The two arguments and the result are all of the same types
  • UnaryOperator – it is similar to a Function, taking a single argument and returning a result of the same type

Q7. What Is a Lambda Expression and What Is It Used for?

In very simple terms, a lambda expression is a function that can be referenced and passed around as an object.

Lambda expressions introduce functional style processing in Java and facilitate the writing of compact and easy-to-read code.

Because of this, lambda expressions are a natural replacement for anonymous classes as method arguments. One of their main uses is to define inline implementations of functional interfaces.

//Java 7 way of initializing a thread
Thread t1 = new Thread(new Runnable(){
  
  @Override
  public void run() {
    System.out.println("Thread started in Java7");
  } 
});
t1.start();

//Java 8 Lambda Expression
Thread t2 = new Thread(() -> System.out.println("Thread started in Java8"));
t2.start();

//Java 7 way of sorting list
List<String> a1 = Arrays.asList("equity", "stocks", "gold", "foreign exchange","fixed income", "future");
a1.sort(new Comparator<String>() {

  @Override
  public int compare(String o1, String o2) {
    return o1.compareTo(o2);
  }
});
		
//Java 8 Lambda Expression
a1.sort((o1, o2) -> o2.compareTo(o1));

Q8. Explain the Syntax and Characteristics of a Lambda Expression?

A lambda expression consists of two parts: the parameter part and the expressions part separated by a forward arrow as below:

// Syntax
param -> expression

Any lambda expression has the following characteristics:

  • Optional type declaration – when declaring the parameters on the left-hand side of the lambda, we don’t need to declare their types as the compiler can infer them from their values.
  • Optional parentheses – when only a single parameter is declared, we don’t need to place it in parentheses. But when more than one parameter is declared, parentheses are required
  • Optional curly braces – when the expressions part only has a single statement, there is no need for curly braces. But curly braces are required when there is more than one statement
  • Optional return statement – when there is only single statement, then we can omit return keyword from return statement.
// Lamda Expression
Stream.of("java", "spring", "spring boot").filter((String s) -> { return s == "java"; });

// Removed Optional Type Declaration, Parentheses, Curly Braces and Return Keyword
Stream.of("java", "spring", "spring boot").filter(s -> s == "java");

Q9. What Is a Stream? How Does It Differ from a Collection?

In simple terms, a stream is an iterator whose role is to accept a set of actions to apply on each of the elements it contains.

The stream represents a sequence of objects from a source such as a collection, which supports aggregate operations. They were designed to make collection processing simple and concise. Contrary to the collections, the logic of iteration is implemented inside the stream, so we can use methods like map and flatMap for performing a declarative processing.

Another difference is that the Stream API is fluent and allows pipelining:

int sum = Arrays.stream(new int[]{1, 2, 3})
  .filter(i -> i >= 2)
  .map(i -> i * 3)
  .sum();

And yet another important distinction from collections is that streams are inherently lazily loaded and processed.

Q10. What is the Difference Between Intermediate and Terminal Operations?

Stream operations are combined into pipelines to process streams. All operations are either intermediate or terminal.

Intermediate operations are those operations that return Stream itself allowing for further operations on a stream.

These operations are always lazy, i.e. they do not process the stream at the call site, an intermediate operation can only process data when there is a terminal operation. Some of the intermediate operations are filter, map and flatMap.

Terminal operations terminate the pipeline and initiate stream processing. The stream is passed through all intermediate operations during terminal operation call. Terminal operations include forEach, reduce, Collect and sum.

To drive this point home, let us look at an example with side effects:

public static void main(String[] args) {
    System.out.println("Stream without terminal operation");
     
    Arrays.stream(new int[] { 1, 2, 3 }).map(i -> {
        System.out.println("doubling " + i);
        return i * 2;
    });
  
    System.out.println("Stream with terminal operation");
        Arrays.stream(new int[] { 1, 2, 3 }).map(i -> {
            System.out.println("doubling " + i);
            return i * 2;
    }).sum();
}

The output will be as follows:

Output
Stream without terminal operation Stream with terminal operation doubling 1 doubling 2 doubling 3

As you can see, the intermediate operations are only triggered when a terminal operation exists.

Q11. What is the Difference Between Map and flatMap Stream Operation?

There is a difference in signature between map and flatMap. Generally speaking, a map operation wraps its return value inside its ordinal type while flatMap does not.

For example, in Optional, a map operation would return Optional<String> type while flatMap would return String type.

So after mapping, one needs to unwrap (read “flatten”) the object to retrieve the value whereas, after flat mapping, there is no such need as the object is already flattened. The same concept is applied to mapping and flat mapping in Stream.

Both map and flatMap are intermediate stream operations that receive a function and apply this function to all elements of a stream.

The difference is that for the map, this function returns a value, but for flatMap, this function returns a stream. The flatMap operation “flattens” the streams into one.

Here’s an example where we take a map of users’ names and lists of phones and “flatten” it down to a list of phones of all the users using flatMap:

Map<String, List<String>> people = new HashMap<>();
people.put("John", Arrays.asList("555-1123", "555-3389"));
people.put("Mary", Arrays.asList("555-2243", "555-5264"));
people.put("Steve", Arrays.asList("555-6654", "555-3242"));
 
List<String> phones = people.values().stream()
  .flatMap(Collection::stream)
    .collect(Collectors.toList());

Q12. What Is Stream Pipelining in Java 8?

Stream pipelining is the concept of chaining operations together. This is done by splitting the operations that can happen on a stream into two categories: intermediate operations and terminal operations.

Each intermediate operation returns an instance of Stream itself when it runs, an arbitrary number of intermediate operations can, therefore, be set up to process data forming a processing pipeline.

There must then be a terminal operation which returns a final value and terminates the pipeline.


Reference:

  1. Baeldung