➤ How to Code a Game
➤ Array Programs in Java
➤ Java Inline Thread Creation
➤ Java Custom Exception
➤ Hibernate vs JDBC
➤ Object Relational Mapping
➤ Check Oracle DB Size
➤ Check Oracle DB Version
➤ Generation of Computers
➤ XML Pros & Cons
➤ Git Analytics & Its Uses
➤ Top Skills for Cloud Professional
➤ How to Hire Best Candidates
➤ Scrum Master Roles & Work
➤ CyberSecurity in Python
➤ Protect from Cyber-Attack
➤ Solve App Development Challenges
➤ Top Chrome Extensions for Twitch Users
➤ Mistakes That Can Ruin Your Test Metric Program
Java 8 Interview Questions | We will see the important questions on Java 8 including why Java 8 was introduced. What are functional interfaces, lambda expressions, method references, and many more? Also see:- Java 8 Stream All Operations with Examples
Table of Contents
- Java 8 QA
- QA on Default & Static Methods in Interface
- QA on Predicate, Function, Consumer, Supplier
- QA on Streams & Parallel Streams
- Processing Of Streams
- Java 8 Grouping By Scenarios
- Java 8 Statistics Example
- Different Ways to Find Duplicate Elements Using Java Stream
- Java 8 Optional QA | Scenario Based
- Stream QA
- Java 8 Short Circuit Operations
- Map Vs FlatMap
- Java Arrays Coding Question using Stream
- Reduce and Peek Operations
- Coding Questions on Java 8
- Refactoring Code to Java 8
Java 8 QA
1. Why Java8 was introduced? What was the main agenda behind the Java 8?
- The significant reason for introducing Java 8 was to introduce Conciseness in the code.
- Java brings in Functional programming which is enabled by Lambda expressions (a powerful tool to create a concise code base.)
- If you have ever observed, with Python, and Scala we can do the same thing in Very little LOC (line of code). By mid 20s Java lost a large market due to these languages. To prevent further loss Java upgraded itself from only OOPs language to some concepts of Functional programming to create a concise code base.
2. What are the new features introduced in Java8?
Lots of new features were added to Java 8. Here is the list of important features that are mostly asked as Java 8 interview questions:-
- Lambda Expression
- Stream API
- Functional Interface
- Default & Static methods in the interface
- Optional class
- Method references & Constructor references
- Date API
3. What are the main advantages of using Java 8?
- More compact code (Less boilerplate code)
- More readable and reusable code
- More testable code
- Parallel operations
4. What is Lambda Expression?
The lambda expression is an anonymous function without a name, return type, and access modifier and has one lambda (->
) symbol.
Normal programming technique:-
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.add(10, 5); // 15
}
public void add(Integer a, Integer b) {
System.out.println(a + b);
}
}
Equivalent Lambda expression:-
import java.util.function.BiConsumer;
public class Test {
public static void main(String[] args) {
BiConsumer<Integer, Integer> biConsumer = (a, b) -> System.out.println(a + b);
biConsumer.accept(10, 5); // 15
}
}
5. What are functional interfaces?
- Functional interfaces are those interfaces that can have only one abstract method.
- It can have any number of static methods or default methods.
- There are many functional interfaces already present in Java such as eg: Comparable, Comparator, and Runnable.
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
6. How lambda expression and functional interfaces are related?
- The functional interface is used to provide reference to lambda expressions.
- Lambda expressions can only be applied to abstract methods of functional interfaces.
Comparator<String> c = (s1, s2) -> s1.compareTo(s2);
(s1, s2) -> s1.compareTo(s2) | This is lambda Expression |
Comparator<String> c | This is a functional interface. |
Thus we can see, that to call lambda expressions we need functional interfaces.
7. Can we create our own functional interface?
Yes. As we know Functional interface is an interface with exactly one single abstract method and can have multiple Static or default methods. To create our own Functional interface, You can do the following steps:-
- Create An interface
- Annotate that with @FunctionalInterface.
- Define exactly one Abstract method.
- There is no restriction on the number of static methods and default methods defined in such an interface.
Java can implicitly identify functional interfaces but still, we can also annotate it with @FunctionalInterface
. It just gives us the security that in case by mistake we add 2 abstract methods then the Compiler will throw the compile time error.
interface Inf {
public void m1();
// default method
default void m2() { }
// static method
public static m3() { }
}
8. What is method reference in Java 8?
- Method reference is the replacement of lambda expressions. It is used to refer method of Functional interface to an existing method. Mainly it is used for code reusability.
- Functional Interface’s Abstract method can be mapped to the specific existing method using the double colon operator (::). This is a Method reference.
- Hence Method reference is an alternative to Lambda expressions.
- Whenever we have an existing Implementation of the Abstract method of our Functional interface then we can go for method reference. If no such method like testImplementation() is available then go for lambda expressions.
In Java 8, method references provide a concise way to refer to methods or constructors without invoking them. They are often used in functional interfaces, where a method reference can be passed as an argument or assigned to a variable.
There are different types of method references in Java:-
- Reference to a static method:
ContainingClass::staticMethodName
- Reference to an instance method of a particular object:
containingObject::instanceMethodName
- Reference to an instance method of an arbitrary object of a particular type:
ContainingType::methodName
- Reference to a constructor:
ClassName::new
Method references can be used as a shorthand for lambda expressions when the lambda body simply calls an existing method. They improve code readability and conciseness, making the code more expressive.
Here’s a simple example of method references:-
import java.util.Arrays;
import java.util.List;
public class MethodReferenceExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Using lambda expression
names.forEach(name -> System.out.println(name));
// Using method reference
names.forEach(System.out::println);
}
}
- Reference to a static method:-
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
String[] names = { "Alice", "Bob", "Charlie", "David" };
// using lambda expression
// Arrays.sort(names, (a, b) -> Test.compareNames(a, b));
// using static method reference
Arrays.sort(names, Test::compareNames);
System.out.println(Arrays.toString(names));
}
public static int compareNames(String a, String b) {
return b.compareTo(a);
}
}
import java.util.Arrays;
import java.util.List;
public class Test1 {
public static void main(String[] args) {
Integer[] numbers = { 4, 2, 1, 6, 5 };
// using lambda expression
// Arrays.sort(names, (a, b) -> Integer.compare(a, b));
// using method reference
Arrays.sort(numbers, Integer::compare);
System.out.println(Arrays.toString(numbers));
List<Integer> list = Arrays.asList(numbers);
list.forEach(System.out::println);
}
}
- Reference to an instance method of an arbitrary object of a particular type.
import java.util.function.BiFunction;
public class Test1 {
public static void main(String[] args) {
// using arbitrary object lambda expression
BiFunction<String, String, Boolean> biFunctionLambda = (str1, str2) -> str1.equals(str2);
System.out.println(biFunctionLambda.apply("hello", "hello"));
// using arbitrary object method reference
BiFunction<String, String, Boolean> biFunctionMethodReference = String::equals;
System.out.println(biFunctionMethodReference.apply("hello", "hello"));
}
}
- Reference to a constructor.
import java.util.function.Function;
public class Test1 {
public static void main(String[] args) {
// Using lambda expression
// Function<String, Person> fun = name -> new Person(name);
// using method reference
Function<String, Person> fun = Person::new;
Person person = fun.apply("Alice");
System.out.println(person.getName());
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
QA on Default & Static Methods in Interface
Before Java 8, we could have only method declarations in the interfaces. The issue was:- If we wanted to change or add any new method in the interface then it required change in all implementing classes. But from Java 8, we can have default methods and static methods in the interfaces which can be used to add new functionalities to the interface without modifying the implementing classes.
For creating a default method in the Java interface, we need to use the “default” keyword with the method signature.
default void myAbstractMeth2() {
System.out.println("New Default Method");
}
The advantage is that all the child classes now don’t need to forcefully provide an implementation of this method, it’s present in this interface by default.
1. What are default methods?
The default method is a way to add new methods to the interface without affecting the implementing classes. Hence with this new feature Java people defended many compile time errors that may arise due to unimplemented methods of interface.
The use of these default methods is “Backward Compatibility” which means if JDK modifies any Interface (without the default method) then the classes that implement this Interface will break.
On the other hand, if you add the default method in an Interface then you will be able to provide the default implementation. This won’t affect the implementing classes.
2. Is it necessary to override the default methods of the interface in Java 8?
- No. Default methods have dummy implementations.
- Implementing classes if ok with dummy implementation then use dummy implementation of default methods.
- If not satisfied then they can override and provide their own implementation.
3. Is the default keyword one of the access modifiers?
No, default is not the access modifier like public, protected, or private. For the default access modifier, we do not use any keyword.
The default keyword was only used in classes till the 1.8 version for switch cases only, but never in the interface. Now it is used for default methods in the interface to provide the default implementation for all implementing classes to use.
All the methods in an interface are public.
@FunctionalInterface
public interface FunctionalInterfaceDemo {
void singleAbstractMethod();
// Has public accessibility
default void test() {
System.out.println("FunctionalInterfaceDemo.test()");
}
}
4. How to override default methods?
- We can override the default method by keeping the same method signature (name + arguments)
- Remove the default keyword from the method because, in class, the default keyword is used only in the switch case to denote the default case if no previous cases are matched. So we can’t use the default keyword in the method inside a Class.
- Add public as an access modifier as in Java 8, by default all methods are public. In the child, we can’t reduce the visibility of the overridden default method while giving our own implementation. Therefore the overridden method must be public.
5. Can we use hashcode() default implementation in interface?
- We can’t give the default implementation of hashcode() in the interface for all implementing classes to use.
- We are not allowed to override Object class methods as default methods in the interface else it will give a compile-time error:- A default method cannot override a method from java.lang.Object
- All implementing classes by default have access to all methods of the Object class.
@FunctionalInterface
public interface FunctionalInterfaceDemo {
void singleAbstractMethod();
// Error:- A default method cannot override a method from java.lang.Object
default boolean equals(Object obj) {
return true;
}
}
6. How do default methods in the interface cope with the diamond problem?
The diamond problem occurs in Java while implementing multiple interfaces. One class implements two interfaces and both of the interfaces have some common method then the implementation class gets confused regarding which parent method to choose. This problem is known as the diamond problem.
The diamond problem of default methods of interface:- if 2 implemented interfaces contain the same default methods then that’s the diamond problem.
In Java, in such a situation, the code will not compile. The solution to the diamond problem:- Use interfaceName.super.methodName()
public interface DiamondProblemInterf1 {
default void add() {
System.out.println("DiamondProblemInterf1.add()");
}
}
public interface DiamondProblemInterf2 {
default void add() {
System.out.println("DiamondProblemInterf1.add()");
}
}
public class DiamondProblemClass implements DiamondProblemInterf1, DiamondProblemInterf2 {
public static void main(String[] args) {
DiamondProblemClass dpc = new DiamondProblemClass();
dpc.add();
}
// own implementation
@Override
public void add() {
System.out.println("DiamondProblemClass.add()");
}
// DiamondProblemInterf2 implementation
// @Override
// public void add() {
// DiamondProblemInterf2.super.add();
// }
// DiamondProblemInterf1 implementation
// @Override
// public void add() {
// DiamondProblemInterf1.super.add();
// }
}
Static Methods in Interface
These are similar to default methods, just that it’s static hence its implementation cannot be changed in child classes. Thus implementing class need not and cannot change the definition.
Static methods of the interface can not be overridden. Similar to static methods of a class, these should be called through the interface name. Example:- interface.staticMethodName;
Why do we need static methods in the interface?
- The interface static method helps us in providing security by not allowing implementation classes to override them.
- Interface static methods are good for providing utility methods, for example, null checking i.e. whose definition will never change.
7. Why Static methods were introduced in Java8?
- The only reason for introducing static methods in the interface is that you can call those methods with just the interface name. No Need to create a class and then its object.
- Since Interface can never contain:-
- Constructors,
- Static blocks,
- Nothings are costly in terms of memory and performance.
- We don’t need to create an object and hence if we have everything static, then use interface rather than class. We have this flexibility only after Java 8, before that you need to create a class.
public interface Inter1 {
public static void show() {
System.out.println("Inter1.show()");
}
}
public class Test {
public static void main(String[] args) {
Inter1.show();
}
}
8. Are Static Methods available to implementation classes by default?
Static methods are not available for implementing classes. They are not default methods. They are static.
Hence we can call these methods using the Interface name explicitly from the implementing classes as implementing classes won’t have access to these methods directly. The disadvantage of static methods of the interface is that they are not available to implementation classes.
QA on Predicate, Function, Consumer, Supplier
1. What are predicates?
The Predicate is a predefined functional Interface having only 1 abstract method test(T t):-
public boolean test(T t);
Whenever we want to check some boolean condition then we can go for Predicate.
2. How to use Predicates?
Assume we need to test if the length of the given string is greater than or equal to 9. Then in such situations where we need to test conditions, use the test() method of predicate.
Normal Programming:-
public class Test {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.testStringLength("Know Program")); // true
System.out.println(test.testStringLength("Java")); // false
}
public boolean testStringLength(String string) {
if (string.length() >= 9) {
return true;
}
return false;
}
}
Using Predicate:-
import java.util.function.Predicate;
public class Test {
public static void main(String[] args) {
Predicate<String> checkLength = str -> str.length() >= 9;
System.out.println(checkLength.test("Know Program")); // true
System.out.println(checkLength.test("Java")); // false
}
}
3. What are the Type parameter and return types of Predicates?
Input to predicate can be anything like:-
- Predicate<String>
- Predicate<Integer>
- Predicate<Employee>
Hence only 1 type-argument is required which is input type in predicate. Return type is not required as it is always Boolean only.
4. What are the advantages of Predicates?
- Code Reusability.
- If the same conditions are used 100 times in a program, then we can write once and just use 100 times with checkLength.test(different string to be tested).
- Functional interfaces hold conditional checks.
5. What is Predicate joining?
We can combine predicates in serial predicates. Three ways to join:-
- And
- Or
- Negate
If you want to test 2 conditions:-
- To check the length of the string
- To check if the length is even.
import java.util.function.Predicate;
public class Test {
public static void main(String[] args) {
// check string length is >= 9
Predicate<String> checkLength = str -> str.length() >= 9;
System.out.println(checkLength.test("Know Program")); // true
// Check string length is even or not
Predicate<String> checkEvenLength = str -> str.length() % 2 == 0;
System.out.println(checkEvenLength.test("Know Program")); // true
// Join with and()
// Check string length is >= 9 AND length is even
System.out.println(checkLength.and(checkEvenLength).test("KnowProgram")); // false
// Join with or()
// Check string length is >= 9 OR length is even
System.out.println(checkLength.or(checkEvenLength).test("Java")); // true
// check string length is < 9 (Opposite of predicate checkLength)
System.out.println(checkLength.negate().test("Java")); // true
// Check string length is odd or not
System.out.println(checkEvenLength.negate().test("Know Program")); // false
}
}
6. What are Functions?
The Function is a predefined Functional Interface having only 1 abstract method apply(T t);-
R apply(T t);
Given some input to perform some operation on input and then produce/return the result (not necessarily a boolean value).
This takes 1 input and returns one output. In the Predicate we used to take 1 input and the return type is always boolean. In the Function, the return type is not fixed hence we declare both input type and return type.
Normal Programming:-
public class Test {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.square(5)); // 25
}
public int square(int i) {
return i*i;
}
}
Using Function:-
import java.util.function.Function;
public class Test {
public static void main(String[] args) {
Function<Integer, Integer> square = i -> i * i;
System.out.println(square.apply(9)); // 81
}
}
7. Difference between Predicate and Function?
Predicate | Function |
It has the return type of Boolean. It is used for conditional checks. | It has the return type as Object. It is used to perform operations and return results. |
It is written in the form of Predicate<T> which accepts a single argument. | It is written in the form of Function<T, R> which also accepts a single argument but returns any type of object denoted by R. |
It contains the test() abstract method. | It contains the apply() abstract method. |
8. What is Functional chaining?
We can combine / chain multiple functions with andThen() or compose():-
f1.andThen(f2).apply(Input)
:- first f1 then f2f1.compose(f2).apply(Input)
:- first f2 then f1
Multiple functions can be chained together like:- f1.andThen(f2).andThen(f3).andThen(f4).apply(Inputs);
import java.util.function.Function;
public class Test {
public static void main(String[] args) {
Function<Integer, Integer> doubleVal = i -> 2 * i;
System.out.println(doubleVal.apply(9)); // 18
Function<Integer, Integer> cube = i -> i * i * i;
System.out.println(cube.apply(2)); // 8
// First double the value then cube it
System.out.println(doubleVal.andThen(cube).apply(2)); // 64
// First cube the value then double double it
System.out.println(cube.andThen(doubleVal).apply(2)); // 16
System.out.println(doubleVal.compose(cube).apply(2)); // 16
// Cube two times then double two times
System.out.println(cube.andThen(cube).andThen(doubleVal)
.andThen(doubleVal).apply(2));
// 2048
}
}
9. What is Consumer Functional Interface?
It will consume the Item. Consumers never return anything (never supply), they just consume.
Example usages:- Take any object and save its details in the Database and don’t return anything. It has only one abstract method accept().
Interface Consumer<T> {
public void accept(T t)
}
Normal Programming:-
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.squareInt(9);
}
public void squareInt(int i) {
int squareValue = i * i;
System.out.println("Square value: " + squareValue);
}
}
Using Consumer:-
import java.util.function.Consumer;
public class Test {
public static void main(String[] args) {
Consumer<Integer> square = i -> System.out.println("Square Value: " + i * i);
square.accept(9);
}
}
10. What is Consumer chaining?
We can combine / chain multiple consumers with andThen():-
c1.andThen(c2).apply(Input);
– first c1 then c2
Note:- No compose() in Consumer Functional Interface.
Multiple consumers can be chained together like:-c1.andThen(c2).andThen(c3).andThen(c4).apply(Inputs);
import java.util.function.Consumer;
public class Test {
public static void main(String[] args) {
Consumer<Integer> square = i -> System.out.println("Square Value: " + i * i);
square.accept(9);
Consumer<Integer> doubleVal = i -> System.out.println("Double Value: " + i * 2);
doubleVal.accept(9);
square.andThen(doubleVal).accept(9);
}
}
11. What is the Supplier Functional Interface?
- It will just supply the required objects and will not take any input. It’s always going to supply never consume/take any input.
- It has only one abstract method get().
- It does not have any static and default methods.
- Chaining of Supplier is not applicable because it does not take any input.
Interface Supplier<T> {
T get();
}
Example:- always supply the current date.
Normal Programming:-
import java.util.Date;
public class Test {
public static void main(String[] args) {
Test test = new Test();
System.out.println(test.getCurrentDate());
}
public Date getCurrentDate() {
return new Date();
}
}
Using Supplier:-
import java.util.Date;
import java.util.function.Supplier;
public class Test {
public static void main(String[] args) {
Supplier<Date> currentDate = () -> new Date();
System.out.println(currentDate.get());
}
}
12. Advantages of Supplier Function?
- Write once, and use anywhere.
- Code Reusability.
Summary on Predicate, Function, Consumer, Supplier:-
- Predicate<T> => test() => Accepts T, Return Boolean
- Function<T, R> => apply() => Accepts T, return R (anything)
- Consumer<T> => accept() => Accepts T, return nothing
- Supplier<R> => get() => Accepts nothing, Return R (anything)
Interface | Abstract Method | Default Method(s) |
---|---|---|
Predicate<T> | boolean test(T t); | and(), or(), negate() |
Function<T, R> | R apply(T t); | andThen(), compose() |
Consumer<T> | void accept(T t); | andThen() |
Supplier<T> | T get(); | Doesn’t have any default methods. |
13. Use of BiConsumer, BiFunction, BiPredicate, and why no BiSupplier?
What if we need 2 arguments for the operation? Then we need BiXYZ Functional Interfaces. There is no input in the Supplier so no 1 or 2 Input arguments are needed. Hence no BiSupplier is available.
Example:-
- BiPredicate:- Take two numbers and check whether the sum of them is >= 9.
- BiFunction:- Take two numbers and return the multiplication.
- BiConsumer:- Take two numbers and display their sum.
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
public class Test {
public static void main(String[] args) {
// Take two numbers and check whether the sum of them is >= 9
BiPredicate<Integer, Integer> checkSumOfTwo = (a, b) -> a + b >= 9;
System.out.println(checkSumOfTwo.test(2, 5)); // false
System.out.println(checkSumOfTwo.test(7, 5)); // true
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
System.out.println(multiply.apply(2, 5)); // 10
System.out.println(multiply.apply(7, 5)); // 35
BiConsumer<Integer, Integer> sum = (a, b) -> System.out.println(a + b);
sum.accept(10, 20); // 30
}
}
Interface | Abstract Method | Default Method(s) |
---|---|---|
BiPredicate<T, U> | boolean test(T t, U u); | and(), or(), negate() |
BiFunction<T, U, R> | R apply(T t, U u); | andThen(), compose() |
BiConsumer<T, U> | void accept(T t, U u); | andThen() |
14. If we want to operate on 3 arguments then can we use TriPredicate?
There are no TriPredicate or TriFunction etc. Similarly, no QuadPredicate or QuadFunction are given. Java 8 has inbuilt Functional interfaces that can take only 1 or 2 arguments, no more.
QA on Streams & Parallel Streams
1. What are streams?
- If we want to process bulk objects of collection then go for the streams concept.
- It provides a way to operate on collection in Java 8.
- It’s a special iterator class that allows processing collections of objects in a functional manner.
Example: fetch all objects from the collection of lists whose value is greater than 15.
Using Normal programming:-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(10, 20, 12, 33, 9);
System.out.println(findElements(list)); // [20, 33]
}
public static List<Integer> findElements(List<Integer> list) {
List<Integer> newAl = new ArrayList<>();
for (Integer i : list) {
if (i >= 15) {
newAl.add(i);
}
}
return newAl;
}
}
Using Streams:-
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(10, 20, 12, 33, 9);
List<Integer> newList = list.stream().filter(x -> x >= 15)
.collect(Collectors.toList());
newList.forEach(x -> System.out.println(x));
}
}
2. Why streams were introduced in Java 8 if we already had java.io.stream?
- The java.io.streams is a sequence of characters or binary data that is used to be written to a file or to read data from a file.
- While streams of Java 1.8 are nowhere related to files, it’s related to collection objects.
- The java.io.streams streams are related to files whereas java 8 streams are related to collection objects.
- Hence if we need to perform some operations on collection there we should go for streams.
3. What is the difference between Java 8 streams and collections?
When we want to represent a group of items as a single entity then we should use the collection concept. But If we want to perform operations on bulk objects in collection then we should go for Streams.
4. Steps to create and process stream?
We can get stream objects by:- Stream s = collectionObject.stream();
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(10, 20, 12, 33, 9);
Stream<Integer> stream = list.stream();
stream.forEach(System.out::println);
}
}
Once we get the stream object we can process the object of collection. Processing of the stream consists of 2 steps/ stages:-
- Configuration of the stream by map() or filter()
- Processing that configuration
Filter even elements from the list using stream:-
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(10, 20, 12, 33, 9);
// Filter even elements from list
Stream<Integer> stream = list.stream().filter(x -> x % 2 == 0);
stream.forEach(x -> System.out.println(x));
// list.stream().filter(x -> x % 2 == 0).forEach(System.out::println);
}
}
Filter even elements from the list and store them in a new list:-
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(10, 20, 12, 33, 9);
// Filter even elements from the list and store them in a new list
List<Integer> evenList = list.stream()
.filter(x -> x % 2 == 0).collect(Collectors.toList());
System.out.println(evenList); // [10, 20, 12]
}
}
5. When to use map() on the stream objects?
If we don’t want to filter out and we rather want to create a new object against each existing stream object based on some function then we can use map() on the stream objects. Example:- In the given stream create a new object by squaring its value.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(10, 20, 12, 33, 9);
// create another list with double of previous list
List<Integer> doubleList = list.stream()
.map(x -> x * x).collect(Collectors.toList());
System.out.println(doubleList); // [100, 400, 144, 1089, 81]
// list.stream().map(x -> 2 * x).forEach(System.out::println);
}
}
6. What is difference between filter() and map() in Java 8 stream?
If we want to fetch/filter objects from collections eg: filter only even numbers from array list collection we use filter() for the configuration of the stream.
If we want to perform some operation on each object of the collection then create another mapped object with a different value(after the operation is performed ) for each object of that collection, then use the map().
In filter(), because of filtering, the number of objects in the filtered list is less than the original list while in the map() same number of objects are there in both the new and original list created.
Processing Of Streams
1. How to process elements using collect()?
If we want to collect elements of the stream after filtering or mapping and add them to the required collection then use the collect() method.
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(15, 25, 52, 10, 8, 9, 25);
// filter numbers > 20 and store it in a list
List<Integer> newlist = list.stream()
.filter(num -> num > 20)
.collect(Collectors.toList());
System.out.println(newlist); // [25, 52, 25]
// filter numbers > 20 and get unique elements
Set<Integer> newSet = list.stream()
.filter(num -> num > 20)
.collect(Collectors.toSet());
System.out.println(newSet); // [52, 25]
}
}
2. How to process elements using count()?
If we want to count how many elements are there in the collection that satisfy a given condition then use the collect method.
List<Integer> list = Arrays.asList(15, 25, 52, 10, 8, 9, 25);
long count = list.stream().filter(num -> num > 20).count();
System.out.println(count); // 3
3. How to process elements using sorted()?
If we want to sort elements inside a stream use this sorted() method. It will sort based on the default natural sorting order. If we want to sort using a customized sorting order then use a comparator.
List<Integer> list = Arrays.asList(15, 25, 52, 10, 8, 9, 21);
Stream<Integer> sortedList = list.stream().sorted();
sortedList.forEach(System.out::println);
// sort and store to the list
List<Integer> sortedList = list.stream().sorted().collect(Collectors.toList());
System.out.println(sortedList); // [8, 9, 10, 15, 21, 25, 52]
// sort and filter
Stream<Integer> filteredSortedStream = list.stream()
.filter(num -> num > 20)
.sorted();
filteredSortedStream.forEach(x -> System.out.println(x)); // 21 25 52
4. How to sort elements in descending order?
Using Comparator. If we want to sort using a customized sorting order then use a comparator. Sort in descending order:-
List<Integer> list = Arrays.asList(15, 25, 52, 10, 8, 9, 21);
List<Integer> sortedList = list.stream()
.sorted((i1, i2) -> -i1.compareTo(i2))
.collect(Collectors.toList());
System.out.println(sortedList); // [52, 25, 21, 15, 10, 9, 8]
To sort in the reverse order of the natural sorting order we can also use the Collections class reverseOrder() method as follows:-
List<Integer> list = Arrays.asList(10, 20, 12, 33, 9);
List<Integer> sortedList = list.stream()
.sorted(Collections.reverseOrder())
.collect(Collectors.toList());
System.out.println(sortedList);
5. How to process elements using min() and max() methods?
- min(Comparator) will return the minimum value based on the defined comparator.
- max(Comparator) will return the maximum value based on the defined comparator.
The min(), and max() methods take a comparator to find and decide how to sort it, and then it fetches first/last.
List<Integer> list = Arrays.asList(15, 25, 52, 10, 8, 9, 21);
Integer min = list.stream().filter(n -> n > 20)
.min((i1, i2) -> i1.compareTo(i2)).get();
System.out.println(min);
System.out.println(list.stream()
.min((i1, i2) -> i1.compareTo(i2)).get()); // 8
System.out.println(list.stream()
.min((i1, i2) -> -i1.compareTo(i2)).get()); // 52
System.out.println(list.stream()
.min((i1, i2) -> i2.compareTo(i1)).get()); // 52
Integer max = list.stream().max((i1, i2) -> i1.compareTo(i2)).get();
System.out.println(max); // 52
Integer min = list.stream().max((i1, i2) -> -i1.compareTo(i2)).get();
System.out.println(min); // 8
5. How to process elements using forEach()?
All the above methods return something but this method does not return anything. Rather this method takes a lambda expression as an argument and applies that lambda expression to each element present in that stream.
List<Integer> list = Arrays.asList(15, 25, 52, 10, 8, 9, 21);
list.stream().forEach(n -> System.out.println(n));
list.stream().forEach(System.out::println);
6. How do elements be processed using the toArray() method?
To copy elements present in the steam to a specified array. Note:- It always returns Object[ ].
List<Integer> list = Arrays.asList(15, 25, 52, 10, 8, 9, 21);
Object[] arr1 = list.toArray();
System.out.println(Arrays.toString(arr1));
Object[] arr2 = list.stream().filter(n -> n > 20).toArray();
System.out.println(Arrays.toString(arr2)); // [25, 52, 21]
However, we can convert it to an Integer array as follows:-
Integer[] arr1 = list.toArray(new Integer[0]);
Integer[] arr2 = list.toArray(Integer[]::new);
7. How do elements be processed using the Stream.of() method?
The stream concept is not applicable just to the collections it’s also applicable to “ANY GROUP OF VALUE”. Even for arrays you can use stream. Stream.of() method can take any group of values and convert them to stream.
Stream<Integer> stream = Stream.of(15, 25, 52, 10, 8, 9, 21);
stream.forEach(n -> System.out.println(n));
String[] names = { "Know", "Program", "Java" };
Stream.of(names).filter(name -> name.length() > 4)
.forEach(name -> System.out.println(name)); // Program
8. What is a Parallel Stream?
- Java Parallel Streams came into the picture after Java 1.8.
- It’s meant to utilize multiple cores of processors.
- Till Now our Java code has 1 stream of processing where it executes sequentially.
- But when we use parallel streams, we divide code into multiple streams that execute parallelly, on separate cores and the final result is the outcome of individual cores outcomes combined.
9. Sequential VS Parallel Streams?

The output of this sequential steam is T1, T2, T3, T4 in sequential order. Tasks are executed and the output of 1 can be input to another.

- The output of this Parallel stream is T2, T4, T1, and T3 not in sequential order.
- The order of execution is not under control.
- Hence it’s advisable to use parallel stream only when the order of execution of threads does not matter and the state of one element does not affect another.
Java 8 Grouping By Scenarios
Prerequisite for many below questions and examples:-
import java.util.Objects;
public class Employee {
private Integer id;
private Integer age;
public Employee(Integer id, Integer age) {
this.id = id;
this.age = age;
}
// getter and setter
@Override
public String toString() {
return "Employee [id=" + id + ", age=" + age + "]";
}
// hashCode based on id only
@Override
public int hashCode() {
return Objects.hash(id);
}
// equals based on id only
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
return Objects.equals(id, other.id);
}
}
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(new Employee(10, 18),
new Employee(11, 25), new Employee(12, 24),
new Employee(13, 25), new Employee(14, 18),
new Employee(15, 25), new Employee(11, 25),
new Employee(14, 18));
employees.stream().forEach(System.out::println);
}
}
1. Why do we need a Grouping Collector?
Given a stream of objects, there are scenarios where these objects need to be grouped based on a certain distinguishing characteristic they possess. Example- Create a map with the key as Age and value as a list of employees in that age group.
This concept of grouping is the same as the ‘group by’ clause in SQL which takes an attribute, or a calculated value derived from attribute(s), to divide the retrieved records into distinct groups.
Generally what we used to do to group by is – iterate over each object, checking which group the object being examined falls in, and then adding that object to its correct group. The group itself is held together using a Collection instance.
2. How Grouping Collector Works?
Grouping collectors use a classification function, which is an instance of the Function<T, K>.
T Type of the object in the stream
K is the ‘group names’ or ‘group keys’.
For every value of K, there is a collection of objects all of which return that value of K when subjected to the classification function.
All these K-values and corresponding Collections of stream objects are stored by the grouping collector in a Map<K, Collection<T>>.
3. Grouping collector with a single parameter.
Map<Integer, List<Employee>> grouppedEmp = employees.stream()
.collect(Collectors.groupingBy(emp -> emp.getAge()));
System.out.println(grouppedEmp);
Output:-
{
18=[
Employee[id=10,age=18],
Employee[id=14,age=18],
Employee[id=10,age=18],
],
24=[
Employee[id=12,age=24],
],
25=[
Employee[id=11,age=25],
Employee[id=13,age=25],
Employee[id=15,age=25],
Employee[id=11,age=25],
]
}
The classification function passed to groupingBy() method is the method specified as “Employee.getAge()”. As the end result of applying the grouping collector for achieving this, we want a Map with keys as age and corresponding values as a List of employees of that age.
4. 2 parameter Collectors.groupingBy()
We can also use a user-specified Collector to collect grouped elements. The 1st variant of groupingBy() always returns a List containing the elements of a group, the 2nd variant of groupingBy() provides the flexibility to specify how the grouped elements need to be collected using a second parameter which is a Collector.
So, instead of just storing the groups in the resultant Map as Lists, we can instead store them in say Sets:- (Collectors.groupingBy(Employee.getAge(), Collectors.toSet()));
Fetch all the employees on the basis of their age key-value pair, but the list of the employees should be unique:-
Map<Object, Set<Employee>> employees = employeeService.findAll().stream()
.collect(Collectors.groupingBy(emp -> emp.getAge(), Collectors.toSet()));
Output:-
{
18=[
Employee[id=10,age=18],
Employee[id=14,age=18],
],
24=[
Employee[id=12,age=24],
],
25=[
Employee[id=11,age=25],
Employee[id=13,age=25],
Employee[id=15,age=25],
]
}
5. 3 Parameter Collectors.groupingBy()
Here, the 2nd parameter can be used to sort the result based on the key. Fetch all the employees on the basis of their age key-value pair, but the list of the employees should be unique and the result of the key-value pair should be sorted based on the ascending order of the ages.
Map<Object, Set<Employee>> grouppedSortedSet = employees.stream()
.collect(Collectors.groupingBy(emp -> emp.getAge(), TreeMap::new, Collectors.toSet()));
Output:-
{
18=[
Employee[id=10,age=18],
Employee[id=14,age=18],
],
24=[
Employee[id=12,age=24],
],
25=[
Employee[id=11,age=25],
Employee[id=13,age=25],
Employee[id=15,age=25],
]
}
The same can be written as:-
.collect(Collectors.groupingBy(emp -> emp.getAge(),
() -> new TreeMap<>(), Collectors.toSet()));
If we want to sort descending order or the key (ages):-
Map<?, Set<Employee>> groupedSortedSet = employees.stream()
.collect(Collectors.groupingBy(
emp -> emp.getAge(),
() -> new TreeMap<>(Comparator.reverseOrder()),
Collectors.toSet()
));
System.out.println(groupedSortedSet);
Output:-
{
25=[
Employee[id=11,age=25],
Employee[id=13,age=25],
Employee[id=15,age=25],
],
24=[
Employee[id=12,age=24],
],
18=[
Employee[id=10,age=18],
Employee[id=14,age=18],
]
}
6. Difference between different parameterized Collectors.groupingBy()?
How variant#1 and variant#2 of grouping collector are closely related
In the Collectors class’ code, the first variant of grouping Collector which accepts just the classification function as input does not itself return the Collector which processes the Stream elements. Instead, internally it delegates the call forward to the second variant with the call-
groupingBy(classifier, toList()). So, the first variant of grouping collector is that’s why just a convenient way of invoking the second variant with the downstream collector ‘hardcoded’ as a List.
In short, 1 parameter internally calls 2 parameter method, and 2 parameter method internally calls 3 parameter method.
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream) {
// code
}
Java 8 Statistics Example
1. Java 8 Statistics Example: Get count, min, max, sum, and the average for numbers.
Given employee DB, fetch max aged employee, Min aged employee, find the average age of all employees working in an organization, etc.
stream().mapToInt(x -> x) .summaryStatistics();
Since these statistics operations are numeric in nature, it’s essential to call the mapToInt() method. It provides us with utility methods like getMin(), getMax(), getSum(), or getAverage().
By using these general-purpose methods, we can easily do a lot of things that require a lot of code before Java 8.
import java.util.Arrays;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(new Employee(10, 18),
new Employee(11, 25), new Employee(12, 24),
new Employee(13, 25), new Employee(14, 18),
new Employee(15, 25), new Employee(11, 25),
new Employee(14, 18));
// System.out.println(employees);
List<Integer> ages = employees.stream()
.map(emp -> emp.getAge())
.collect(Collectors.toList());
System.out.println("Ages: " + ages);
// Ages: [18, 25, 24, 25, 18, 25, 25, 18]
IntSummaryStatistics summary = ages.stream()
.mapToInt(x -> x)
.summaryStatistics();
System.out.println(summary);
System.out.println(summary.getMin() + ", "
+ summary.getMax() + ", "
+ summary.getCount() + ", "
+ summary.getAverage() + ", "
+ summary.getSum()); // 18, 25, 8, 22.25, 178
}
}
2. How to get a slice of a stream in Java?
Input: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Output: [2, 3, 4]
Explanation: The output contains a slice of the stream from index 1 to 3.
Using skip) and limit():- Stream API in Java provides a skip() method which is used to discard the other non-required elements from the stream. It also provides a limit() function which is applied to fetch the new stream with the specified index as the limit, in the encountered order.
stream.skip(startIndex);
// Specify the number of elements to skiplimit(endIndex - startIndex + 1);
// Specify the no. of elements in the stream that should be limited
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(new Employee(10, 18),
new Employee(11, 25), new Employee(12, 24),
new Employee(13, 25), new Employee(14, 18),
new Employee(15, 25), new Employee(11, 25),
new Employee(14, 18));
// System.out.println(employees);
// get all unique ages
Set<Integer> uniqueAges = employees.stream()
.map(emp -> emp.getAge())
.collect(Collectors.toSet());
System.out.println("Ages: " + uniqueAges);
// Ages: [18, 24, 25]
// 2nd & 3rd youngest age
List<Integer> secondAndThirdYoungestAges = uniqueAges
.stream()
.skip(1)
.limit(2)
.collect(Collectors.toList());
System.out.println(secondAndThirdYoungestAges); // [24, 25]
}
}
3. Convert Strings to uppercase and Join them with a comma.
For example given an employee table fetch the employee object with ID return the name in upper case, and concat all of them separated by a space using Java 8.
String ListOfNames = list.stream().map(x -> x.toUpperCase())
.collect(Collectors.joining(", "));
We have joined all String using the Collectors.joining(“,”) method, another utility method from Java 8, which can join String by using a given delimiter.
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
Stream<String> names = Stream.of("Hello", "World");
String string = names.map(name -> name.toUpperCase())
.collect(Collectors.joining(", "));
System.out.println(string); // HELLO WORLD
}
}
Different Ways to Find Duplicate Elements Using Java Stream
1. How to find duplicate elements in a Stream in Java?
Method-1: using Collections.frequency(list, i)
Count the frequency of each element, using the Collections.frequency() method. Then for each element in the collection list, if the frequency of any element is more than one, then this element is a duplicate element.
stream().filter(i -> Collections.frequency(list, i) > 1) .collect(Collectors.toSet());
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(new Employee(10, 18),
new Employee(11, 25), new Employee(12, 24),
new Employee(13, 25), new Employee(14, 18),
new Employee(15, 25), new Employee(11, 25),
new Employee(14, 18));
// System.out.println(employees);
Set<Employee> duplicateEmployees = employees.stream()
.filter(emp -> Collections.frequency(employees, emp) > 1)
.collect(Collectors.toSet());
System.out.println("Ages: " + duplicateEmployees);
}
}
Method2:- Using Set
public class Test {
public static void main(String[] args) {
List<Integer> ages = List.of(18, 25, 24, 25, 18, 25, 25, 18);
Set<Integer> uniqueAge = new HashSet<>();
Set<Integer> duplicateAges = ages.stream()
.filter(age -> !uniqueAge.add(age))
.collect(Collectors.toSet());
System.out.println(uniqueAge); // [18, 24, 25]
System.out.println(duplicateAges); // [18, 25]
}
}
Method-3:- Using Collectors.groupingBy()
The groupingBy() method of the Collectors class in Java groups the objects by some property. So we will pass the property of redundancy and collect the result in a Set.
For each element in the stream, group them along with their frequency in a map, using the Collectors.groupingBy() method. Then for each element in the collected map, if the frequency of any element is more than one, then this element is a duplicate element.
public class Test {
public static void main(String[] args) {
List<Integer> ages = List.of(18, 25, 24, 25, 18, 25, 25, 18);
Map<Integer, Long> agesCount = ages.stream()
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()
)
);
System.out.println(agesCount); // {18=3, 24=1, 25=4}
Set<Integer> uniqueAges = agesCount.entrySet()
.stream()
.filter(entry -> entry.getValue() > 1)
.map(entry -> entry.getKey())
.collect(Collectors.toSet());
System.out.println(uniqueAges); // [18, 25]
}
}
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(new Employee(10, 18),
new Employee(11, 25), new Employee(12, 24),
new Employee(13, 25), new Employee(14, 18),
new Employee(15, 25), new Employee(11, 25),
new Employee(14, 18));
// employees.stream().forEach(System.out::println);
Map<Employee, Long> employeesCount = employees.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(employeesCount);
Set<Employee> duplicateEmployees = employeesCount.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.map(entry -> entry.getKey()).collect(Collectors.toSet());
System.out.println(duplicateEmployees);
// [Employee [id=11, age=25], Employee [id=14, age=18]]
}
}
In a single line it can be written as follows:-
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(new Employee(10, 18),
new Employee(11, 25), new Employee(12, 24),
new Employee(13, 25), new Employee(15, 24), new Employee(11, 25));
System.out.println(employees);
Set<Employee> duplicateEmployees = employees.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
// convert this map into a stream
.entrySet().stream()
// check if frequency > 1 for duplicate elements, find such elements
.filter(m -> m.getValue() > 1).map(Map.Entry::getKey)
// collect them in a set
.collect(Collectors.toSet());
System.out.println(duplicateEmployees);
// [Employee [id=11, age=25], Employee [id=14, age=18]]
}
}
Java 8 Optional QA | Scenario Based
1. What is Optional? Why and how can you use it?
Java Optional class provides a way to deal with null values. It is used to represent whether a value is present or not. Java 8 added a new class Optional available in java.util
package. An optional class was introduced in Java 8 to avoid NullPointerException in Java.
A NullPointerException is a common issue in Java applications. To prevent this, we normally add frequent NULL checks in our code to check if a variable is not empty before we use it in our program. Optional provides a better approach to handling such situations.
You can view Optional as a single-value container that either contains a value or doesn’t (it is then said to be “empty”),
Java 8’s Benefits Optional:-
- Null checks aren’t necessary.
- NullPointerException is no longer thrown at runtime.
- We can create neat and tidy APIs.
- There will be no more code for a boilerplate.
To demonstrate it, let us create a Spring starter project with dependencies:- Lombok, Spring Data JPA, MySQL Driver, Spring Web, and Spring Boot DevTools.
In application.properties:-
spring.application.name=demo
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=update
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name="emp")
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> { }
DB Queries:-
use test;
INSERT INTO `emp` (`id`, `name`) VALUES ('1', 'Ramesh');
INSERT INTO `emp` (`id`, `name`) VALUES ('2', 'Rakesh');
INSERT INTO `emp` (`id`, `name`) VALUES ('3', 'Krishna');
INSERT INTO `emp` (`id`) VALUES ('4');
INSERT INTO `emp` (`id`) VALUES ('5');
1. Returning Value With get()
get() can only return a value if the wrapped object is not null; otherwise, it throws a no such element exception
This is the major flaw of the get() method. Ideally, Optional should help us avoid such unforeseen exceptions. Therefore, this approach works against the objectives of Optional and will probably be deprecated in a future release.
@RestController
@RequestMapping("/api/employee")
public class EmployeeController {
@Autowired
EmployeeRepository employeeRepository;
@GetMapping("/{id}")
public ResponseEntity<?> findById(@PathVariable Integer id) {
Employee employee = employeeRepository.findById(id).get();
return ResponseEntity.ok(employee);
}
@GetMapping
public ResponseEntity<List<Employee>> findAll() {
return ResponseEntity.ok(employeeRepository.findAll());
}
}
2. Checking Value Presence
- When we have an Optional object returned from a method or created by us, we can check if there is a value in it or not with the isPresent() method.
- This method returns true if the wrapped value is not null.
@GetMapping("/{id}")
public ResponseEntity<?> findById(@PathVariable Integer id) {
Optional<Employee> emp = employeeRepository.findById(id);
if (emp.isPresent()) {
return new ResponseEntity<>(emp.get(), HttpStatus.OK);
}
return new ResponseEntity<>("Employee with Given ID is not Present",
HttpStatus.NOT_FOUND);
}
We can also do the opposite with the isEmpty() method. The isEmpty() method was introduced in Java 11.
@GetMapping("/{id}")
public ResponseEntity<?> getEmployeeById(@PathVariable Integer id) {
Optional<Employee> emp = employeeRepository.findById(id);
if (emp.isEmpty()) {
return new ResponseEntity<>("Not Found", HttpStatus.NOT_FOUND);
}
return ResponseEntity.ok(emp.get());
}
3. Creating Optional Objects
- We can create an optional object with Optional’s static method – Of()
- The argument passed to the of() method can’t be null. Otherwise, we’ll get a NullPointerException.
- But in case we expect some null values, we can use the ofNullable() method:
- By doing this, if we pass in a null reference, it doesn’t throw an exception but rather returns an empty Optional object:
Return employeeName by Id.
@GetMapping("/{id}")
public ResponseEntity<?> findById(@PathVariable Integer id) {
Optional<Employee> emp = employeeRepository.findById(id);
if (emp.isPresent()) {
// In the below line, Optional.of() will give NullPointerException if passed
// parameter is null
// Optional<String> name = Optional.of(emp.get().getName());
Optional<String> name = Optional.ofNullable(emp.get().getName());
if (name.isPresent()) {
return new ResponseEntity<>(name.get().toUpperCase(), HttpStatus.OK);
}
return new ResponseEntity<>("Name is null", HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>("Employee with Given ID is not Present",
HttpStatus.NOT_FOUND);
}
4. Conditional Action With ifPresent()
The ifPresent()
method enables us to run some code on the wrapped value if it’s found to be non-null. Before Optional, we’d do:-
if(name != null) {
System.out.println(name.length());
}
This code checks if the name variable is null or not before going ahead to execute some code on it. This approach is lengthy, and that’s not the only problem – it’s also prone to error.
Indeed, what guarantees that after printing that variable, we won’t use it again and then forget to perform the null check?
This can result in a NullPointerException at runtime if a null value finds its way into that code. When a program fails due to input issues, it’s often a result of poor programming practices. Optional makes us deal with nullable values explicitly as a way of enforcing good programming practices.
Given an employee table, fetch the employee with the given ID and print its name in upper case. If the name is null print “The name is null “, using Java 8.
If you are using Java 9+, you can use ifPresentOrElse() method:-
nullableEmpName.ifPresentOrElse(name -> System.out.println("present"),
()-> System.out.println("No vaue present"));
5. Default Value With orElse()
Given an employee DB, fetches the employee by ID and returns the name of the employee. If it’s not present return the default name using Java 8.
The orElse() method is used to retrieve the value wrapped inside an Optional instance. It takes one parameter, which acts as a default value. The orElse() method returns the wrapped value if it’s present, and its argument otherwise:-
String name = Optional.ofNullable(nullName).orElse(“Anonymous”);
Optional<Employee> e = repo.findById(id);
if(!e.isEmpty()) {
String name = Optional.ofNullable(e.get().getName())
.orElse("Anonymous");
return new ResponseEntity<>(name, HttpStatus.OK);
}
6. Default Value With orElseGet()
The orElseGet() method is similar to orElse(). However, instead of taking a value to return if the Optional value is not present, it takes a supplier functional interface, which is invoked and returns the value of the invocation:-
String name = Optional.ofNullable(nullName)
.orElseGet(() -> "Anonymous");
Optional<Employee> e = repo.findById(id);
if(!e.isEmpty()) {
String name = Optional.ofNullable(e.get().getName())
.orElseGet(() -> "Anonymous");
return new ResponseEntity<>(name, HttpStatus.OK);
}
7. Difference between orElse() and orElseGet() method of Optional class.
There is a very important difference between the two that can affect the performance of our code drastically if not well understood. Let’s create a method callMe()
which takes no arguments and returns a default value:-
public String callMe() {
System.out.println("Getting Default Value");
return "Anonymous";
}
Optional<Employee> e = repo.findById(id);
if(!e.isEmpty()) {
// String name = Optional.ofNullable(e.get().getName())
// .orElse(callMe());
String name = Optional.ofNullable(e.get().getName())
.orElseGet(() -> callMe());
return new ResponseEntity<>(name, HttpStatus.OK);
}
When using orElse(), whether the wrapped value is present or not, the default object is created (callMe() method will get called). So in this case, we have just created one redundant object that is never used. Whereas, when using orElseGet() to retrieve the wrapped value, the callMe() method will not be invoked if the contained value is present.
In case of orElse() method, still, the function is still called and a default object is being created, which will never be used as the value is present for the variable wrapped in the Optional object. But orElseGet() works just fine, and the functional interface is never even invoked.
In this simple example, there is no significant cost to creating a default object, as the JVM knows how to deal with such. However, when a method such as callMe() has to make a web service call or even query a database, the cost becomes very obvious.
Final conclusion point:- Don’t use orElse() method, instead use orElseGet() method.
8. Exceptions with orElseThrow()
Given an employee table fetch the employee object with Id and return the name in upper case, but if name is not present throw an exception to stop program execution using Java 8.
The orElseThrow() method follows from orElse() and orElseGet() and adds a new approach for handling an absent value.
Instead of returning a default value when the wrapped value is not present, it throws an exception:-
String name = Optional.ofNullable(e.get().getName())
.orElseThrow(() -> new IllegalArgumentException("The id sent has no name"));
Stream QA
See in detail:- Java 8 Stream All Operations with Examples
1. Given an Employee list, sort employees based on their salaries in descending order using Java stream.
public class Employee {
private Integer id;
private String name;
private Double salary;
// getter and setter
public Employee(Integer id, String name, Double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
}
}
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee(10, "Ram", 5000.0),
new Employee(15, "Krishna", 4803.0),
new Employee(2, "Amlesh", 15000.0));
List<Employee> sortedEmployees = employees.stream()
.sorted((e1, e2) -> -e1.getSalary().compareTo(e2.getSalary()))
.collect(Collectors.toList());
System.out.println(sortedEmployees);
// if we want to sort the original list without creating separate another list
employees.sort((e1, e2) -> e2.getSalary().compareTo(e1.getSalary()));
}
}
Output:-
[Employee [id=2, name=Amlesh, salary=15000.0], Employee [id=10, name=Ram, salary=5000.0], Employee [id=15, name=Krishna, salary=4803.0]]
2. Given an Employee list, Fetch the Top 3 salaried Employees using Java stream.
We can use the limit() method after sorting the data.
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee(10, "Ram", 5000.0),
new Employee(15, "Krishna", 4803.0),
new Employee(2, "Amlesh", 15000.0),
new Employee(9, "Rakesh", 5100.0));
List<Employee> sortedEmployees = employees.stream()
.sorted((e1, e2) -> -e1.getSalary().compareTo(e2.getSalary()))
.limit(3)
.collect(Collectors.toList());
System.out.println(sortedEmployees);
}
}
Output:-
[Employee [id=2, name=Amlesh, salary=15000.0], Employee [id=9, name=Rakesh, salary=5100.0], Employee [id=10, name=Ram, salary=5000.0]]
3. Given an Employee list, Fetch all employees with a salary less than the 3rd highest using Java Stream.
Here we can use the skip() method, skip(N) will skip the first N entries from the stream.
public class Test {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee(10, "Ram", 5000.0),
new Employee(15, "Krishna", 4803.0),
new Employee(2, "Amlesh", 15000.0),
new Employee(9, "Rakesh", 5100.0));
List<Employee> sortedEmployees = employees.stream()
.sorted((e1, e2) -> -e1.getSalary().compareTo(e2.getSalary()))
.skip(3)
.collect(Collectors.toList());
System.out.println(sortedEmployees);
}
}
Output:-
[Employee [id=15, name=Krishna, salary=4803.0]]
Java 8 Short Circuit Operations
Java 8 short-circuiting operations are just like boolean short-circuit evaluations in Java.
In boolean short-circuiting logic, for example (firstBoolean && secondBoolean), if firstBoolean is false then the remaining part of the expression is ignored (the operation is short-circuited) because the remaining evaluation will be redundant. Similarly in firstBoolean || secondBoolean, if firstBoolean is true the remaining part is short-circuited.
Java 8 Stream short-circuit operations are not limited to boolean types. There are pre-defined short-circuiting operations.
Java 8 stream intermediate and terminal operations both can be short-circuiting.
- Intermediate – limit()
- Terminal – findFirst(), findAny(), anyMatch(), allMatch(), noneMatch()
Intermediate short-circuiting methods
This method (limit()) takes one (long N) as an argument and returns a stream of size no more than N.
Stream limit(long N) – Where N is the number of elements the stream should be limited to and this function returns a new stream as output.
Use the Java 8 Stream.limit() method to retrieve only the first n objects and set the maximum size. It ignores the remaining values after size n. Stream.limit(long maxSize) returns a Stream of objects. As soon as limit() reaches the maximum number of items, it doesn’t consume any more items and simply returns the resulting stream. Hence, we say that limit() is a short-circuiting operation.
limit() returns the first n elements in the encounter order and not just any n elements. This is an intermediate operation.
@GetMapping
public ResponseEntity<List<Employee>> findAll() {
return new ResponseEntity<List<Employee>>(
employeeRepository.findAll()
.stream()
.limit(3)
.collect(Collectors.toList()), HttpStatus.OK);
}
Rules of limit()
- It is an intermediate operation.
- It is a good choice when working with infinite streams or infinite values.
- The limit() method is invoked after calling the terminal operation such as count() or collect() methods.
- It returns a stream of elements with the size or max limit given.
- It works well in sequential streams. Not suggested to use in parallel streams such as larger or higher in size.
- maxSize can not be negative. If negative then it will throw IllegalArgumentException.
limit(-2) => IllegalArgumentException
findFirst()
- Optional<T> findFirst(): It returns the very first element (wrapped in Optional object) of this stream which matches the condition before traversing the other.
- The terminal operation terminated on finding the first element hence short-circuited.
@GetMapping
public ResponseEntity<?> findAll() {
Employee emp = employeeRepository.findAll().stream()
.filter(e -> e.getName().contains("Ra")).findFirst().get();
return new ResponseEntity<>(emp, HttpStatus.OK);
}
findAny()
- Optional<T> findAny():- It returns an Optional instance that wraps any and only one element of the stream.
- The behavior of this operation is explicitly nondeterministic; it is free to select any element in the stream. This is to allow for maximal performance in parallel operations; the cost is that multiple invocations on the same source may not return the same result. If a stable result is desired, use findFirst() instead.
Note:- For a sequential stream there won’t be any difference between ‘findFirst()’ and ‘findAny()’. But for a parallel stream findAny() will return ‘any’ element rather than waiting for the ‘first’ element. To get a parallel stream use parallel() after stream().
@GetMapping
public ResponseEntity<?> findAll() {
Employee emp = employeeRepository.findAll()
.stream().parallel()
.filter(e -> e.getName() != null && e.getName().contains("Ra"))
.findAny().get();
return new ResponseEntity<>(emp, HttpStatus.OK);
}
anyMatch()
- Boolean anyMatch(Predicate<? super T> predicate)
- It tests whether any elements of this stream match the provided predicate. This terminal method will return as soon as it finds the match and will not transverse all the remaining elements to apply the predicate.
@GetMapping
public ResponseEntity<?> findAll() {
Boolean itContains = employeeRepository.findAll().stream()
.anyMatch(e -> e.getName().contains("Ra"));
return new ResponseEntity<>(itContains, HttpStatus.OK);
}
allMatch()
- Boolean anyMatch(Predicate<? super T> predicate)
- Tests whether all elements match the provided predicate. It may return early with false result when any element doesn’t match first.
@GetMapping
public ResponseEntity<?> findAll() {
Boolean itContains = employeeRepository.findAll().stream()
.allMatch(e -> e.getName().contains("Ra"));
return new ResponseEntity<>(itContains, HttpStatus.OK);
}
noneMatch()
- boolean noneMatch(Predicate<? super T> predicate)
- Tests whether no elements of this stream match the provided predicate. It may return early with false result when any element matches the provided predicate first.
@GetMapping
public ResponseEntity<?> findAll() {
Boolean itContains = employeeRepository.findAll().stream()
.noneMatch(e -> e.getName() != null && e.getName().length() > 20);
return new ResponseEntity<>(itContains, HttpStatus.OK);
}
Map Vs FlatMap
public class Test {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
List<Integer> doubleList = list.stream()
.map(n -> n * 2).collect(Collectors.toList());
System.out.println(doubleList); // [2, 4, 6]
List<List<String>> studentSubjects = Arrays.asList(
Arrays.asList("Math", "Science"),
Arrays.asList("History", "Geography"),
Arrays.asList("English", "Art"));
// get name of all subjects
List<String> subjects = studentSubjects.stream()
.flatMap(subject -> subject.stream())
.collect(Collectors.toList());
System.out.println(subjects);
// [Math, Science, History, Geography, English, Art]
}
}
How Map works
- The Stream.map() function performs a map functional operation i.e. it takes a Stream and transforms it into another new Stream.
- It applies a function on each element of Stream and stores return value into new Stream.
- The map operation takes a Function, which is called for each value in the input stream and produces one result value, which is sent to the output stream.
Let’s assume that we have a list of employees and we need to get the list of cities where they have worked in past years. How to get the names of all those cities where all employees have worked?
public class Employee {
private Integer id;
private String name;
private List<String> cities;
// all argument constructor
// getter and setter methods
// toString() method
}
public class Test {
public static void main(String[] args) {
List<String> citiesWorked1 = List.of("Boston", "Seattle", "Phoenix");
Employee emp1 = new Employee(1, "Jerry", citiesWorked1);
Employee emp2 = new Employee(2, "William",
List.of("Dallas", "Seattle", "Austin"));
Employee emp3 = new Employee(3, "Rocco",
List.of("Seattle", "Austin", "Boston", "Dallas"));
List<Employee> employees = List.of(emp1, emp2, emp3);
// all IDs of employees
// name of unique cities where all employees have worked
}
}
To get all IDs of employees:-
List<Integer> employeeIds = employees.stream()
.map(e -> e.getId())
.collect(Collectors.toList());
System.out.println(employeeIds);
To get names of unique cities where all employees have worked:-
Set<String> uniqueCities = employees.stream()
.flatMap(emp -> emp.getCities().stream())
.collect(Collectors.toSet());
System.out.println(uniqueCities);
// [Seattle, Phoenix, Austin, Dallas, Boston]
How Flat Map Works?
- It is the combination of a map and a flat operation. This means we first apply the map function and then flatten the result.
- The key difference is the function used by map operation returns a Stream of values or a list of values rather than a single value, that’s why we need flattening. When we flat a Stream of Stream, it gets converted into Stream of values.
- To understand what flattening a stream consists in, consider a structure like [ [1,2,3],[4,5,6],[7,8,9] ] which has “two levels”. It’s a list containing three more List. Flattening this means transforming it into a “one level” structure e.g. [ 1,2,3,4,5,6,7,8,9 ] i.e. just one list.
- In short,
- Before flattening – Stream of List of Integer
- After flattening – Stream of Integer
- emp -> emp.getCities() is a mapper function that produces multiple values for each single input. i.e. there are multiple cities for each single employee.
- flatMap() is flattening those multiple values into a single stream and removing duplicates by set.
- This is the best explanation of flatMap. The values from the stream returned by the mapper are drained from the stream and are passed to the output stream. The “clumps” of values returned by each call to the mapper function are not distinguished at all in the output stream, thus the output is said to have been “flattened”.
map() | flatMap() |
The function we pass to the map() operation returns a single value. | The function we pass to flatMap() operation returns a Stream of value. The flatMap() is a combination of map and flat operation. |
map() is used for transformation only. | flatMap() is used for both transformation and flattening. |
Key Differences:
- Map: It’s like applying a rule to each item individually in a collection.
- FlatMap: It’s like applying a rule to each item, but also handling situations where there are nested collections and combining their results into a single flat collection.
- Map produces a one-to-one mapping, while FlatMap can produce a one-to-many mapping.
Java Arrays Coding Question using Stream
1. Given an array of integers, write a Java 8 program to find the second smallest element.
Input: int[ ] numbers = {5, 2, 8, 3, 1};
int secondSmallest = Arrays.stream(numbers)
.sorted()
.skip(1)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(
"Array don't have second smallest element"));
System.out.println(secondSmallest); // 2
2. The array contains duplicate elements, now find the second smallest number.
Input: int[ ] numbers = {5, 2, 8, 3, 1, 1, 2, 8};
Before sorting, find the distinct element using the distinct() method.
Arrays.stream(numbers)
.distinct()
.sorted()
.skip(1)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(
"Array don't have second smallest element"));
3. Given two arrays of integers, write a Java 8 program to find the common elements between them.
Input:
int[ ] array1 = {1, 2, 3, 4, 5};
int [ ] array2 = {4, 5, 6, 7, 8};
List<Integer> commonElements = Arrays.stream(array1)
.filter(n1 -> Arrays.stream(array2).anyMatch(n2 -> n2 == n1))
.boxed()
.collect(Collectors.toList());
System.out.println(commonElements); // [4, 5]
4. Write a Java 8 program to reverse an array of integers in place.
Input: int[] numbers = {1,2,3,4,5};
In-place means without creating any new array.
Swap 5 with 1, and 4 with 2.
import java.util.Arrays;
import java.util.stream.IntStream;
public class Test {
public static void main(String[] args) {
int[] numbers = { 1, 2, 3, 4, 5 };
// iterate till the middle length of the array
IntStream.range(0, numbers.length / 2).forEach(i -> {
int temp = numbers[i];
numbers[i] = numbers[numbers.length - i - 1];
numbers[numbers.length - i - 1] = temp;
});
System.out.println(Arrays.toString(numbers)); // [5, 4, 3, 2, 1]
}
}
5. Given an array of strings, write a Java 8 program to find the length of the longest string.
Input:
String[ ] strings = {“Apple”, “Banana”, “Avocado”, “Apricot”, “Grapes”};
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
String[] strings = { "Apple", "Banana", "Avocado", "Apricot", "Grapes" };
// map string to its length
int maxLength = Arrays.stream(strings)
.mapToInt(string -> string.length()).max().orElse(0);
System.out.println(maxLength); // 7
// using method reference
int maxLength1 = Arrays.stream(strings)
.mapToInt(String::length).max().orElse(0);
System.out.println(maxLength1); // 7
}
}
6. In the above question also display the longest string along with its length.
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
String[] strings = { "Apple", "Banana", "Avocado", "Apricot", "Grapes" };
String longestString = Arrays.stream(strings)
.max((s1, s2) -> Integer.compare(s1.length(), s2.length()))
.orElse(null);
System.out.println(longestString + ": " + longestString.length()); // Avocado: 7
}
}
7. Write a Java 8 program to remove duplicates from an array of strings while preserving the original order.
Input: String[] strings = {“Apple”, “Banana”, “Apple”, “Grapes”, “Banana”};
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test {
public static void main(String[] args) {
String[] strings = { "Apple", "Banana", "Apple", "Grapes", "Banana" };
List<String> uniqueStrings = Arrays.stream(strings)
.distinct()
.collect(Collectors.toList());
String[] uniqueStringArray = uniqueStrings.toArray(new String[0]);
System.out.println(Arrays.toString(uniqueStringArray)); // [Apple, Banana, Grapes]
}
}
8. Given an array of integers, write a Java 8 program to find the product of all the elements except the given element.
Input: int[] numbers = {2,4,6,8};, Element to exclude = 4
int except = 4;
int res = Arrays.stream(numbers)
.filter(n -> n != except)
.reduce(1, (n1, n2) -> n1 * n2);
System.out.println(res); // 96
Reduce and Peek Operations

What is an Intermediate operation?
The operations which return another stream, as a result, are called intermediate operations. Very important point:- they are lazy. Eg:- filter(), map(), distinct(), sorted(), limit(), skip()
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
// Find even numbers, square them, and filter numbers greater than 10
intList.stream().filter(n -> n % 2 == 0)
.map(n -> n * n).filter(n -> n > 10)
.forEach(System.out::println); // 16
What is Terminal Operation?
The operations that return non-stream values like primitive or object or collection or return nothing are called terminal operations.
You can chain multiple intermediate operations and none of them will do anything until you invoke a terminal operation. At that time, all of the intermediate operations that you invoked earlier will be invoked along with the terminal operation. Eg:- forEach(), toArray(), reduce(), collect(), min(), max(), count(), anyMatch(), allMatch(), noneMatch(), findFirst(), findAny()
@Data
@AllArgsConstructor
public class Employee {
private Integer id;
private String name;
public void printName() {
System.out.println("In emp class: " + name);
}
}
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
Employee e1 = new Employee(1, "A");
Employee e2 = new Employee(2, "B");
Employee e3 = new Employee(3, "C");
Employee e4 = new Employee(4, "D");
List<Employee> empList = Arrays.asList(e1, e2, e3, e4);
// display name of those employees whose id is even and call printName() method
// for them
empList.stream().filter(e -> e.getId() % 2 == 0).map(e -> {
e.printName();
return e.getName();
}).forEach(System.out::println);
}
}
Output:-
In emp class: B
B
In emp class: D
D

What will be output in the below case?
empList.stream().filter(e -> e.getId() % 2 == 0).map(e -> {
e.printName();
return e.getName();
});
It won’t give any output. Intermediate operations (like map()) are lazy loading. Without a terminal operator, it won’t call the printName() method.
Point to note:- The intermediate operation will never run if there is no terminal operation.
Terminal vs Intermediate Operations
Intermediate Operations | Terminal Operations |
They return stream. | They return non-stream values. |
They can be chained together to form a pipeline of operations. | They can’t be chained together. |
Pipeline of operations may contain any number of intermediate operations. | The pipeline of operations can have a maximum of one terminal operation, that too at the end. |
Intermediate operations are lazily loaded. | Terminal operations are eagerly loaded. |
They don’t produce end result. | They produce end result. |
What is peek()?
- The peek() method is an intermediate operation.
- It takes a consumer object as an input and returns a Stream consisting of the elements of the current stream.
- It additionally performs the provided action on each element as elements.
Use of Peek
- peek() exists mainly to support debugging, where we want to see the elements as they flow past a certain point in a pipeline.
- It is similar to Map, but it takes a consumer object, and performs some action on the object, and returns nothing. Whereas map takes a function argument hence apply operation on each element and return the stream having modified elements.
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
intList.stream().filter(n -> n % 2 == 0).peek(System.out::println)
.map(n -> n * n).peek(System.out::println)
.filter(n -> n > 10).forEach(System.out::println);
What is reduce()?
- The
Stream.reduce()
combines elements of a stream and produces a single value. - reduce operation applies a binary operator to each element in the stream where the first argument to the operator is the return value of the previous application and the second argument is the current stream element.
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
// sum of elements
int sum = intList.stream().reduce((a, b) -> a + b).get();
System.out.println(sum); // 15
Coding Questions on Java 8
1. Find the duplicate elements in a given integers list in Java using Stream functions.
Input: [10,28,87,10,20,76,28,80]
Output: 10,28
List<Integer> numbers = List.of(10,28,87,10,20,76,28,80);
Set<Integer> duplicateNumbers = numbers.stream()
.filter(num -> Collections.frequency(numbers, num)>1)
.collect(Collectors.toSet());
System.out.println(duplicateNumbers); // [10, 28]
List<Integer> numbers = List.of(10, 28, 87, 10, 20, 76, 28, 80);
Set<Integer> uniqueNumbers = new HashSet<>();
Set<Integer> duplicateNumbers = numbers.stream()
.filter(num -> !uniqueNumbers.add(num))
.collect(Collectors.toSet());
System.out.println(uniqueNumbers); // [80, 20, 87, 10, 28, 76]
System.out.println(duplicateNumbers); // [10, 28]
2. Write a program to multiply 2 no’s using the Functional interface.
@FunctionalInterface
public interface FunctionalInterfaceDemo {
int multiply(int a, int b);
}
public class Test {
public static void main(String[] args) {
FunctionalInterfaceDemo total = (a, b) -> a * b;
System.out.println(total.multiply(10, 2));
}
}
3. What’s the difference between limit() and skip()? explain using examples.
- Limit: The limit(n) method is an intermediate operation that returns a stream not longer than the requested size. As before, the n parameter can’t be negative.
- Skip: The skip(n) method is another intermediate operation that discards the first n elements of a stream. The n parameter can’t be negative, and if it’s higher than the size of the stream, skip() returns an empty stream.
4. Count the no of occurrences of words in a given string using Java 8.
Input: “welcome to know program and know program welcome you”;
Output: {and=1, know=2, program=2, to=1, welcome=2, you=1}
String word = "welcome to know program and know program welcome you";
List<String> list = Arrays.asList(word.split(" "));
Map<String, Long> wordCount = list.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
System.out.println(wordCount);
// {and=1, know=2, program=2, to=1, welcome=2, you=1}
Refactoring Code to Java 8
1. Refactor the given isPrime(int num) to Java 8.
public static boolean isPrime(int number) {
boolean isDivisible = false;
for (int i = 2; i < number; i++) {
if (number % i == 0) {
isDivisible = true;
break;
}
}
return number > 1 && !isDivisible;
}
In Java8:-
public static boolean isPrime(int number) {
return number > 1 &&
IntStream.range(2, number)
.noneMatch(n -> number % n == 0);
}
By the way, to find N is a prime number we don’t have to check from 1 to N. The reason for this is based on the mathematical fact that a larger factor of the number must be a multiple of a smaller factor that has already been checked. This reduces the amount of computation needed to determine if a number is prime.
return number > 1 && IntStream.rangeClosed(2, (int) Math.sqrt(number))
.noneMatch(n -> number % n == 0);
2. Convert the given code in Java8
public static void main(String[] args) {
// square root of first 10 prime numbers
List<Double> sqrtOf10Prime = new ArrayList<>(10);
int index = 1;
while (sqrtOf10Prime.size() < 10) {
if (isPrime(index)) {
sqrtOf10Prime.add(Math.sqrt(index));
}
index++;
}
System.out.println(sqrtOf10Prime);
}
In Java8:-
List<Double> sqrtOf10Prime = Stream.iterate(1, i -> i + 1)
.filter(Test::isPrime).peek(System.out::println)
.map(Math::sqrt)
.limit(10)
.collect(Collectors.toList());
System.out.println(sqrtOf10Prime);
If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!