Java 8 Interview Questions and Answer
π Clap for the story to help this article be featured
Stream API Interview Questions GITHUB Link https://github.com/vinosubi/Streams-Example-part1
Source code download : https://github.com/vinosubi/Streams-Example-part1/archive/refs/heads/master.zip
Topics Covered
- Lambda Expressions
- Functional Interfaces
- built-in functional interfaces [ Predicate, BiPredicate, Function, BiFunction, Consumer, BiConsumer, Supplier, UnaryOperator, BinaryOperator ] With examples
- Method references [ Reference to a Static Method, Reference to an Instance Method of a Particular Object, Reference to an Instance Method of an Arbitrary Object of a Particular Type, Reference to a Constructor ]
- Default methods
- Static methods
- Optional Class (
Optional.empty(),Optional.of(), Optional.ofNullable(), isPresent(), ifPresent(), get(), orElse(), orElseGet(), orElseThrow() ) - Date/Time API [ LocalDate class, LocalTime class, LocalDateTime class, ZonedDateTime class, Period class, Duration Class, Temporal adjusters ].
forEach()method [ Iterating a Map, Iterating a List, Iterating a Set, Iterating a Stream, Using forEach with Consumer, Exception Handling with forEach, forEach vs forEachOrdered ]- Stringjoiner class
- Spliterator interface
- Nashorn JavaScript engine
- Stream API
What are Lambda Expressions? A lambda expression is an anonymous function; that is, a function without a name. It allows for functional programming in Java, enabling developers to pass functions as arguments to methods or be stored in a variable.
(parameters) -> expression or (parameters) -> { statements; }
Letβs break it down:
- Parameters: These are the inputs to the lambda expression. If there are no parameters, you can simply use empty parentheses
(). - Arrow token (
->): This separates the parameters from the body of the lambda. - Body: This is the code that gets executed when the lambda is invoked. It can be a single expression or a block of statements.
Without lambda expressions:
List<String> names = Arrays.asList("Peter", "Paul", "Mary");
for (String name : names) {
System.out.println(name);
}With lambda expressions:
List<String> names = Arrays.asList("Peter", "Paul", "Mary");
names.forEach(name -> System.out.println(name));Using Lambda Expressions with Functional Interfaces
@FunctionalInterface
interface Greeting {
void sayHello(String name);
}
public class LambdaExample {
public static void main(String[] args) {
Greeting greeting = (name) -> System.out.println("Hello, " + name);
greeting.sayHello("World");
}
}In this example, Greeting is a functional interface with a single method sayHello. We use a lambda expression to provide an implementation for this method.
Benefits of Lambda Expressions
Lambda expressions bring several benefits to Java programming:
- Conciseness: They reduce the boilerplate code, making your programs shorter and easier to read.
- Readability: Code written using lambda expressions is more straightforward and looks closer to natural language.
- Functional Programming: They enable a more functional programming style, which is beneficial for processing collections and streams of data.
What is Functional Interfaces
- A Functional Interface is an interface that has only one abstract method.
- Instances of functional interfaces can be created with the help of lambda expressions, method references and constructor references.
- @FunctionalInterface Annotation: While itβs not mandatory, itβs a good practice to annotate functional interfaces with
@FunctionalInterface. This annotation helps the compiler catch any accidental addition of multiple abstract methods to the interface. - A functional interface can have any number of default methods.
- A functional interface can also contain any number of static methods.
Letβs Understand It With an Example:
@FunctionalInterface
public interface ExampleInterface {
int randomCalculate(int a, int b);
default void print(int result) {
System.out.println(result);
}
}The above example interface contains one abstract method and one default method.
Now letβs understand how lambda expression can create instances of functional interface and how to call the implemented method.
Example : Functional interface using a lambda expression.
import java.util.Random;
public class TestFunctionalInterface {
public static void main(String[] args) {
// functional interface using a lambda expression.
ExampleInterface exampleInterface = (a, b) -> {
int randomCal = a * b / 20;
Random random = new Random();
randomCal = randomCal + random.nextInt(1000);
return randomCal;
};
exampleInterface.print(exampleInterface.randomCalculate(10, 20));
}
}TestFuntionalInterface this class is a driver class that has a main method and inside this method, a lambda expression exampleInterface has implemented the abstract method from ExampleInterface . Using this lambda the function definition can be called(e.g. exampleInterface.randomCalculate(10, 20)). Similarly, the default method of the functional interface can also be called using the same lambda exampleInterface.(e.g. exampleInterface.print()).
Example : Functional interface using a method reference.
@FunctionalInterface
public interface ExampleInterface {
int randomCalculate(int a, int b);
default void print(int result) {
System.out.println(result);
}
}
public class TestFunctionalInterface {
// Static method matching the signature of randomCalculate
public static int calculate(int a, int b) {
int randomCal = a * b / 20;
Random random = new Random();
randomCal = randomCal + random.nextInt(1000);
return randomCal;
}
public static void main(String[] args) {
// Functional interface using a method reference.
ExampleInterface exampleInterface = TestFunctionalInterface::calculate;
exampleInterface.print(exampleInterface.randomCalculate(10, 20));
}
}Example : Functional interface using a constructor reference.
import java.util.function.BiFunction;
// Class that performs the calculation
class Calculation {
private int a;
private int b;
public Calculation(int a, int b) {
this.a = a;
this.b = b;
}
public int performCalculation() {
int randomCal = a * b / 20;
Random random = new Random();
randomCal = randomCal + random.nextInt(1000);
return randomCal;
}
}
// Functional interface for factory method
@FunctionalInterface
interface CalculationFactory {
Calculation create(int a, int b);
}
public class TestConstructorReference {
public static void main(String[] args) {
// Constructor reference
CalculationFactory factory = Calculation::new;
// Use the factory to create an instance and perform the calculation
Calculation calculation = factory.create(10, 20);
System.out.println(calculation.performCalculation());
}
}Common Functional Interfaces:
Java 8 introduced several built-in functional interfaces in the java.util.function package, including:
Predicate<T>: Represents a boolean-valued function of one argument.BiPredicate<T, U>: Takes two arguments and returns a boolean.Function<T, R>: Represents a function that accepts one argument and produces a result.BiFunction<T, U, R>: Takes two arguments and returns a result.Consumer<T>: Represents an operation that accepts a single input argument and returns no result.BiConsumer<T, U>: Takes two arguments and performs an operation, returning nothing (void).Supplier<T>: Represents a supplier of results.UnaryOperator<T>: Represents an operation on a single operand that produces a result of the same type as its operand.BinaryOperator<T>: Represents an operation upon two operands of the same type, producing a result of the same type as the operands.
Example for all function interface
class Employee {
private String name;
private int id;
public Employee(String name, int id) {
this.name = name;
this.id = id;
}
// Getters and toString method
public String getName() {
return name;
}
public int getId() {
return id;
}
@Override
public String toString() {
return "Employee{name='" + name + "', id=" + id + "}";
}
}1. Supplier<T>:
- Abstract method:
T get() - This represents a supplier of results, providing a value without taking any input.
Example : The Supplier functional interface is used to represent a function that does not take any arguments and produces a result. It is often used for lazy evaluation or to supply instances of a certain object.
public class Main {
public static void main(String[] args) {
// Supplier that supplies an Employee object
Supplier<Employee> employeeSupplier = () -> new Employee("John Doe", 123);
// Get the Employee object from the Supplier
Employee employee = employeeSupplier.get();
// Output the Employee object
System.out.println(employee);
}
}
// output
Employee{name='John Doe', id=123}Predicate<T>:
- Abstract method:
boolean test(T t) - It represents a predicate/condition, a boolean-valued function that checks a condition on an input.
- It is used for filtering or testing elements based on a condition.
Example : The Predicate interface represents a function that takes a single input and returns a boolean value. It is commonly used for filtering or matching conditions.
Letβs say you have an Employee object, and you want to use a Predicate to check certain conditions, such as whether the employee's ID is greater than a certain value or if the employee's name starts with a specific letter.
public class Main {
public static void main(String[] args) {
Employee employee = new Employee("Alice Smith", 102);
// Predicate to check if the employee's ID is greater than 100
Predicate<Employee> isIdGreaterThan100 = emp -> emp.getId() > 100;
// Predicate to check if the employee's name starts with "A"
Predicate<Employee> isNameStartsWithA = emp -> emp.getName().startsWith("A");
// Using the predicates
boolean result1 = isIdGreaterThan100.test(employee);
boolean result2 = isNameStartsWithA.test(employee);
// Output the results
System.out.println("Is employee ID greater than 100? " + result1);
System.out.println("Does employee name start with 'A'? " + result2);
}
}
// output
Is employee ID greater than 100? true
Does employee name start with 'A'? trueFunction<T, R>
- Abstract method:
R apply(T t) - This represents a function that accepts one argument and produces a result.
- It is commonly used for mapping or transforming input to output.
Example : The Function<T, R> interface represents a function that takes an input of type T and returns a result of type R. This interface is useful when you want to transform an input object into another object or value.
public class Main {
public static void main(String[] args) {
Employee employee = new Employee("Bob Johnson", 201);
// Function that takes an Employee and returns a formatted String
Function<Employee, String> employeeToStringFunction = emp ->
"Employee Details: Name - " + emp.getName() + ", ID - " + emp.getId();
// Apply the function to the Employee object
String employeeDetails = employeeToStringFunction.apply(employee);
// Output the result
System.out.println(employeeDetails);
}
}
//output
Employee Details: Name - Bob Johnson, ID - 201Consumer<T>:
- Abstract method:
void accept(T t) - This represents an operation that accepts a single input and returns no value or result.
- It is used for performing actions on input values, such as printing, saving, or processing.
Example : The Consumer<T> interface represents an operation that takes a single input argument of type T and performs some action on it but does not return any result. It's often used for operations like printing, logging, or modifying an object.
Hereβs an example using the Consumer<T> interface with an Employee object. Let's create a Consumer that prints out the details of an Employee and another Consumer that gives a raise to the employee by modifying their salary.
public class Main {
public static void main(String[] args) {
Employee employee = new Employee("Sarah Connor", 303, 50000);
// Consumer to print Employee details
Consumer<Employee> printEmployeeDetails = emp -> System.out.println(emp);
// Consumer to give a raise by increasing salary by 10%
Consumer<Employee> giveRaise = emp -> emp.setSalary(emp.getSalary() * 1.10);
// Apply the Consumers
printEmployeeDetails.accept(employee); // Print details before raise
giveRaise.accept(employee); // Give a raise
printEmployeeDetails.accept(employee); // Print details after raise
}
}
// output
Employee{name='Sarah Connor', id=303, salary=50000.0}
Employee{name='Sarah Connor', id=303, salary=55000.0}UnaryOperator<T>:
- Abstract method:
T apply(T t) - This is a special case of the
Functioninterface where the input and output types are the same. - It is used for operations that transform an input into an output of the same type.
Example : The UnaryOperator<T> interface is a specialized version of the Function<T, R> interface where both the input and output are of the same type. It's often used for operations that map an object to another object of the same type, such as updating or transforming a value.
Hereβs an example using the UnaryOperator<T> interface with an Employee object. We'll create a UnaryOperator that increases the employee's salary by a certain percentage.
import java.util.function.UnaryOperator;
class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
// Getters and setters
public String getName() {
return name;
}
public int getId() {
return id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{name='" + name + "', id=" + id + ", salary=" + salary + "}";
}
}
public class Main {
public static void main(String[] args) {
Employee employee = new Employee("John Doe", 101, 40000);
// UnaryOperator to give a 10% raise
UnaryOperator<Employee> giveRaise = emp -> {
emp.setSalary(emp.getSalary() * 1.10);
return emp;
};
// Apply the UnaryOperator
Employee updatedEmployee = giveRaise.apply(employee);
// Output the updated Employee details
System.out.println(updatedEmployee);
}
}
// output
Employee{name='John Doe', id=101, salary=44000.0}BinaryOperator:
- Abstract method:
T apply(T t, T u) - It is a special case of the
BiFunctioninterface where the two input arguments and the output are of the same type. - It is used for binary operations that combine two inputs into a single result.
Example : The BinaryOperator<T> interface is a specialized version of the BiFunction<T, T, T> interface, where both the operands and the result are of the same type. It's commonly used for operations that combine two values of the same type into a single result of that type.
Letβs create an example using the BinaryOperator<T> interface with an Employee object. Suppose you want to combine two Employee objects and decide which one has the higher salary. The BinaryOperator will return the Employee with the higher salary.
import java.util.function.BinaryOperator;
class Employee {
private String name;
private int id;
private double salary;
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
// Getters
public String getName() {
return name;
}
public int getId() {
return id;
}
public double getSalary() {
return salary;
}
@Override
public String toString() {
return "Employee{name='" + name + "', id=" + id + ", salary=" + salary + "}";
}
}
public class Main {
public static void main(String[] args) {
Employee employee1 = new Employee("Alice Smith", 201, 60000);
Employee employee2 = new Employee("Bob Johnson", 202, 75000);
// BinaryOperator to determine which Employee has a higher salary
BinaryOperator<Employee> higherSalary = (emp1, emp2) -> emp1.getSalary() > emp2.getSalary() ? emp1 : emp2;
// Apply the BinaryOperator
Employee result = higherSalary.apply(employee1, employee2);
// Output the Employee with the higher salary
System.out.println("Employee with the higher salary: " + result);
}
}
// output
Employee with the higher salary: Employee{name='Bob Johnson', id=202, salary=75000.0}
What is Method References?
Method references are shorthand notations for lambda expressions that directly call a method. They provide a way to refer to methods without invoking them. Method references simplify the code and make it more concise, especially when the lambda expression contains only a method call.
In Java, there are four types of method references:
- Reference to a Static Method: A method reference to a static method uses the syntax `ClassName::staticMethodName`. It refers to a static method of the specified class.
- Reference to an Instance Method of a Particular Object: A method reference to an instance method of a particular object uses the syntax `objectReference::instanceMethodName`. It refers to an instance method of the specified object.
- Reference to an Instance Method of an Arbitrary Object of a Particular Type: A method reference to an instance method of an arbitrary object of a particular type uses the syntax `ClassName::instanceMethodName`. It refers to an instance method of the first argument of the specified type.
- Reference to a Constructor: A method reference to a constructor uses the syntax `ClassName::new`. It refers to the constructor of the specified class.
Example: Reference to a Static Method
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);
}
}Example 2 :
public class MathUtils {
public static int multiply(int x, int y) {
return x * y;
}
}
public class Main {
public static void main(String[] args) {
// Method reference to MathUtils.multiply
MyFunction multiply = MathUtils::multiply;
// Use the method reference
int result = multiply.apply(4, 5);
System.out.println(result); // Output: 20
}
}Example : Reference to an Instance Method
public class MyClass {
public void instanceMethod(String message) {
System.out.println(message);
}
}import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
MyClass myClassInstance = new MyClass();
// Using a lambda expression
Consumer<String> lambdaConsumer = (message) -> myClassInstance.instanceMethod(message);
lambdaConsumer.accept("Hello from lambda!");
// Using method reference
Consumer<String> methodRefConsumer = myClassInstance::instanceMethod;
methodRefConsumer.accept("Hello from method reference!");
}
}
// output
Hello from lambda!
Hello from method reference!Example: Reference to a Constructor
import java.util.function.Supplier;
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class MethodReferenceExample {
public static void main(String[] args) {
// Using lambda expression
Supplier<Person> personSupplier1 = () -> new Person("Alice");
Person alice = personSupplier1.get();
// Using method reference
Supplier<Person> personSupplier2 = Person::new;
Person bob = personSupplier2.get();
System.out.println(alice.getName()); // Output: Alice
System.out.println(bob.getName()); // Output: null
}
}What is Default method?
Why Default method? Adding one or more new methods to interface will result in all the implementing classes will have to provide implementation for new method which creates a major issue while maintaining backward compatibility. This problem usually occurs in java version prior to java 8.
- Default methods were introduced in Java 8 as a new feature for interfaces.
- Allow the addition of new methods to interfaces without breaking existing implementations
- Implementers can choose whether to use the default method implementation present in interface or provide their own implementation(@Override).
πΎπππ ππππ !? πΎπ πππ πππ ππππππ ππππππ π , ππ π πππ πππ ππππ πππππ .
No , static methods cannot be overrided , otherwise we can override default methods .
Example 1 :
interface Printer {
default void print() {
System.out.println("Printing in black and white");
}
}
class BasicPrinter implements Printer {
// Uses the default implementation
}
class ColorPrinter implements Printer {
// Overrides the default implementation
@Override
public void print() {
System.out.println("Printing in color");
}
}
public class Main {
public static void main(String[] args) {
Printer basicPrinter = new BasicPrinter();
Printer colorPrinter = new ColorPrinter();
basicPrinter.print(); // Output: Printing in black and white
colorPrinter.print(); // Output: Printing in color
}
}In this example:
- The
Printerinterface provides a default implementation for theprint()method. BasicPrinterimplementsPrinterbut doesn't overrideprint(). It uses the default implementation as-is.ColorPrinteralso implementsPrinterbut overridesprint()with its own implementation.
Example 2 :
interface Vehicle {
void start();
default void stop() {
System.out.println("Vehicle stopped");
}
}
class Car implements Vehicle {
public void start() {
System.out.println("Car started");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car();
car.start(); // Output: Car started
car.stop(); // Output: Vehicle stopped (default implementation from Vehicle interface)
}
}What is Static method? Static methods in interfaces can be called directly on the interface without requiring an instance of a class implementing the interface. These methods are commonly used for utility functions related to the interface.
Example :
interface MathOperations {
static int add(int a, int b) {
return a + b;
}
static int multiply(int a, int b) {
return a * b;
}
}
public class Main {
public static void main(String[] args) {
int sum = MathOperations.add(5, 3); // Using static method directly from interface
int product = MathOperations.multiply(5, 3); // Using static method directly from interface
System.out.println("Sum: " + sum); // Output: Sum: 8
System.out.println("Product: " + product); // Output: Product: 15
}
}Static methods never Override
In Java interfaces, static methods cannot be overridden by implementing classes. When a class implements an interface that includes a static method, it canβt provide its implementation of that static method or override it.
What is Optional Class?
Java 8 introduced the Optional class as a way to handle null values avoid NullPointerException.
Methods of Optional:
Hereβs a set of examples using an Employee object to demonstrate the usage of the Optional class methods in Java:
Example : Employee Class
public class Employee {
private String name;
private Integer id;
public Employee(String name, Integer id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public Integer getId() {
return id;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}1. Optional.empty()
Represents an empty Optional instance, meaning no value is present.
Optional<Employee> emptyOptional = Optional.empty();
System.out.println(emptyOptional); // Output: Optional.empty2. Optional.of()
Creates an Optional with a non-null value. Throws NullPointerException if the argument is null.
Employee emp = new Employee("John", 1001);
Optional<Employee> empOptional = Optional.of(emp);
System.out.println(empOptional); // Output: Optional[Employee{name='John', id=1001}]3. Optional.ofNullable()
Creates an Optional that may or may not hold a non-null value. Doesn't throw an exception if the argument is null.
Employee emp = null;
Optional<Employee> empOptional = Optional.ofNullable(emp);
System.out.println(empOptional); // Output: Optional.empty
emp = new Employee("John", 1001);
empOptional = Optional.ofNullable(emp);
System.out.println(empOptional); // Output: Optional[Employee{name='John', id=1001}]4. isPresent()
Returns true if there is a value present, otherwise false.
Optional<Employee> empOptional = Optional.ofNullable(new Employee("John", 1001));
System.out.println(empOptional.isPresent()); // Output: true
empOptional = Optional.empty();
System.out.println(empOptional.isPresent()); // Output: false5. ifPresent()
Performs the given action if a value is present.
Optional<Employee> empOptional = Optional.ofNullable(new Employee("John", 1001));
empOptional.ifPresent(e -> System.out.println("Employee is present: " + e));
// Output: Employee is present: Employee{name='John', id=1001}
empOptional = Optional.empty();
empOptional.ifPresent(e -> System.out.println("Employee is present: " + e));
// Output: (Nothing is printed because the Optional is empty)6. get()
Returns the value if present; otherwise, throws NoSuchElementException.
Optional<Employee> empOptional = Optional.ofNullable(new Employee("John", 1001));
Employee emp = empOptional.get();
System.out.println(emp); // Output: Employee{name='John', id=1001}
empOptional = Optional.empty();
try {
empOptional.get(); // Throws NoSuchElementException
} catch (NoSuchElementException e) {
System.out.println("No value present!"); // Output: No value present!
}7. orElse()
Returns the value if present; otherwise, returns the specified default value.
Optional<Employee> empOptional = Optional.ofNullable(null);
Employee emp = empOptional.orElse(new Employee("Default", 9999));
System.out.println(emp);
// Output: Employee{name='Default', id=9999}8. orElseGet()
Returns the value if present; otherwise, returns the result produced by the given supplier.
Optional<Employee> empOptional = Optional.ofNullable(null);
Employee emp = empOptional.orElseGet(() -> new Employee("Default", 9999));
System.out.println(emp);
// Output: Employee{name='Default', id=9999}9. orElseThrow()
Returns the value if present; otherwise, throws an exception created by the provided supplier.
Optional<Employee> empOptional = Optional.ofNullable(null);
try {
Employee emp = empOptional.orElseThrow(() -> new IllegalArgumentException("No Employee found"));
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // Output: No Employee found
}Date/Time API in Java 8
Java 8 introduced a powerful Date/Time API within java.time package to handle date and time-related operations.
LocalDate class
The LocalDate class represents a date without a timezone in the format yyyy-mm-dd.
An instance of the current date can be created using the LocalDate.now() method.
LocalDate today = LocalDate.now(); // 2023-08-28By using of or parse method we can create an instance of a LocalDate for a specific date.
LocalDate localDate = LocalDate.of(2023, 08, 31); // 2023-08-31
LocalDate localDate = LocalDate.parse("2023-09-20"); // 2023-09-20We can add/subtract days, months, and years from an instance of LocalDate.
LocalDate yesterday = LocalDate.now().plusDays(1); // 2023-08-27
LocalDate tomorrow = LocalDate.now().minusDays(1); // 2023-08-29
LocalDate lastMonth = LocalDate.now().minusMonths(1); //2023-07-28
LocalDate nextMonth = LocalDate.now().plusMonths(1); //2023-09-28
LocalDate lastYear = LocalDate.now().minusYears(1); //2022-08-28
LocalDate nextYear = LocalDate.now().plusYears(1); //2024-08-28We can derive the day of the week, day, month, and year from an instance of LocalDate.
DayOfWeek day = LocalDate.now().getDayOfWeek(); //MONDAY
int day = LocalDate.parse("2023-01-12").getDayOfMonth(); // 12
Month month = LocalDate.parse("2023-01-12").getMonth(); // JANUARY
int year = LocalDate.parse("2023-01-12").getYear(); // 2023We can check if a particular year is a leap year by doing the following.
boolean isLeap = LocalDate.now().isLeapYear(); //false
boolean isLeap1 = LocalDate.parse("2024-01-12").isLeapYear(); //trueComparison of two dates can be done as follows.
LocalDate date1 = LocalDate.parse("2022-08-12");
LocalDate date2 = LocalDate.parse("2023-01-09");
// check if date1 is before date2
boolean isBefore = date1.isBefore(date2); //true
// check if date1 is after date2
boolean isAfter = date1.isAfter(date2); //falseLocalTime class
The LocalTime class represents time without a date.
An instance of the current time can be created using the LocalTime.now() method.
LocalTime now = LocalTime.now(); // 14:07:12.503202341 By using of or parse method we can create an instance of a LocalTime for a specific time.
LocalTime eightFifteen = LocalTime.parse("08:15"); // 08:15
LocalTime eightFifty = LocalTime.of(8, 50); // 08:50Similar to LocalDate, we can add/subtract minutes, seconds, and hours from an instance of a LocalTime.
LocalTime timeInOneHour = LocalTime.now().plus(1, ChronoUnit.HOURS); //15:16:05.836826479
LocalTime timeInOneMinute = LocalTime.now().plusMinutes(1); //14:17:05.836826479We can obtain seconds, minutes, and hours from a specific instance of a LocalTime.
//Local Time : 14:21:22.383214776
int hour = LocalTime.now().getHour(); //14
int minutes = LocalTime.now().getMinute(); //21
int seconds = LocalTime.now().getSecond();` //22We can compare two times as follows.
LocalTime time1 = LocalTime.parse("02:23");
LocalTime time2 = LocalTime.parse("23:13");
// check if time1 is before time2
boolean isBefore = time1.isBefore(time2); // true
// check if time1 is after time2
boolean isAfter = time1.isAfter(time2); // falseLocalDateTime class
LocalDateTime class is used to represent date and time without a time zone.
Similar operations as LocalDate and LocalTime can be performed for this class too.
//Get the current date and time
LocalDateTime now = LocalDateTime.now(); //2023-08-28T14:31:21.433439004
//Create an instance using of and parse
LocalDateTime parseDateTime = LocalDateTime.parse("2023-09-29T15:20"); //2023-09-29T15:20
LocalDateTime givenDateTime = LocalDateTime.of(2023, Month.SEPTEMBER, 4, 02, 02); //2023-09-04T02:0We can add specific time and date units like hours, minutes, seconds, months, years, and days using plus and minus methods.
LocalDateTime yesterday = LocalDateTime.now().plusDays(1);
LocalDateTime timebeforeOneHour = LocalDateTime.now().minusHours(1);We can get the specific day, month, year, second, minute, hour from an instance of a LocalDateTime class.
//LocalDateTime : 2023-08-28T14:40:11.302826996
int hour = LocalDateTime.now().getHour(); // 14
int minutes = LocalDateTime.now().getMinute(); // 40
int seconds = LocalDateTime.now().getSecond(); // 11
int day = LocalDateTime.now().getDayOfMonth(); // 28
Month month = LocalDateTime.now().getMonth(); // AUGUST
int year = LocalDateTime.now().getYear(); // 2023We can also compare two LocalDateTime instances using isAfter and isBefore methods.
ZonedDateTime class
ZonedDateTime is provided by Java8 to handle time-zone-specific dates and times.
We can get the available ZoneIds as follows.
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();The zone Ids would be as Asia/Aden, America/Cuiaba, etc.
We can convert the LocalDateTime to a specific zone.
LocalDateTime now = LocalDateTime.now(); //2023-08-28T15:00:30.487070279
ZonedDateTime zonedDT = ZonedDateTime.of(now, ZoneId.of("Africa/Nairobi")); //2023-08-28T15:00:30.487070279+03:00[Africa/Nairobi]The ZonedDateTime also provides the parse method to get time-zone-specific date-time.
ZonedDateTime zonedDT = ZonedDateTime.parse("2019-12-20T10:15:30+05:00[Asia/Karachi]");Similar to the previously mentioned classes, we can execute plus/minus operations, get different time and date units, and compare two instances using the ZonedDateTime class as well.
Period Class
The Period class is used to modify the values of a given date and to get the difference between to given class.
The following code illustrates how you can manipulate an instance of LocalDate:
// Current Local Date : 2023-08-28
// Add a period of 6 days
LocalDate futureDate = LocalDate.now().plus(Period.ofDays(6)); // 2023-09-03
// Subtract a period of 2 weeeks
LocalDate pastDate = LocalDate.now().minus(Period.ofWeeks(2)); //2023-08-14
// Add period of 2 months and 15 days
LocalDate specificDate = LocalDate.now().plus(Period.ofMonths(2).plusDays(15)); //2023-11-12We can get the difference between two dates in different date units using Period class.
LocalDate date1 = LocalDate.of(2022, Month.AUGUST, 12); // 2022-08-12
LocalDate date2 = LocalDate.of(2023, Month.DECEMBER, 02); // 2023-12-02
// Difference between two dates
Period period = Period.between(date1, date2); //P1Y3M20D
int days = period.getDays(); // 20
int months = period.getMonths(); // 3
int years = period.getYears(); // 1
boolean isNegative = period.isNegative(); // false
boolean isZero = period.isZero(); // falseP1Y3M20D represents a Period of 1 Year, 3 Months, and 20 Days.
We can use the ChronoUnit class to obtain the difference between two dates in a specific unit such as day, month, or year.
long days = ChronoUnit.DAYS.between(date1, date2); // 477
long months = ChronoUnit.MONTHS.between(date1, date2); // 15
long years = ChronoUnit.YEARS.between(date1, date2); // 1Duration Class
Similar to Period Class, Duration Class is used to modify the values of a given time or to find the difference between two times.
// Current Local Time: 15:39:40.053910730
// Add a duration of 8 hours
LocalTime futureTime = LocalTime.now().plus(Duration.ofHours(8)); //23:39:40.063033292
// Subtract 1 day
LocalTime pastTime = LocalTime.now().minus(Duration.ofDays(1)); //15:39:40.063269984
// Add 4 Days and 3 hours
LocalTime specificTime = LocalTime.now().plus(Duration.ofDays(4).plusHours(3)); //15:39:40.063269984We can get the duration between two given times as follows.
LocalTime time1 = LocalTime.of(10, 30); // 10:30
LocalTime time2 = LocalTime.of(12, 28); // 12:28
// get the difference between times
Duration duration = Duration.between(time1, time2); //PT1H58M
long seconds = duration.getSeconds(); // 7080
long minutes = duration.toMinutes(); // 118
long hours = duration.toHours(); // 1We can use the ChronoUnit class to obtain the difference between two times in a specific unit such as seconds, minutes, or hours.
long seconds = ChronoUnit.SECONDS.between(time1, time2); // 7080
long minutes = ChronoUnit.MINUTES.between(time1, time2); // 118
long hours = ChronoUnit.HOURS.between(time1, time2); // 1Temporal Adjusters
Temporal adjusters are useful when carrying out diverse calculations involving dates and times. We can use them to manipulate temporal objects effectively and address queries such as discovering the first day of the Month or finding the next Sunday of the month etc.
LocalDate givenDate= LocalDate.parse("2023-08-28")
givenDate.with(TemporalAdjusters.lastDayOfMonth()) //2023-08-31
givenDate.with(TemporalAdjusters.firstDayOfMonth()) //2023-08-01
givenDate.with(TemporalAdjusters.firstDayOfNextMonth()) //2023-09-01Some of the other Temporal Adjusters are :
- next(DayOfWeek.TUESDAY)
- dayOfWeekInMonth
- firstDayOfNextYear
- firstDayOfYear
- lastDayOfNextMonth
- lastDayOfNextYear
- lastDayOfYear
forEach() Method in Java 8
The forEach() method is part of the Iterable interface and is commonly used with collections (such as List, Set, etc.) and streams in Java. It takes a Consumer as an argument and applies that consumer's action to each element in the collection.
Example : forEach
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Using forEach to double each number and print the result
numbers.forEach(num -> System.out.println(num * 2));Example : normal for loop:
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Normal for loop to double each number and print the result
for (int i = 0; i < numbers.size(); i++) {
int num = numbers.get(i);
System.out.println(num * 2);
}
}
}- Iterating a Map
- Iterating a List
- Iterating a Set
- Iterating a Stream
- Using forEach with Consumer
- Exception Handling with forEach
- forEach vs forEachOrdered
Iterating a Map
Map<String, Integer> map = new HashMap<>();
map.put("apple", 1);
map.put("banana", 2);
map.put("orange", 3);
// Java 8 forEach
map.forEach((key, value) -> System.out.println(key + " -> " + value));
// Java 7 loop
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}Iterating a List
List<String> list = Arrays.asList("apple", "banana", "orange");
// Java 8 forEach
list.forEach(item -> System.out.println(item));
// Java 7 loop
for (String item : list) {
System.out.println(item);
}Iterating a Set
Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3));
// Java 8 forEach
set.forEach(item -> System.out.println(item));
// Java 7 loop
for (Integer item : set) {
System.out.println(item);
}Iterating a Stream
List<String> list = Arrays.asList("apple", "banana", "orange");
// Java 8 forEach on Stream
list.stream().forEach(item -> System.out.println(item));
// Java 7 loop on Stream
for (String item : list) {
System.out.println(item);
}Using forEach with Null Check
list.forEach(item -> {
if (item != null) {
// Perform some action
}
});Exception Handling with forEach
list.forEach(item -> {
try {
// Perform some operation that may throw an exception
} catch (Exception e) {
// Handle the exception
}
});forEach vs forEachOrdered
list.stream().forEach(item -> System.out.println(item)); // May not maintain order
list.stream().forEachOrdered(item -> System.out.println(item)); // Maintains orderStringJoiner
The stringjoiner class was introduced in java8. string joiner is a final class and is present in Java. util. package
The StringJoiner class was introduced to provide a convenient way to join strings with a delimiter. It is particularly useful when you need to concatenate multiple strings with a separator between them.
import java.util.StringJoiner;
public class StringJoinerExample {
public static void main(String[] args) {
StringJoiner stringJoiner = new StringJoiner(", ");
stringJoiner.add("Bade Miyan Chote Miyan");
stringJoiner.add("Maidaan");
stringJoiner.add("Dukaan");
stringJoiner.add("The Lost Girl");
String hindiMovies = stringJoiner.toString();
System.out.println(hindiMovies);
}
}
// output
Bade Miyan Chote Miyan, Maidaan, Dukaan, The Lost GirlYou can also specify a prefix and suffix for the joined string by providing them as arguments to the StringJoiner constructor.
import java.util.StringJoiner;
public class StringJoinerExample {
public static void main(String[] args) {
StringJoiner playingXI = new StringJoiner(", ", "[", "]");
playingXI.add("Quinton de Kock");
playingXI.add("KL Rahul");
playingXI.add("Devdutt Padikkal");
playingXI.add("Marcus Stoinis");
playingXI.add("Nicholas Pooran");
playingXI.add("Ayush Badoni");
playingXI.add("Krunal Pandya");
playingXI.add("Ravi Bishnoi");
String playerNames = playingXI.toString();
System.out.println(playerNames);
}
}
// Output
[Quinton de Kock, KL Rahul, Devdutt Padikkal, Marcus Stoinis, Nicholas Pooran, Ayush Badoni, Krunal Pandya, Ravi Bishnoi]Spliterator
The Spliterator interface in Java 8 provides a way to traverse and partition elements in a collection.
Itβs part of the Java collections framework and is designed to work with streams for efficient parallel execution.
Basic Functionality
- Traversal: Spliterator allows traversal of elements in a collection.
- Partitioning: It can partition a portion of elements for parallel processing, which is especially useful for parallel streams.
- Characteristics: Spliterator defines characteristics like SIZED, SORTED, CONCURRENT, and others, which describe the properties of the underlying data source and how it can be processed.
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
public class SpliteratorExample {
public static void main(String[] args) {
List<String> listNames = List.of("kuna", "runa", "kuna", "kanha");
// Iterator<String> iterator = listNames.iterator();
// while (iterator.hasNext()) {
// System.out.println(iterator.next());
// }
Spliterator<String> spliterator = listNames.stream().sorted().spliterator();
spliterator.forEachRemaining(System.out::println);
}
}Letβs see an example of using Spliterator with a List to process elements in parallel:
import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;
public class SpliteratorExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add("Mango");
// Get a Spliterator for the list
Spliterator<String> spliterator = fruits.spliterator();
// Split the Spliterator for parallel processing
Spliterator<String> split1 = spliterator.trySplit();
Spliterator<String> split2 = spliterator.trySplit();
// Process elements in parallel
System.out.println("Split 1:");
split1.forEachRemaining(System.out::println);
System.out.println("Split 2:");
split2.forEachRemaining(System.out::println);
// Process remaining elements
System.out.println("Remaining:");
spliterator.forEachRemaining(System.out::println);
}
}Split 1:
Apple
Banana
Split 2:
Orange
Mango
Remaining:In this above example
- We create a List of fruits and obtain a Spliterator using the spliterator() method.
- We then use trySplit() to split the Spliterator into multiple parts for parallel processing.
- Each split is processed using forEachRemaining(), and then the remaining elements are processed.
What is Nashorn?
Nashorn is a JavaScript engine introduced in Java 8, allowing developers to execute JavaScript code within the Java Virtual Machine (JVM).
Nashorn is a JavaScript engine for the JVM that is released with Java 8. Nashorn comprises of an embedded JavaScript interpreter and a command line tool. The objective of Nashorn is to implement a high-performance JavaScript runtime in Java with a native JVM. Using Nashorn, a developer can embed JavaScript in a Java application and can also invoke Java methods and classes from the JavaScript code providing a seamless integration between the two languages.
Executing JavaScript from Java
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class NashornExample {
public static void main(String[] args) {
// Create a ScriptEngineManager
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
// Get Nashorn ScriptEngine
ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn");
// JavaScript code as a String
String script = "var greeting = 'Hello, Nashorn'; greeting";
try {
// Evaluate the JavaScript code
Object result = nashorn.eval(script);
// Print the result
System.out.println(result); // Output: Hello, Nashorn
} catch (ScriptException e) {
e.printStackTrace();
}
}
}Stream API with Examples
What is the Stream API? the Stream API is used to process collections of objects. A stream is a sequence of objects that supports various methods which can be pipelined to produce the desired result.
Types of Streams:
- Sequential Streams: These process elements in a single thread, ensuring predictable order and simplicity.
- Parallel Streams: Parallel streams, as the name suggests, process elements in parallel using multiple threads. They are designed to take advantage of modern multi-core processors and are especially useful for computationally intensive tasks.
Stream Operations:
Stream operations are divided into two categories: intermediate and terminal operations.
Intermediate Operations:
filter(Predicate<T> predicate)- Filters elements based on a given predicate.map(Function<T, R> mapper)- Transforms each element using a given function.flatMap(Function<T, Stream<R>> mapper)- Transforms each element and flattens the result into a single stream.distinct()- Removes duplicate elements.sorted()- Sorts elements using their natural order or a custom comparator.peek(Consumer<T> action)- Performs an action on each element without changing the stream.limitβ Thelimitoperation sets a boundary on how many elements you want from the streamskip(int n)discards the first three elements, as indicatedn
Terminal Operations:
forEach(Consumer<T> action)- Applies an action to each element.collect(Collector<T, A, R> collector)- Collects the stream into a collection.reduce(T identity, BinaryOperator<T> accumulator)- Combines elements in a stream.count()- Counts the number of elements in the stream.min(Comparator<T> comparator)- Finds the minimum element.max(Comparator<T> comparator)- Finds the maximum element.anyMatch(Predicate<T> predicate)- Checks if any element matches a predicate.allMatch(Predicate<T> predicate)- Checks if all elements match a predicate.noneMatch(Predicate<T> predicate)- Checks if no element matches a predicate.findFirst()- Finds the first element in the stream.findAny()- Finds any element in the stream.
Here as you see some of the operations takes the Consumer, Predicate, Function as parameters. They are functional interfaces. If have not got chance read them, I recommend read this blog here for understanding them.
Stream API Interview Questions GITHUB Link https://github.com/vinosubi/Streams-Example-part1
Example :
Employee Class
class Employee {
private int id;
private String name;
private int age;
private double salary;
private String department;
// Constructor, getters, and setters
public Employee(int id, String name, int age, double salary, String department) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
this.department = department;
}
public int getId() { return id; }
public String getName() { return name; }
public int getAge() { return age; }
public double getSalary() { return salary; }
public String getDepartment() { return department; }
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
", department='" + department + '\'' +
'}';
}
}Employee List Example
List<Employee> employees = Arrays.asList(
new Employee(1, "John", 28, 3000.00, "HR"),
new Employee(2, "Jane", 32, 4000.00, "Finance"),
new Employee(3, "Jack", 24, 2500.00, "IT"),
new Employee(4, "Jill", 29, 3500.00, "HR"),
new Employee(5, "Jerry", 40, 5000.00, "Finance")
);- Filter() Filters the elements of the stream based on a given condition. The resulting stream contains only the elements that match the condition.
List<Employee> hrEmployees = employees.stream()
.filter(e -> e.getDepartment().equals("HR"))
.collect(Collectors.toList());
System.out.println(hrEmployees);
// output
[Employee{id=1, name='John', age=28, salary=3000.0, department='HR'},
Employee{id=4, name='Jill', age=29, salary=3500.0, department='HR'}]2. Map()
Transforms each element in the stream by applying a function to it. The resulting stream contains the transformed elements.
List<String> employeeNames = employees.stream()
.map(Employee::getName)
.collect(Collectors.toList());
System.out.println(employeeNames);
//output
[John, Jane, Jack, Jill, Jerry]3. FlatMap
Flattens a stream of collections into a single stream. Each collection is expanded into a stream of its elements.
List<List<String>> departments = Arrays.asList(
Arrays.asList("HR", "Finance"),
Arrays.asList("IT", "Marketing")
);
List<String> departmentList = departments.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println(departmentList);
// output
[HR, Finance, IT, Marketing]4. Distinct
Removes duplicate elements from the stream. The resulting stream contains only unique elements.
List<String> distinctDepartments = employees.stream()
.map(Employee::getDepartment)
.distinct()
.collect(Collectors.toList());
System.out.println(distinctDepartments);
// output
[HR, Finance, IT]5. Sorted
Sorts the elements in the stream based on a given comparator. The resulting stream is ordered according to the comparator.
List<Employee> sortedByName = employees.stream()
.sorted(Comparator.comparing(Employee::getName))
.collect(Collectors.toList());
System.out.println(sortedByName);
//output
[Employee{id=3, name='Jack', age=24, salary=2500.0, department='IT'},
Employee{id=2, name='Jane', age=32, salary=4000.0, department='Finance'},
Employee{id=4, name='Jill', age=29, salary=3500.0, department='HR'},
Employee{id=1, name='John', age=28, salary=3000.0, department='HR'},
Employee{id=5, name='Jerry', age=40, salary=5000.0, department='Finance'}]6. Peek
Performs an action for each element of the stream, mainly for debugging purposes. It returns the same stream, allowing further operations.
employees.stream()
.peek(e -> System.out.println("Processing: " + e.getName()))
.collect(Collectors.toList());
//output
Processing: John
Processing: Jane
Processing: Jack
Processing: Jill
Processing: Jerry7. Limit
Limits the number of elements in the stream to a given size. The resulting stream contains only the first N elements.
List<Employee> limitedEmployees = employees.stream()
.limit(2)
.collect(Collectors.toList());
System.out.println(limitedEmployees);
//output
[Employee{id=1, name='John', age=28, salary=3000.0, department='HR'},
Employee{id=2, name='Jane', age=32, salary=4000.0, department='Finance'}]8. Skip
Skips the first N elements in the stream. The resulting stream contains the remaining elements after skipping the first N.
List<Employee> skippedEmployees = employees.stream()
.skip(2)
.collect(Collectors.toList());
System.out.println(skippedEmployees);
//output
[Employee{id=3, name='Jack', age=24, salary=2500.0, department='IT'},
Employee{id=4, name='Jill', age=29, salary=3500.0, department='HR'},
Employee{id=5, name='Jerry', age=40, salary=5000.0, department='Finance'}]9. ForEach
Performs an action for each element of the stream. It does not return a value, as it is a terminal operation.
employees.stream()
.forEach(e -> System.out.println(e.getName()));
//output
John
Jane
Jack
Jill
Jerry10. Reduce
Combines the elements of the stream into a single result by repeatedly applying a binary operator.
double totalSalary = employees.stream()
.map(Employee::getSalary)
.reduce(0.0, Double::sum);
System.out.println(totalSalary);
// output
18000.011. Count Counts the number of elements in the stream. The result is a long value representing the count.
long count = employees.stream().count();
System.out.println(count);
//output
512. Min Finds the minimum element in the stream according to a given comparator. It returns an Optional that contains the minimum element if it exists.
Employee youngestEmployee = employees.stream()
.min(Comparator.comparing(Employee::getAge))
.orElse(null);
System.out.println(youngestEmployee);
// output
Employee{id=3, name='Jack', age=24, salary=2500.0, department='IT'}13. Max
Finds the maximum element in the stream according to a given comparator. It returns an Optional that contains the maximum element if it exists.
Employee highestPaidEmployee = employees.stream()
.max(Comparator.comparing(Employee::getSalary))
.orElse(null);
System.out.println(highestPaidEmployee);
//output
Employee{id=5, name='Jerry', age=40, salary=5000.0, department='Finance'}14. AnyMatch
Checks if any element in the stream matches the given condition. It returns a boolean value.
boolean anyOver30 = employees.stream()
.anyMatch(e -> e.getAge() > 30);
System.out.println(anyOver30);
// output
true15. AllMatch
Checks if all elements in the stream match the given condition. It returns a boolean value.
boolean allOver20 = employees.stream()
.allMatch(e -> e.getAge() > 20);
System.out.println(allOver20);
//output
true16. NoneMatch
Checks if no elements in the stream match the given condition. It returns a boolean value.
boolean noneOver50 = employees.stream()
.noneMatch(e -> e.getAge() > 50);
System.out.println(noneOver50);
// output
true17. FindFirst
Finds the first element in the stream. It returns an Optional that contains the first element if it exists.
Employee firstEmployee = employees.stream()
.findFirst()
.orElse(null);
System.out.println(firstEmployee);
// output
Employee{id=1, name='John', age=28, salary=3000.0, department='HR'}18. FindAny
Finds any element in the stream. It returns an Optional that contains any element if it exists.
Employee anyEmployee = employees.stream()
.findAny()
.orElse(null);
System.out.println(anyEmployee);
// output
Employee{id=1, name='John', age=28, salary=3000.0, department='HR'}


