Table of Contents
Share this Resource

Java 8 Features with Examples Explained

The Rundown

1. Java 8 introduced features that support more concise and functional-style programming.
2. Lambda expressions and functional interfaces work together to represent and pass behaviour.
3. The Stream API enables pipeline-based processing of data from sources such as collections.
4. The Date-Time API provides dedicated classes for handling dates, times, time zones and durations.
5. Some Java 8 features, such as Nashorn, are now legacy features and are no longer included in current JDK releases.

Less boilerplate. More expressive code. A new way to work with Java.

That was the shift Java 8 introduced. Features such as lambda expressions, functional interfaces and the Stream API expanded Java's support for functional-style programming, while the new Date-Time API provided a more modern approach to handling dates and times.

Let's explore the key Java 8 features through simple examples and see what each one brought to the language and its APIs.

Java 8: Features and Examples

Java 8 introduced language and API enhancements that support more concise, functional-style programming and improved date-time handling. The following are some important Java 8 features with examples:

Best Java Programming Training

1) Lambda Expressions

Lambda expressions provide a concise way to represent behaviour that can be passed to methods. They can take parameters and may return a value, depending on the target functional interface. They are commonly used with Single Abstract Method (SAM), or functional, interfaces and can help reduce boilerplate code.

Syntax:

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

Here, ‘parameters’ refer to the input arguments of the Lambda expression and ‘expression’ or ‘statements’ refer to the computation performed by the Lambda expression.

Here is an example of using Lambda expressions in Java 8 to sort a list of strings in ascending order:

import java.util.*;
public class LambdaExample
{
public static void main(String[] args)
{
List names = Arrays.asList("John", "Alex", "Mike", "Bob", "Mary");
System.out.println("Before sorting: " + names);
Collections.sort(names, (String s1, String s2) -> s1.compareTo(s2));
System.out.println("After sorting: " + names);
}
}

To sort the list and pass a lambda expression as the second argument to specify the sorting order, the ‘Collections.sort()’ method is used.

The lambda expression (String s1, String s2) -> s1.compareTo(s2) takes two string arguments, ‘s1’ and ‘s2’ and returns an integer value that represents their order. The lambda compares two strings using String.compareTo(), which returns a negative value, zero, or a positive value depending on their lexicographical order.

Output:

Before sorting: [John, Alex, Mike, Bob, Mary]
After sorting: [Alex, Bob, John, Mary, Mike]

Before vs After Java 8

Before Java 8: Behaviour was commonly passed using anonymous classes, often requiring several lines of code.
With Java 8: Lambda expressions can represent compatible functional-interface implementations much more concisely.
The shift: Less boilerplate → clearer expression of behaviour.

2) Functional Interfaces

Functional interface is one of the important Java 8 Features. An interface that carries a single abstract method is called a functional interface. They are used as the basis for Lambda expressions and method references. A functional interface is also known as a Single Abstract Method (SAM) interface.

Syntax:

@FunctionalInterface
public interface MyFunctionalInterface {
public abstract void myMethod();
}

Here, ‘MyFunctionalInterface’ is a functional interface that declares a single abstract method named ‘myMethod()’. The following is an example of using a functional interface:

import java.util.function.*;
public class FunctionalInterfaceExample { public static void main(String[] args) {
   // Example 1: using the Predicate functional interface
    System.out.println("Is 5 positive? " + isPositive.test(5));
    System.out.println("Is -3 positive? " + isPositive.test(-3));
    // Example 2: using the Function functional interface
    **Function incrementByOne = x -> x + 1;**
    System.out.println("Increment 5 by one: " + incrementByOne.apply(5));
    // Example 3: using the Consumer functional interface
    **Consumer printString = x -> System.out.println(x);**
    printString.accept("Hello, world!");
}
}

In this example, three different functional interfaces are used from the java.util.function package: Predicate, Function, and Consumer. These are the functions of the three functional interfaces:

a) The Predicate functional interface takes a single input and returns a boolean value.

b) The Function functional interface takes a single input and returns a single output.

c) The Consumer functional interface takes a single input and performs some action but returns no value.

Output:

Is 5 positive? true
Is -3 positive? false
Increment 5 by one: 6
Hello, world!

3) forEach() Method

The forEach() method is a default method in the Iterable interface. It accepts a Consumer representing the action to be performed on each element of the Iterable. Collections that implement Iterable can therefore use forEach() to process their elements.

forEach() Method Explained

Syntax:

arrayList.forEach(element -> {
   // code to be executed for each element
});

In the above syntax, ‘arrayList’ is the name of the ArrayList that you want to iterate over, and ‘element’ is a parameter that represents each element in the ArrayList. Here is an example of using the forEach() method:

import java.util.Arrays;
import java.util.List;
public class Example
{
public static void main(String[] args)
{
List names = Arrays.asList("Alice", "Bob", "Charlie",
"Dave");
// Print out each name in the list using forEach()
names.forEach(name -> System.out.println(name));
}
}

In this example, a list of names is created, and the forEach() method is used to print out each name in the list. The forEach() method takes a lambda expression as an argument, which specifies what to do with each element in the list.

Output:

Alice
Bob
Charlie
Dave

4) Default Methods

Default methods are interface methods declared with the default keyword and include an implementation. They allow interfaces to provide method implementations while maintaining compatibility with existing implementing classes.

Syntax:

public interface MyInterface
{
// abstract method
void myMethod();
// default method
default void myDefaultMethod()
{
// default implementation
}
}

Here is an example of using default methods in Java 8. In this example, an interface 'Vehicle' declares two methods' start()' and 'stop()', which are implemented by the 'Car' class. The Vehicle interface also has a default method 'horn()', which is implemented with a 'System.out.println()' statement that prints "Beep beep!" to the console.

interface Vehicle
{
void start();
void stop();
default void horn()
{
System.out.println("Beep beep!");
}
}
class Car implements Vehicle
{
public void start()
{
System.out.println("Starting car engine...");
}
public void stop()
{
System.out.println("Stopping car engine...");
}
}
public class Main { public static void main(String[] args)
{
Car myCar = new Car();
myCar.start();
myCar.horn();
myCar.stop();
}
}

Output:

Starting car engine...
Beep beep!
Stopping car engine...

Learn how to make an enterprise application for an organisation with the Introduction To Java EE Training – Register now!

5) Stream Application Programming Interface (Stream API)

Functional-based operations on elements are carried out by the Stream API. The Stream API enables functional-style operations on sequences of elements, such as filtering, mapping, sorting, and reducing data. To understand the Stream API, it is imperative to learn about the Stream. A stream does not store data itself. Instead, it processes elements from a source through a pipeline of operations. Intermediate operations such as filter() and map() are lazy and are evaluated when a terminal operation is invoked.

Streams can be created from collections using stream(), from values using Stream.of(), and through several other factory methods. This is an example of creating a Stream using the 'stream()' method on a List interface:

List names = Arrays.asList("John", "Mike", "Sarah");
Stream stream = names.stream();

The second part is performing operations on a Stream. You can conduct intermediate operations (filter(), map(), etc.) and terminal operations (forEach(), reduce(), etc.). Here is an example of using the ‘filter()’ method to filter out names starting with ‘S’:

Stream filteredStream =
    names.stream().filter(name -> !name.startsWith("S"));

Let’s try to understand this better with an example.

Following is an example of using the Java 8 Stream API to filter a list of strings based on a particular condition:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
    public static void main(String[] args) {
        List names = Arrays.asList(
            "Alice", "Bob", "Charlie", "Dave", "Emily", "Frank"
        );
        // Filter names that start with the letter 'E'
        List filteredNames = names.stream()
            .filter(name -> name.startsWith("E"))
            .collect(Collectors.toList());
        System.out.println(filteredNames);
       }
}

Here, a list of strings called ‘names’ is created using the ‘Arrays.asList()’ method. Then, the ‘stream()’ method on the list is used to create a stream of elements. The ‘filter()’ method applies a filter to the stream. Finally, the filtered elements are collected into a new list using collect(Collectors.toList()), which is available in Java 8.

Output:

[Emily]

Trainer’s Pro Tip

Think of a stream as a processing pipeline, not as a container holding data. Build the pipeline with intermediate operations such as filter() and map(), then trigger processing with a terminal operation such as collect(), reduce() or forEach().

6) Java Date-Time API

The Java 8 Date-Time API provides the java.time package and related subpackages for working with dates, times, date-time values, time zones, durations and formatting. Key classes include LocalDate, LocalTime, LocalDateTime, Instant, ZonedDateTime and Duration.

Java Date Time API Explained

The following is an example of using Java Time API to create a date-time object using the current date and time:

import java.time.LocalDateTime;
LocalDateTime now = LocalDateTime.now();

Now let’s look at an example that deals with obtaining the current date and time and formatting it using a ‘DateTimeFormatter’.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class JavaTimeExample
{
public static void main(String[] args)
{
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedDateTime = now.format(formatter); System.out.println("Current date and time: " + formattedDateTime);
}
}

In the above example, a ‘LocalDateTime’ object representing the current date and time is created using the ‘now()’ method. Then, a ‘DateTimeFormatter’ object with a specific pattern is created using the ‘ofPattern()’ method. Finally, the LocalDateTime object is formatted using the format() method and the DateTimeFormatter, producing the formatted date-time string.

Example Output:

Current date and time: 2023-03-14 14:05:23

7) Nashorn

Nashorn is a JavaScript engine introduced with JDK 8. It allows JavaScript code to be executed from Java applications through the Java Scripting API or through the jjs command-line tool.

Note: Nashorn was deprecated in JDK 11 and later removed from the JDK in JDK 15. Therefore, this example applies specifically to Java 8-era JDKs and other releases that still included Nashorn.

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class NashornExample
{
public static void main(String[] args)
{
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("nashorn");
try
{
engine.eval("print('Hello, World!')");
} catch (ScriptException e)
{
e.printStackTrace();
}
}
}

In the above example, the necessary classes for using Nashorn, which are 'ScriptEngine', 'ScriptEngineManager', and 'ScriptException' are imported. Next, a new 'ScriptEngineManager' object, responsible for managing the script engines, is created.

The 'nashorn' script engine is retrieved by calling the 'getEngineByName' method on the 'ScriptEngineManager' object. Then, a simple JavaScript program is executed that prints "Hello, World!" to the console by calling the eval() method on the ScriptEngine object.

Output:

Hello, World!

Quick Knowledge Check

You need to filter a collection and transform selected elements. Which Java 8 feature would you consider?

A) Default methods
B) Stream API
C) Nashorn
D) LocalDateTime
Answer: B – Stream API, because operations such as filter() and map() can be combined into a processing pipeline.

Gain skills to deploy Java web applications on servers or cloud platforms with our Web Development Using Java Training – Join now!

Vishnu Sankar
Vishnu Sankar

Senior Content Writer

Vishnu Sankar is a Senior Content Writer with 5+ years of experience across content development, software development, web development and system administration. His technical background and professional training support his expertise in IT and Tech, while his extensive research and writing experience covers Project Management and Health and Safety.

View Detail icon
cross

BIGGEST HALLOWEEN SALE!

halloween-sale GET UPTO 40% OFF!

* WHO WILL BE FUNDING THE COURSE?

close

close

Thank you for your enquiry!

One of our training experts will be in touch shortly to go over your training requirements.

close

close

Press esc to close

close close

Back to course information

Thank you for your enquiry!

One of our training experts will be in touch shortly to go overy your training requirements.

close close

Thank you for your enquiry!

One of our training experts will be in touch shortly to go over your training requirements.