Java 8 features in detail

Blog post description.

CORE JAVA

5/13/20246 min read

  1. What are the major features introduced in Java 8?

    • Lambda Expressions

    • Stream API

    • Default Methods in Interfaces

    • Optional Class

    • Date and Time API (java.time package)

    • Nashorn JavaScript Engine

  2. What are Lambda Expressions?


Lambda expressions in Java are a way to create anonymous functions—functions without a name—that can be passed around and used as arguments to other functions. They make your code more concise and expressive.

Here's a simple explanation with an example:

Suppose you have a list of numbers and you want to sort them in ascending order. Traditionally, you might use an anonymous inner class with the Comparator interface like this:);

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);

Collections.sort(numbers, new Comparator<Integer>() {

@Override

public int compare(Integer a, Integer b) {

return a.compareTo(b);

}

});

System.out.println(numbers);

But with lambda expressions, you can achieve the same result with much less code:

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);

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

System.out.println(numbers);

In this example, (a, b) -> a.compareTo(b) is the lambda expression. It represents an implementation of the compare method of the Comparator interface.

Here's a breakdown of what's happening:

  • (a, b) declares the parameters of the lambda expression.

  • -> separates the parameters from the body of the lambda expression.

  • a.compareTo(b) is the body of the lambda expression, which compares two integers a and b.

Lambda expressions are particularly useful in situations where you need to pass a simple piece of functionality as an argument to a method, such as when working with collections, threads, or event handlers. They help you write cleaner and more expressive code.

3.Explain Stream API with example

The Stream API in Java provides a way to work with collections of objects in a functional style. It allows you to perform operations like filtering, mapping, and reducing on collections with ease.

Here's a simple explanation with an example:

Suppose you have a list of numbers and you want to find the sum of all even numbers greater than 5. Using traditional loops, you might write code like this:

List<Integer> numbers = Arrays.asList(3, 5, 8, 10, 12, 15);

int sum = 0;

for (int num : numbers) {

if (num > 5 && num % 2 == 0) {

sum += num;

}

}

System.out.println("Sum of even numbers greater than 5: " + sum);

With the Stream API, you can achieve the same result in a more concise and expressive way:

List<Integer> numbers = Arrays.asList(3, 5, 8, 10, 12, 15);

int sum = numbers.stream() // Convert the list to a stream

.filter(num -> num > 5 && num % 2 == 0) // Filter out numbers greater than 5 and even

.mapToInt(Integer::intValue) // Map each number to its integer value

.sum(); // Calculate the sum

System.out.println("Sum of even numbers greater than 5: " + sum);

In this example:

  • numbers.stream() converts the list of numbers into a stream.

  • .filter(num -> num > 5 && num % 2 == 0) filters out only the numbers greater than 5 and even.

  • .mapToInt(Integer::intValue) converts the stream of Integer objects to an IntStream.

  • .sum() calculates the sum of all elements in the stream.

The Stream API allows you to write code that is more declarative and easier to understand. It encourages functional programming concepts like immutability and lazy evaluation, and it can make your code more readable and maintainable.

4.Explain Default Methods with examples
Default methods in Java interfaces were introduced in Java 8. They allow you to add new methods to an interface without breaking existing implementations of that interface. These methods have a default implementation provided in the interface itself.
Here's a simple explanation with an example:

Suppose you have an interface called Animal:

interface Animal {

void eat();

void sleep();

}

Now, let's say you want to add a new method default void sound(), which provides a default implementation for making a sound. You can do it like this:

interface Animal {

void eat();

void sleep();

default void sound() {

System.out.println("Animal makes a sound");

}

}

In this example, sound() is a default method. It has a default implementation provided within the interface itself.

Here's how you can use this interface with default methods:

class Dog implements Animal {

@Override

public void eat() {

System.out.println("Dog eats");

}

@Override

public void sleep() {

System.out.println("Dog sleeps");

}

}

public class Main {

public static void main(String[] args) {

Dog dog = new Dog();

dog.eat();

dog.sleep();

dog.sound(); // Calls the default method implementation

}

}

Output :

Dog eats

Dog sleeps

Animal makes a sound

Even though the sound() method is not explicitly implemented in the Dog class, it can still be called because it has a default implementation in the Animal interface.

Default methods allow you to extend interfaces without breaking existing code, and they're commonly used to provide backward compatibility when evolving APIs.

4. Explain Optional in Java 8
The Optional class in Java was introduced in Java 8. It's designed to help avoid null pointer exceptions and make your code more robust when dealing with situations where a value may or may not be present.Here's a simple explanation with an example:

Suppose you have a method that returns a String, but sometimes it might not find a result and return null. Without Optional, you would have to check for null every time you call that method, which can lead to messy and error-prone code.

Here's how you might handle it without Optional:

String result = searchForValue();

if (result != null) {

System.out.println("Result found: " + result);

} else {

System.out.println("No result found");

}

With Optional, you can rewrite the code in a cleaner and more expressive way:

Optional<String> optionalResult = Optional.ofNullable(searchForValue());

optionalResult.ifPresentOrElse(

result -> System.out.println("Result found: " + result),

() -> System.out.println("No result found")

);

In this example:

  • Optional.ofNullable(searchForValue()) creates an Optional object that may or may not contain a result. If searchForValue() returns null, it creates an empty Optional.

  • ifPresentOrElse() is a method that takes two parameters: a consumer for the case where the value is present, and a runnable for the case where the value is absent.

5.Explain Date and Time API (java.time package) in Java 8 :

The Date and Time API, introduced in Java 8, is provided by the java.time package. It offers improved date and time handling compared to the old java.util.Date and java.util.Calendar classes, which were known for their complexity and lack of clarity.

Here's an overview of the main components of the java.time package:

  1. LocalDate: Represents a date without a time component. It stores year, month, and day of the month. For example:

LocalDate today = LocalDate.now();

int year = today.getYear();

int month = today.getMonthValue();

int day = today.getDayOfMonth();

2.LocalTime: Represents a time without a date component. It stores hour, minute, second, and fractional seconds. For example:

LocalTime time = LocalTime.now();

int hour = time.getHour();

int minute = time.getMinute();

int second = time.getSecond();

3.LocalDateTime: Represents a date and time without a time zone. It combines LocalDate and LocalTime. For example:

LocalDateTime dateTime = LocalDateTime.now();

int year = dateTime.getYear();

int month = dateTime.getMonthValue();

int day = dateTime.getDayOfMonth();

int hour = dateTime.getHour();

int minute = dateTime.getMinute();

int second = dateTime.getSecond();

4.ZonedDateTime: Represents a date and time with a time zone. It's an extension of LocalDateTime that includes a time zone. For example:


ZonedDateTime zonedDateTime = ZonedDateTime.now();

ZoneId zoneId = zonedDateTime.getZone();

5.Period: Represents a period of time in years, months, and days. It can be used to perform date arithmetic. For example:


LocalDate startDate = LocalDate.of(2022, Month.JANUARY, 1);

LocalDate endDate = LocalDate.of(2023, Month.DECEMBER, 31);

Period period = Period.between(startDate, endDate);

int years = period.getYears();

int months = period.getMonths();

int days = period.getDays();

6.Duration: Represents a duration of time in seconds and nanoseconds. It can be used to perform time arithmetic. For example:



LocalTime startTime = LocalTime.of(9, 0);

LocalTime endTime = LocalTime.of(17, 0);

Duration duration = Duration.between(startTime, endTime);

long hours = duration.toHours();

long minutes = duration.toMinutes();


These are just some of the key classes and concepts in the java.time package. The API provides various other classes and methods for date and time manipulation, formatting, parsing, and more. It's designed to be more intuitive, clearer, and safer to use compared to the old date and time classes in Java.

6.Explain Nashorn JavaScript Engine in Java 8

Nashorn is a JavaScript engine introduced in Java 8 as part of the JDK (Java Development Kit). It allows you to run JavaScript code within a Java application, providing seamless integration between Java and JavaScript.

Here's an overview of Nashorn and its features:

  1. Performance: Nashorn is known for its performance improvements over the older JavaScript engine, Rhino. It uses modern JavaScript runtime optimizations and provides better performance for executing JavaScript code.

  2. Integration with Java: Nashorn allows you to seamlessly call Java code from JavaScript and vice versa. You can create Java objects, call Java methods, and access Java classes directly from JavaScript code, making it easy to integrate JavaScript into Java applications.

  3. ECMAScript 5.1 support: Nashorn supports the ECMAScript 5.1 standard, which is a widely accepted version of the JavaScript language specification. It provides support for features like regular expressions, JSON parsing, and other standard JavaScript functionalities.

  4. Command-line tool: Nashorn comes with a command-line tool (jjs) that allows you to execute JavaScript code directly from the command line. This tool can be handy for testing and experimenting with JavaScript code without the need for a web browser or a separate JavaScript runtime environment.

  5. Optimization: Nashorn optimizes JavaScript code execution by compiling it to Java bytecode. This bytecode can be executed efficiently by the Java Virtual Machine (JVM), resulting in improved performance compared to interpreted JavaScript code.

  6. Access to Java libraries: Since Nashorn is part of the Java ecosystem, it has access to the vast array of Java libraries available, allowing JavaScript code to leverage existing Java functionality without reinventing the wheel.

Overall, Nashorn provides a powerful and efficient way to execute JavaScript code within Java applications, enabling developers to build applications that leverage the strengths of both languages seamlessly. However, it's worth noting that Nashorn has been deprecated starting from Java 11, and developers are encouraged to migrate to alternative JavaScript engines like GraalVM's JavaScript engine for future compatibility and better performance.