Java 8 Interview Questions for Experienced
As same as in the previous versions, Java released two products in it’s 8th version. One is JRE-8 (Runtime environment) and the other one is JDK-8 (Development kit). JRE provides the base libraries (lang and util, input/output etc.), integration libraries (JDBC, RMI etc.), user interface toolkits (Java FX, Swing etc.) and deployments (Java web start). On the other hand, JDK has everything from JRE, in addition to tools like compiler, debuggers and API which are necessary for developing the applications.
Java edition 8 is a major release after the 6th edition. In this article we are going to refresh the concepts which will help us to improve the interview experience. Let’s take a deep dive into it.
Java 8 Interview Questions for Experienced
1. Features of Java 8
- Lambda Expressions – Lambda expressions are anonymous functions or methods without the name
- Functional Interface – It is a single abstract method (SAM) interface, i.e. interface with only one abstract method.
- Default Method – the default implementation of the method and we can override it in the concrete class if required.
- Method Reference – It will just refer to the existing method with name. An easy way of referring to a method of a functional interface.
- Stream – a sequence of elements involved with sequential and parallel aggregate operations. The operations are composed into a stream pipeline.
- Optional – A wrapper object which may or may not contain a non null value.
- Static methods in interface – Static methods makes it easier to organize the helper methods in our libraries. We can create static methods specific to an interface in the same interface rather than in a separate class.
- Nashorn – The Nashorn engine is an implementation of the ECMAScript. Nashorn Java API allows us to write Java Applications as scripts which can be interpreted by the Nashorn engine.
2. What are functional interface
Java 8 introduced the concept of functional programming with the help of functional interfaces. It is a single abstract method (SAM) interface, i.e. interface with only one abstract method. So one abstract method is responsible for one particular functionality. @FunctionalInterface annotation is used to indicate that the interface is a functional interface. This annotation is not mandatory. It just let the compilers to throw the error incase if we maintain more than one abstract method in the interface.
Note that, we can create the functional interface instances with the help of lambda expressions, method references or constructor references.
3. Some Predefined functional interfaces
- Consumer – a function that accepts a single input argument and returns no result.
- Function – a function that accepts one argument and produces a result.
- Predicate – Represents a predicate (boolean-valued function) of one argument.
- Supplier – a function that does not accept input and produces a result.
4. What is lambda expression
Lambda expressions are anonymous functions or methods without the name. We can replace the anonymous class implementation with the help of lambda expressions after Java-8. We can pass functionality as an argument to another method here. Some of the key points are,
- Function without a name
- Anonymous function
- Executes on demand
- Applied only to functional interface
- Standalone, does not belong to any class
The Lambda expression syntax consists of the following,
- A comma separated list of parameters enclosed in parentheses
- The Arrow token, ->
- A Body, which consists of a single expression or a statement block.
// single parameter and single expression example,
p -> p.getGender() > 25 && p.getGender() <= 18
// multiple parameter example,
( a, b ) -> a + b;
// statement block example,
p ->{
return p.getGender() > 25 && p.getGender() <= 18 ;
}
5. What is default method
Before Java 8, interfaces allowed abstract methods only. From Java-8, we can do the abstract method implementation in the interface itself. Let’s say the default implementation of the method and we can override it in the concrete class if required.
The idea behind the default method is that the java-8 team wants to introduce new abstract methods in the old utility libraries. If they do so, it will provide compiler error to all it’s implemented classes. So the default method was introduced to ensure backward compatibility. It enables you to add new methods to the old interfaces with the compatibility of it’s older versions of those interfaces.
6. What is Method Reference
Sometimes we want to create the lambda function(anonymous function) that does nothing but call an existing method. Here we are having the super shortcut called method reference. It will just refer to the existing method by name, an easy way of referring to a method of a functional interface.
// a lambda expression does nothing but call an existing method
Arrays.sort(rosterAsArray,
(a, b) -> Person.compareByAge(a, b)
);
// Using method reference
// refer to the existing method by name
Arrays.sort(rosterAsArray, Person::compareByAge);
7. What is Optional
A wrapper object which may or may not contain a non null value. We can use optional to handle null values after Java-8. If value is not null, isPresent() will return true and get() will return the value.
// Creates the optional object
Optional.of(argument);
8. What is Stream
A Stream is not a data structure that stores elements. It does not hold any data(No Storage). Whereas, is a sequence of elements involved with sequential and parallel aggregate operations. The operations are composed into a stream pipeline. The Stream pipeline consist of below components,
- A source (array, collection, generator function, an I/O channel)
- Zero or more intermediate operations (each intermediate operation transform a stream into another stream)
- A terminal operation (produces a result or side-effect)
Here the important information is, you can have a number of intermediate operations and computation on the source data which is performed only when the terminal operation is initiated. Streams are Lazy and source elements are consumed only as needed.
9. Stream vs Collection
Stream | Collections |
No Storage. A stream is not a data structure that stores elements | Collections holds all the elements on memory |
Functional in nature. An operation on a stream produces a result, but does not modify its source | Non Functional. An operations on a collection would modify its content |
Laziness-seeking. Many stream operations such as filtering, mapping can be implemented lazily. | Collections are not lazy |
Possibly unbounded. While collections have finite size, streams need not. Short circuiting operations such as limit can allow computations on infinite streams to complete in finite time | Collections have the finite size |
Consumable. The elements of a stream are only visited once during the life of a stream | We can process collections a number of times. |
API to process the data | Data structure which holds the data |
10. Intermediate operation vs Terminate operations in Stream
Stream operations are divided into intermediate operations and terminal operations. They are combined to form a stream pipeline. Intermediate operations return a new stream. It’s always lazy, meaning that executing intermediate operations such as filter() does not actually perform. But creates a new stream. When terminal operation is initiated, the traversal of all intermediate operations will be executed. Terminal operations such as Stream.forEach may traverse the stream to produce the final result. After the terminal operation is performed, the stream pipeline is considered as consumed and can no longer be used.
11. map vs flatmap
map – Transforms the stream by applying the given function to all the elements of its stream. It is an intermediate operation.
// transforms all elements to uppercase
Stream.of("apple", "meta", "google")
.map(mapper -> mapper.toUpperCase()).collect(Collectors.toList());
// output
[APPLE, META, GOOGLE]
flatMap – flattening the resulting elements into a new stream. One-to-many transformation to the elements of the stream.
List<Integer> list1 = Arrays.asList(1,2,3);
List<Integer> list2 = Arrays.asList(4,5,6);
List<Integer> list3 = Arrays.asList(7,8,9);
// flatten the elements into a single list
List<Integer> result = Stream.of(list1, list2, list3)
.flatMap(list -> list.stream()).collect(Collectors.toList());
// output
[1, 2, 3, 4, 5, 6, 7, 8, 9]
12. findFirst vs findAny vs anyMatch vs allMatch vs noneMatch
findFirst – To find the first element of the stream. It will return the Optional object with either the first element or the empty optional object. This is a terminal operation.
Optional<String> first = Stream.of("apple", "meta", "google").findFirst();
System.out.println(first.get());
// output
apple
findAny – To find any element of the stream. It will return the Optional object with any one of the elements or the empty optional object. This is also a terminal operation.
Optional<String> any = Stream.of("apple", "meta", "google").findAny();
System.out.println(any.get());
// output
apple
anyMatch – Returns true if any one of the elements evaluates true with the provided predicate. If the stream is empty or all elements evaluate false with the provided predicate then it will return false.
boolean any = Stream.of("apple", "meta", "google").anyMatch(predicate -> predicate.equals("apple"));
System.out.println(any);
// output
true
allMatch – Returns true if all the elements evaluate true with the provided predicate. If the stream is empty then it will return true without evaluating the predicate.
boolean allMatch = Stream.of("apple", "meta", "google").allMatch(predicate -> predicate instanceof String);
System.out.println(allMatch);
// output
true
noneMatch – Returns true if no elements evaluate true with the provided predicate. If the stream is empty then it will return true without evaluating the predicate.
boolean noneMatch = Stream.of("apple", "meta", "google").noneMatch(predicate -> predicate.equals("facebook"));
System.out.println(noneMatch);
// output
true
13. skip vs limit vs count
skip – discards the first N elements of the stream. If the stream contains less elements than N ,then it will return an empty stream. This is an intermediate operation.
List<Integer> skipList = Stream.of(10,20,30,40,50).skip(2).collect(Collectors.toList());
System.out.println(skipList);
// output
[30, 40, 50]
limit – returns only the first N elements of the stream. It will throw an exception if the N is negative. This is also an intermediate operation.
// first skip the list, and then limit from it
List<Integer> limitList = Stream.of(10,20,30,40,50).skip(2).limit(2).collect(Collectors.toList());
System.out.println(limitList);
// output
[30, 40]
count – returns the count of elements in the stream. This is a terminal operation.
Long count = Stream.of(10,20,30,40,50).skip(2).limit(2).count();
System.out.println(count);
// output
2
14. map vs filter vs forEach vs sorted
map – a transformation operation. Returns a new stream after applying the given Function to all the elements of this stream. This is an intermediate operation.
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
// Transformation operation
List<String> strings = Stream.of("apple", "meta", "google")
.map(mapper -> mapper.toUpperCase()).collect(Collectors.toList());
// output
[APPLE, META, GOOGLE]
filter – Returns a new stream which matches the given predicate(the predicate should evaluate true). This is also an intermediate operation.
Stream<T> filter(Predicate<? super T> predicate);
// Filter operation
List<Integer> filterList = Stream.of(10,20,30,40,50)
.filter(predicate -> predicate > 25).collect(Collectors.toList());
// output
[30, 40, 50]
forEach – Performs the action for each element of this stream. This is a terminal operation.
void forEach(Consumer<? super T> action);
// Just performs an action of its element
Stream.of(10,20,30,40,50).forEach(action -> System.out.println(action));
// output
10
20
30
40
50
sorted – Returns a new stream after sorting the elements based on the provided Comparator. This is an intermediate operation.
// Default sorting is ascending without Comparator
List<Integer> sortedList1 = Stream.of(4,2,3,1).sorted().collect(Collectors.toList());
// Based on provided Comparator
List<Integer> sortedList2 = Stream.of(4,2,3,1).sorted(Comparator.reverseOrder()).collect(Collectors.toList());
// output - sortedList1
[1, 2, 3, 4]
// output - sortedList12
[4, 3, 2, 1]
15. groupBy vs mapping
groupBy – groups the elements based on the classification function and returns the result in the map. The key should be the grouping element and value should be the list.
The following will classify Person objects by city:
Map<String, List<Person>> peopleByCity = personStream.collect(Collectors.groupingBy(Person::getCity);
The following will classify Person objects by state and city, cascading two Collectors together:
Map<String, Map<String, List<Person>>> peopleByStateAndCity =personStream.collect(Collectors.groupingBy(Person::getState,
Collectors.groupingBy(Person::getCity)));
mapping – When we do collect with reduction operation, we can transform the element by applying the mapping function to each input before accumulation. In the below example, the Person object transforms to last name during the accumulation.
Map<City, Set<String>> lastNamesByCity
= people.stream().collect(
groupingBy(Person::getCity,
mapping(Person::getLastName,
toSet())));
16. Reduction operations
A reduction operation takes a sequence of input elements and combines them into a single summary result. For example, finding the sum or accumulating elements into a list. The stream classes have multiple forms of general reduction operations called reduce() and collect(). And we are having reductions forms such as sum(), max(), or count().
int sum = 0;
for (int x : numbers) {
sum += x;
}
// Above operation can be implemented using reduction
int sum = numbers.stream().reduce(0, (x,y) -> x+y);
Mutable reduction accumulates input elements into a mutable result container such as a Collection
or StringBuilder
, as it processes the elements in the stream.
17. stream vs parallel stream which one should use
Parallel stream has a higher view compared to sequential stream. But Parallel stream needs threads which takes a significant amount of time. I would prefer to use sequential streams by default and we can go for parallel ones if
- having large amount of items to process
- having the performance problem
- we don’t already run other process in a multithreaded environment
Meanwhile, note that parallel streams will not solve synchronization problems if predicates and functions in the process uses a shared resource. Here, we have to ensure that all are thread-safe. So whenever we use parallel streams, we should worry about side effects.
18. why static method introduced in Interface with Java 8
Static methods make it easier to organize the helper methods in our libraries. We can create static methods specific to an interface in the same interface rather than in a separate class. We can also ignore public modifiers since all methods are implicitly public in the interface. At the same time you should be careful that the interface has to be clear and does not create additional clutter in the API.
Even the JDK code has Collectors – static factory methods, and a Collector interface at the same time. Those methods could be merged into the Collector interface, but that would make the interface more clunky.
19. Date API’s in Java 8
LocalDate – a date in ISO format(yyyy-MM-dd). LocalDate provides a lot of utility methods to perform various data operations.
LocalDate localDate = LocalDate.now();
LocalDate.of(2015, 02, 20);
LocalDate.parse("2015-02-20");
LocalTime – a time without the date. LocalTime also provides lot of utility methods.
LocalTime now = LocalTime.now();
LocalTime sixThirty = LocalTime.of(6, 30);
LocalTime sixThirty = LocalTime.parse("06:30");
LocalDateTime – a combination of date and time. We can use this while we need the combination of date and time.
LocalDateTime.now();
LocalDateTime.of(2015, Month.FEBRUARY, 20, 06, 30);
LocalDateTime.parse("2015-02-20T06:30:00");
ZonedDateTime API – If we need a zone specific date and time we can go for a ZonedDatTime class. Here the ZoneId used to represent different zones.
ZoneId zoneId = ZoneId.of("Europe/Paris");
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId);
ZonedDateTime.parse("2015-05-03T10:15:30+01:00[Europe/Paris]");
Period and Duration – The Period provides a quantity of time in terms of year, month and days. The Duration provides the quantity of time in terms of seconds and nanosconds.
20. Nashorn Engine in java 8
The Nashorn engine is an implementation of ECMAScript. It was developed in the Java language as the Nashorn project. The Nashorn engine is included in the JDK in Java 1.8. Nashorn Java API allows us to write Java Applications as scripts which can be interpreted by the Nashorn engine.