top

Search

Java Tutorial

.

UpGrad

Java Tutorial

Java 8 features

Introduction

The Java programming language underwent substantial improvements with the release of Java 8 in 2014. Lambda expressions, the Stream API, the Date and Time API, default and static methods in interfaces, Collection API improvements, concurrency API improvements, and Java IO enhancements are just a few of the new features it introduced. For you to fully utilize Java 8 in your development projects, we will delve into these capabilities in this post, including Java 8 features with examples, explanations, and best practices.

Image Courtesy: RealToughCandy.com (Pexels)

Overview

Java 8 introduced a range of features that revolutionized the way developers write code. These features enhance the expressiveness, efficiency, and functionality of the language. By incorporating functional programming concepts, Java 8 allows for more concise and readable code, making it easier to solve complex problems.

What is Java 8?

Java 8 is a major release of the Java programming language, introducing new features and improvements to enhance developer productivity and code quality. It focuses on bringing functional programming capabilities to the language, enabling developers to write more expressive and efficient code.

Key Java 8 Features With Examples For Experienced 

Let's explore the important Java 8 features with examples along with explanations for each.

Lambda Expressions and Functional Interfaces

A lambda expression is a fundamental addition to Java 8, enabling developers to write more concise and expressive code. They represent anonymous functions and can be used wherever a functional interface is expected.

Functional interfaces are interfaces that contain exactly one abstract method. They serve as the basis for lambda expressions and facilitate functional programming in Java. With lambda expressions and functional interfaces, you can write code that is more declarative and focused on behavior.

Here's an example that demonstrates the use of lambda expressions:

java
interface MathOperation {
    int operate(int a, int b);
}
public class LambdaExample {
    public static void main(String[] args) {
        MathOperation addition = (a, b) -> a + b;
        MathOperation subtraction = (a, b) -> a - b;

        int result1 = addition.operate(5, 3);
        int result2 = subtraction.operate(10, 6);

        System.out.println("Result of addition: " + result1);
        System.out.println("Result of subtraction: " + result2);
    }
}

In this example, we define the abstract method operate() as the only functional method of the interface MathOperation. In order to describe the behaviour for addition and subtraction operations, we build instances of this interface using lambda expressions. The procedures are then carried out on these occurrences, and the outcomes are displayed.

When working with functional programming ideas, lambda expressions and functional interfaces offer a potent technique to construct more succinct and expressive code.

Stream API for Bulk Data Operations on Collections

The new Java 8 features Stream API introduced in Java 8 provides a declarative and functional method for processing and manipulating collection data. It permits concise and legible code by facilitating operations such as filtering, mapping, and reducing, among others.

Various sources, including collections, arrays, and I/O channels, can be used to construct streams. Streams can be converted and operated on using intermediate and terminal operations once they have been created.

Intermediate Operations:

  • filter(): Filters the stream based on a given predicate.

  • map(): Transforms each element of the stream using a mapping function.

  • sorted(): Sorts the elements of the stream in natural order or using a custom comparator.

  • distinct(): Removes duplicate elements from the stream.

  • limit(): Limits the size of the stream to a specified number of elements.

  • skip(): Skips the first n elements of the stream.

Terminal Operations:

  • forEach(): Applies a specified action to each element of the stream.

  • count(): Returns the count of elements in the stream.

  • collect(): Performs a mutable reduction operation on the elements of the stream and accumulates the results in a collection.

  • reduce(): Performs a reduction operation on the elements of the stream, combining them into a single result.

  • anyMatch(), allMatch(), noneMatch(): Checks if any, all, or none of the elements match a given predicate.

  • findFirst(), findAny(): Returns the first or any element of the stream, respectively.

Here's an example that demonstrates the use of the Stream API:

java

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
                .filter(n -> n % 2 == 0)
                .mapToInt(n -> n * 2)
                .sum();
System.out.println("Sum of doubled even numbers: " + sum);

In this example, a list of numbers is converted into a stream. Several operations are performed on the stream, including filter() to retain only the even numbers, mapToInt() to double each even number, and sum() to calculate the sum of the resultant numbers.

The Stream API offers a robust and expressive method for performing bulk data operations on collections, thereby fostering a more functional and declarative programming style.

Java 8 features w3schools- The Date and Time API in Java

In Java 8, the Date and Time API was introduced to address the shortcomings of the java.util.Date and java.util.Calendar classes. The new API offers enhanced date, time, and time zone flexibility and functionality.

The Date and Time API includes the LocalDate, LocalTime, LocalDateTime, and ZonedDateTime classes. These classes provide methods for performing operations such as parsing, formatting, arithmetic, and comparison.

The following example illustrates the use of the Date and Time API:

java

LocalDate currentDate = LocalDate.now();

LocalDate futureDate = currentDate.plusDays(7);

System.out.println("Current date: " + currentDate);

System.out.println("Date after 7 days: " + futureDate);

In this example, we use the LocalDate class to represent the current date. We then use the plusDays() method to calculate the date after seven days. Finally, we display the current date and the date after seven days.

The Date and Time API simplifies date and time handling and provides a more intuitive and flexible approach to working with temporal data.

Default and Static Methods in Interfaces

Before Java 8, interfaces merely declared abstract methods. Previously, implementing classes implemented them. Java 8's default and static methods now allow interfaces to implement methods.

Interfaces can define default methods for implementing classes to use. This feature allows new interface methods to be backwards compatible.

Static methods can be used on the interface without an instance of the implementing class. Interfaces with static methods provide practical implementation methods.

Here's an example that demonstrates the use of default and static methods in interfaces:

java

interface Shape {
    void draw();
    default void printDescription() {
        System.out.println("This is a shape.");
    }
    static void printInfo() {
        System.out.println("This is the Shape interface.");
    }
}
class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }
}
public class InterfaceExample {
    public static void main(String[] args) {
        Circle circle = new Circle();
        circle.draw();
        circle.printDescription();
        Shape.printInfo();
    }
}

In this example, we define an interface Shape using a default method printDescription() and a static method printInfo(). We create a class Circle that uses the Shape interface and provides an implementation for the draw() method. We then create an instance of Circle and invoke its methods as well as the static method of the Shape interface.

Default and static methods in interfaces provide additional flexibility and utility, making it easier to evolve interfaces and reuse code across implementations.

Collection API Improvements

Java 8 brings several improvements to the Collection API, making it more powerful and convenient to work with collections.

The forEach() method that has been added to the Iterable and Collection interfaces is a notable enhancement. You can loop through collection elements using the forEach() method and give each one specific action.

Here's a Java 8 feature with example that demonstrates the usage of the forEach() method:

Java

java

List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.forEach(name -> System.out.println("Hello, " + name));

In this example, we create a list of names, iterate over each name using the forEach() method, and output a salutation for each. Each element's action is specified by the lambda expression (name -> System.out.println("Hello, " + name)).

Java 8 also introduces the removeIf() method to the Collection interface. The removeIf() method allows you to remove elements from a collection based on a specified predicate.

Here's an example that demonstrates the usage of the removeIf() method:

java

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

numbers.removeIf(n -> n % 2 == 0);

System.out.println(numbers);

In this example, we create a list of numbers and use the removeIf() method to remove even numbers from the list. The lambda expression (n -> n % 2 == 0) specifies the predicate for removing elements.

These enhancements to the Collection API simplify common operations on collections and make code more readable and expressive.

Concurrency API Improvements

With the addition of the CompletableFuture class and updates to the java.util.concurrent package, Java 8 improves the concurrency utilities.

The CompletableFuture class in Java presents a higher-level abstraction for asynchronous programming. It makes it simpler to manage complex scenarios with numerous dependent activities by allowing you to build and chain asynchronous computations.

Here's an example that demonstrates the usage of CompletableFuture:

java

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 42)

        .thenApplyAsync(num -> num * 2)

        .thenApplyAsync(num -> num + 1);

future.thenAcceptAsync(result -> System.out.println("Final result: " + result));

// ...

In this example, we create a CompletableFuture that represents an asynchronous computation to calculate the number 42. We then chain two additional computations using thenApplyAsync() to double the number and add 1. Finally, we use thenAcceptAsync() to print the final result when it becomes available.

Additionally, Java 8 adds the parallelStream() method to the Stream interface, allowing stream operations to be executed concurrently. Using multi-core processors, this technique automatically splits the stream into several substreams and processes them simultaneously, enhancing performance for computationally demanding jobs.

Java IO Improvements

Java 8 includes improvements to the IO APIs, making it easier and more efficient to work with files and streams.

The Files class provides new methods for reading, writing, and manipulating files. It simplifies common file operations and provides better control over file handling.

Here's an example that demonstrates the usage of the Files class:

java

try {
    List<String> lines = Files.readAllLines(Paths.get("file.txt"));
    lines.forEach(System.out::println);
    Files.write(Paths.get("output.txt"), lines);
} catch (IOException e) {
    e.printStackTrace();
}

In this example, we use the Files class to read all lines from a file and print them. We then write the lines to another file using the write() method.

These IO improvements streamline file handling and improve the overall efficiency of IO operations in Java.

Java 8 Features Interview Questions

Some of the common interview questions that you can come across are discussed below. 

  • What programming paradigm does Java 8 fall under?

    • Language for object-oriented programming.

    • Language for functional programming.

    • Language for procedural programming.

    • Logic programming language

  • What are the genuine benefits of Java 8

    • More concise and legible code.

    • Code is more reusable

    • It is easier to evaluate and maintain code.

    • Now, code is both synchronous and scalable.

    • Users can compose code in tandem

    • Users are able to compose database-like operations

  • What is the significance of Java 8's functional interfaces?

In Java 8, functional interfaces are interfaces with a single abstract method.

The following three categories of methods may be present:

  • The static method

  • The default method

  • The overridden class method

Functional Interfaces and Lambda Expressions

Referring to Java 8 features geekforgeeks, functional interfaces and lambda expressions are powerful features introduced in Java 8. Lambda expressions work with functional interfaces, which have one abstract method. Lambda expressions instead of unnamed inner classes make functional interface code succinct and expressive. These traits enhance functional programming paradigms and code legibility. Java 8's functional interfaces and lambda expressions simplify working with functions and behaviors in applications.

Consider, for instance, the Comparator interface. In previous versions of Java, custom sorting logic required the creation of an anonymous inner class. In Java 8, you can accomplish the same functionality in a more compact manner by using a lambda expression.

java

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// Sorting using anonymous inner class

Collections.sort(names, new Comparator<String>() {

    @Override

    public int compare(String name1, String name2) {

        return name1.compareTo(name2);

    }

});

// Sorting using lambda expression

Collections.sort(names, (name1, name2) -> name1.compareTo(name2));

Best Practices for Using Lambda Expressions in Java 8 Programming

It is essential to adhere to best practices when using lambda expressions in Java 8 to ensure clear and maintainable code. Ensure that the lambda expression is concise and legible, concentrating on its function. Consider extracting a lengthy lambda expression into a distinct method or utilizing method references to improve readability. Check out the following example:

java

// Lengthy lambda expression
Function<Integer, String> toString = (num) -> {
    if (num % 2 == 0) {
        return "Even";
    } else {
        return "Odd";
    }
};


// Improved version using method references
Function<Integer, String> toString = (num) -> (num % 2 == 0) ? "Even" : "Odd";

Additionally, be cautious when capturing variables from the surrounding scope and avoid mutating them. This promotes immutability and prevents unexpected side effects:

java

int counter = 0;
Runnable runnable = () -> {
    // Avoid mutating variables from the surrounding scope
    // counter++; // Avoid such mutations
    System.out.println("Counter value: " + counter);
};
counter = 10;
runnable.run(); // Output: Counter value: 0

In order to improve thread safety and reusability, lambda expressions should be immutable and stateless.

Conclusion

Java 8 added features and improvements that help developers write more expressive and efficient code. Java 8's lambda expressions, functional interfaces, Stream API, Collection API, Date and Time API, concurrency API, and Java IO features simplify and improve programming.

Developers may maximize Java's capabilities and productivity by using these features. Java 8 features in Spring Boot further augment your programming output. These improvements can improve your coding and maximize Java 8's potential.

FAQs

  1. What is the best feature of Java 8?

Java Lambda, which describes a method interface using an expression, is a major Java 8 innovation. This saves the developer time and effort.

  1. What is optional in Java 8? 

Optional, a container object contains non-null objects. An optional object represents null without value. 

      3. Which Java 8 method can be used to obtain an object's source?

Java 8's Collection interface provides two methods for generating Streams. stream() Returns a sequential stream with collection as the source. parallelStream() Returns a parallel Stream with source collection.

Leave a Reply

Your email address will not be published. Required fields are marked *