Working with Java 8 Features

Mohit  UpadhyayMohit Upadhyay
5 min read

On Day 20, we will focus on the major enhancements introduced in Java 8. This version of Java introduced several powerful features aimed at functional programming, making the code more concise, readable, and optimized for parallel processing. Key concepts include Lambda Expressions, Stream API, Functional Interfaces, Method References, and the Joda Time API.


1. Lambda Expressions in Java

A Lambda Expression is essentially a concise way to represent an anonymous function (a function without a name) that can be passed around. It enables you to express instances of single-method interfaces (functional interfaces) more succinctly.

A. Syntax of Lambda Expressions

Lambda expressions are defined using the following syntax:

(parameters) -> expression
(parameters) -> { statements }

B. Example of Lambda Expressions

Here’s how you would traditionally implement a runnable using an anonymous class:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running in a thread");
    }
};
new Thread(runnable).start();

With a Lambda Expression, the same can be written in a more concise form:

Runnable runnable = () -> System.out.println("Running in a thread");
new Thread(runnable).start();

C. Benefits of Lambda Expressions

  • Conciseness: Reduces boilerplate code.

  • Improved readability: Makes code easier to understand.

  • Functional programming style: Lambda brings functional programming elements to Java.


2. Stream API

The Stream API is one of the most powerful features introduced in Java 8. It allows you to process collections of objects in a declarative way (i.e., you focus on the what rather than the how). With streams, you can perform operations like filtering, mapping, reducing, and more.

A. Characteristics of Streams

  • Sequence of elements: A stream provides a view of data that can be processed in a sequence.

  • Stateless: Each operation on the stream does not affect the original data.

  • Lazy: Intermediate operations are not executed until a terminal operation is invoked.

B. Stream Operations

Streams support two types of operations:

  • Intermediate operations: These operations return another stream, such as filter(), map(), sorted(), etc.

  • Terminal operations: These operations terminate the stream and produce a result, such as collect(), forEach(), reduce(), etc.

C. Example of Stream API

Here’s an example that demonstrates how to filter and print a list of numbers using a stream:

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, 6);

        // Using Stream to filter even numbers and print them
        numbers.stream()
               .filter(n -> n % 2 == 0)
                // Method Refrence
               .forEach(System.out::println); // System.out::println , it is basically a syntatical sugar ===> x -> Sytem.out.println(x) is same 
    }
}

D. Common Stream Methods

  • filter(): Filters elements based on a condition.

  • map(): Transforms each element.

  • reduce(): Reduces the elements to a single result.

  • collect(): Gathers the elements into a collection or other container.

  • forEach(): Performs an action on each element.


3. Functional Interfaces and Method References

A. Functional Interface

A Functional Interface is an interface that contains exactly one abstract method. It can have multiple default or static methods, but only one abstract method is required to be functional. The primary purpose of functional interfaces is to facilitate the use of Lambda Expressions.

Java 8 introduced several pre-defined functional interfaces in the java.util.function package:

  • Predicate<T>: Represents a boolean-valued function of one argument (test()).

  • Function<T, R>: Represents a function that takes an argument of type T and returns a result of type R (apply()).

  • Consumer<T>: Represents an operation that takes a single input but returns no result (accept()).

B. Example of Functional Interface

Here’s an example of using the Predicate interface:

import java.util.function.Predicate;

public class Main {
    public static void main(String[] args) {
        Predicate<Integer> isEven = n -> n % 2 == 0;

        System.out.println(isEven.test(4));  // Output: true
        System.out.println(isEven.test(5));  // Output: false
    }
}

C. Method References

Method References allow you to refer to methods or constructors directly using the :: operator. They are shorthand for lambdas that call a single method.

There are four types of method references:

  1. Static method reference: ClassName::staticMethodName

  2. Instance method reference of a particular object: object::instanceMethodName

  3. Instance method reference of an arbitrary object of a given type: ClassName::instanceMethodName

  4. Constructor reference: ClassName::new

Example:
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Jane", "Tom");

        // Using method reference to print each name
        names.forEach(System.out::println);
    }
}

4. Joda-Time API (Java Time API)

Java 8 introduced the java.time package as a modern date and time API, addressing the limitations of the older java.util.Date and java.util.Calendar classes. The API is heavily influenced by the Joda-Time library and provides a more natural and intuitive way of handling dates and times.

A. Key Classes in the Java Time API

  1. LocalDate: Represents a date (year, month, day) without time or timezone information.

  2. LocalTime: Represents a time (hours, minutes, seconds) without date or timezone information.

  3. LocalDateTime: Combines date and time without timezone information.

  4. ZonedDateTime: Represents a date-time with timezone information.

B. Example of the Java Time API

Here’s how to use the LocalDate and LocalTime classes:

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        // Current Date
        LocalDate currentDate = LocalDate.now();
        System.out.println("Current Date: " + currentDate);

        // Current Time
        LocalTime currentTime = LocalTime.now();
        System.out.println("Current Time: " + currentTime);

        // Formatting a date
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
        String formattedDate = currentDate.format(formatter);
        System.out.println("Formatted Date: " + formattedDate);
    }
}

C. Benefits of the Java Time API

  • Immutable objects: All date-time classes are immutable and thread-safe.

  • Clear separation: There’s a clear separation between different types of date and time.

  • Time zones: Full support for time zones with ZonedDateTime.

  • More readable: It’s easier to manipulate dates and times without the convoluted logic of Date or Calendar.


Conclusion

The features introduced in Java 8, like Lambda Expressions, Stream API, Functional Interfaces, Method References, and the Java Time API, transformed the way Java developers write code, making it more expressive, concise, and aligned with functional programming concepts. Mastering these features is essential to becoming proficient in modern Java development. Stay tuned! for further updates .

10
Subscribe to my newsletter

Read articles from Mohit Upadhyay directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mohit  Upadhyay
Mohit Upadhyay