Prominent Java 8 Features With Code Examples

By Vijay

By Vijay

I'm Vijay, and I've been working on this blog for the past 20+ years! I’ve been in the IT industry for more than 20 years now. I completed my graduation in B.E. Computer Science from a reputed Pune university and then started my career in…

Learn about our editorial policies.
Updated March 7, 2024

A Comprehensive List And Explanation Of All The Prominent Features Introduced In Java 8 Release With Examples:

Java 8 release from Oracle was a revolutionary release of the world’s #1 development platform. It included a huge upgrade to the Java programming model as a whole along with the evolution of the JVM, Java language, and libraries in a coordinated manner.

This release included several features for Ease of use, Productivity, Improved Polyglot Programming, Security, and Overall improved performance.

Java 8 Features

Features Added To Java 8 Release

Among the major changes, the following are the notable features that were added to this release.

  • Functional Interfaces and Lambda Expressions
  • forEach() method in Iterable interface
  • Optional class,
  • default and static methods in Interfaces
  • Method references
  • Java Stream API for Bulk Data Operations on Collections
  • Java Date Time API
  • Collection API improvements
  • Concurrency API improvements
  • Java IO improvements
  • Nashorn JavaScript engine
  • Base64 Encode Decode
  • Miscellaneous Core API improvements

In this tutorial, we will discuss each of these features briefly and try to explain each of them with the help of simple and easy examples.

Functional Interfaces And Lambda Expressions

Java 8 introduces an annotation known as @FunctionalInterface that is usually for compiler level errors. It is typically used when the interface you are using violates the contracts of functional interface.

Alternatively, you can call a functional interface as SAM interface or Single Abstract Method interface. A functional interface allows exactly one “abstract method” as its member.

Below given is an example of Functional Interface:

@FunctionalInterface
public interface MyFirstFunctionalInterface {
    public void firstWork();
}

You can omit the annotation, @FunctionalInterface and your functional interface will still be a valid one. We use this annotation only to inform the compiler that the interface will have a single abstract method.

Note: By definition, default methods are Non-abstract and you can add as many default methods in the functional interface as you like.

Secondly, if an interface has an abstract method that overrides one of the public methods of “java.lang.object” then it is not considered as the interface’s abstract method.

Given below is a valid Functional Interface example.

@FunctionalInterface
public interface FunctionalInterface_one
{
    public void firstInt_method();

    @Override
    public String toString();                //Overridden from Object class

    @Override
    public boolean equals(Object obj);        //Overridden from Object class
}

A Lambda Expression (or function) can be defined as an anonymous function, (a function with no name and an identifier). Lambda Expressions are defined exactly in the place where they are needed, usually as a parameter to some other function.

Suggested reading =>> How to use Lambda Function in Python

From a different perspective, Lambda Expressions express instances of Functional Interfaces (described above). Lambda Expressions implement the only abstract function present in the functional interface and thus implement functional interfaces.

The basic syntax of a Lambda Expression is:

lambda expression syntax

A basic example of the Lambda Expression is:

lambda expression example

The above expression takes two parameters x and y and returns its sum x+y. Based on the data type of x and y, the method can be used multiple times in various places. Thus the parameters x and y will match int or Integer and string, and based on context, it will add two integers (when parameters are int) or concat the two strings (when parameters are a string).

Let’s implement a program that demonstrates Lambda Expressions.

interface MyInterface 
{ 
     void abstract_func(int x,int y); 

     default void default_Fun() 
    { 
         System.out.println("This is default method"); 
    } 
} 

class Main 
{ 
     public static void main(String args[]) 
    { 
        //lambda expression
        MyInterface fobj = (int x, int y)->System.out.println(x+y); 

        System.out.print("The result = ");
        fobj.abstract_func(5,5); 
        fobj.default_Fun();
    } 
}

Output:

lambda expression - output

The above program shows the use of Lambda Expression to add to parameters and displays their sum. Then we use this to implement the abstract method “abstract_fun” which we declared in the interface definition. The result of calling the function “abstract_fun” is the sum of the two integers passed as parameters while calling the function.

We will learn more about Lambda Expressions later in the tutorial.

forEach() Method In Iterable Interface

Java 8 has introduced a “forEach” method in the interface java.lang.Iterable that can iterate over the elements in the collection. “forEach” is a default method defined in the Iterable interface. It is used by the Collection classes that extend the Iterable interface to iterate elements.

The “forEach” method takes the Functional Interface as a single parameter i.e. you can pass Lambda Expression as an argument.

Example of the forEach() method.

importjava.util.ArrayList;  
importjava.util.List;  
public class Main {  
     public static void main(String[] args) {  
        List<String> subList = new ArrayList<String>();  
        subList.add("Maths");  
        subList.add("English");  
        subList.add("French");  
        subList.add("Sanskrit");
        subList.add("Abacus");
        System.out.println("------------Subject List--------------");  
        subList.forEach(sub -> System.out.println(sub));  
  }  
}  

Output:

forEach method output

So we have a collection of subjects i.e. subList. We display the contents of the subList using the forEach method that takes Lambda Expression to print each element.

Optional Class

Java 8 introduced an optional class in the “java.util” package. “Optional” is a public final class and is used to deal with NullPointerException in the Java application. Using Optional, you can specify alternate code or values to run. By using Optional you don’t have to use too many null checks to avoid nullPointerException.

You can use the Optional class to avoid abnormal termination of the program and prevent the program from crashing. The Optional class provides methods that are used to check the presence of value for a particular variable.

The following program demonstrates the use of the Optional class.

import java.util.Optional;   
public class Main{   
 
   public static void main(String[] args) {   
        String[] str = new String[10];   
        Optional<String>checkNull =  
                       Optional.ofNullable(str[5]);   
        if (checkNull.isPresent()) {   
            String word = str[5].toLowerCase();   
            System.out.print(str);   
         } else  
           System.out.println("string is null");   
    }   
}  

Output:

Optional class output

In this program, we use the “ofNullable” property of the Optional class to check if the string is null. If it is, the appropriate message is printed to the user.

Default And Static Methods In Interfaces

In Java 8, you can add methods in the interface that are not abstract i.e. you can have interfaces with method implementation. You can use the Default and Static keyword to create interfaces with method implementation. Default methods mainly enable Lambda Expression functionality.

Using default methods you can add new functionality to your interfaces in your libraries. This will ensure that the code written for the older versions is compatible with those interfaces (binary compatibility).

Let’s understand the Default Method with an example:

import java.util.Optional;   
interface interface_default {
     default void default_method(){
         System.out.println("I am default method of interface");
    }
}
class derived_class implements interface_default{

}
class Main{
   public static void main(String[] args){
        derived_class obj1 = new derived_class();
        obj1.default_method();
    }
}

Output:

Default method - output

We have an interface named “interface_default” with the method default_method() with a default implementation. Next, we define a class “derived_class” that implements the interface “interface_default”.

Note that we have not implemented any interface methods in this class. Then in the main function, we create an object of class “derived_class” and directly call the “default_method” of the interface without having to define it in the class.

This is the use of default and static methods in the interface. However, if a class wants to customize the default method then you can provide its own implementation by overriding the method.

Method References

The Method reference feature introduced in Java 8 is a shorthand notation for Lambda Expressions to call a method of Functional Interface. So each time you use a Lambda Expression to refer a method, you can replace your Lambda Expression with method reference.

Example of Method Reference.

import java.util.Optional;   
interface interface_default {
       void display();
}
class derived_class{

    public void classMethod(){  
            System.out.println("Derived class Method");  
    }
}
class Main{
     public static void main(String[] args){
        derived_class obj1 = new derived_class();
        interface_default  ref = obj1::classMethod; 
        ref.display();
    }
}

Output:

Method Reference output

In this program, we have an interface “interface_default” with an abstract method “display ()”. Next, there is a class “derived_class” which has a public method “classMethod” that prints out a message.

In the main function, we have an object for the class, and then we have a reference to the interface that references a class method “classMethod” through obj1 (class object). Now when the abstract method display is called by interface reference, then the contents of classMethod are displayed.

Java Stream API For Bulk Data Operations On Collections

The Stream API is yet another major change introduced in Java 8. Stream API is used for processing the collection of objects and it supports a different type of iteration. A Stream is a sequence of objects (elements) that allows you to pipeline different methods to produce the desired results.

A Stream is not a data structure and it receives its input from collections, arrays or other channels. We can pipeline various intermediate operations using Streams and the terminal operations return the result. We will discuss stream API in more detail in a separate Java tutorial.

Java Date Time API

Java 8 introduces a new date-time API under the package java.time.

The most important classes among them are:

  • Local: Simplified date-time API with no complexity of timezone handling.
  • Zoned: Specialized date-time API to deal with various timezones.

Dates

Date class has become obsolete in Java 8.

Following are the new classes introduced:

  • The LocalDate class defines a date. It has no representation for time or time-zone.
  • The LocalTime class defines a time. It has no representation for date or time-zone.
  • The LocalDateTime class defines a date-time. It has no representation of a time-zone.

To include time-zone information with date functionality, you can use Lambda that provides 3 classes i.e. OffsetDate, OffsetTime, and OffsetDateTime. Here Timezone offset is represented using another class – “ZoneId”. We will cover this topic in detail in the later parts of this Java series.

Nashorn JavaScript Engine

Java 8 introduced a much-improved engine for JavaScript i.e. Nashorn that replaces the existing Rhino. Nashorn directly compiles the code in memory and then passes the bytecode to JVM thereby improving the performance by 10 times.

Nashorn introduces a new command-line tool – jjs that executes JavaScript code at the console.

Let us create a JavaScript file ‘sample.js’ that contains the following code.

print (‘Hello, World!!’);

Give the following command in the console:

C:\Java\jjs sample.js

Output: Hello, World!!

We can also run JavaScript programs in interactive mode and also provide arguments to the programs.

Base64 Encode Decode

In Java 8 there is inbuilt encode and decode for Base64 encoding. The class for Base64 encoding is java.util.Base64.

This class provides three Base64 encodes and decoders:

  • Basic: In this, the output is mapped to a set of characters between A-Za-z0-9+/. No line feed is added to the output by the encoder and the decoder rejects any character other than the above.
  • URL: Here the output is the URL and filename safe is mapped to the set of characters between A-Za-z0-9+/.
  • MIME: In this type of encoder, the output is mapped to a MIME friendly format.

Collection API Improvements

Java 8 has added the following new methods to the Collection API:

  • forEachRemaining (Consumer action): This is a Default method and it is for the Iterator. It performs the “action” for each of the remaining elements until all the elements are processed or “action” throws an exception.
  • The default method for collection removeIf (Predicate filter): This removes all the elements in the collection that satisfies the given “filter”.
  • Spliterator (): This is a collection method and returns spliterator instance which you can use for traversing the elements in either sequential or parallel fashion.
  • Map collection has replaceAll (), compute() and merge() methods.
  • HashMap class with Key collisions has been improved to enhance performance.

Concurrency API Changes/Enhancements

Following are the important enhancements in Concurrent API:

  • ConcurrentHashMap is enhanced with the following methods:
    1. compute (),
    2. forEach (),
    3. forEachEntry (),
    4. forEachKey (),
    5. forEachValue (),
    6. merge (),
    7. reduce () and
    8. search ()
  • The method “newWorkStealingPool ()” for executors creates a work-stealing thread pool. It uses the available processors as its target parallelism level.
  • Method “completableFuture” is the one that we can complete explicitly (by setting its value and status).

Java IO Improvements

IO improvements done in Java 8 include:

  • Files.list (Path dir): This returns a jlazily populated stream, whose each element is the entry in the directory.
  • Files.lines (Path path): Reads all the lines from a stream.
  • Files.find (): Search for files in the file tree rooted at a given starting file and returns a stream populated by a path.
  • BufferedReader.lines (): Returns a stream with its every element as the lines read from BufferedReader.

Miscellaneous Core API Improvements

We have the following misc API improvements:

  • Static method withInitial (Supplier supplier) of ThreadLocal to create instance easily.
  • The interface “Comparator” is extended with the default and static methods for natural ordering reverse order etc.
  • Integer, Long and Double wrapper classes have min (), max () and sum () methods.
  • Boolean class is enhanced with logicalAnd (), logicalOr () and logicalXor () methods.
  • Several utility methods are introduced in the Math class.
  • JDBC-ODBC Bridge is removed.
  • PermGen memory space is removed.

Conclusion

In this tutorial, we have discussed the major features that were added to the Java 8 release. As Java 8 is a major release from Java, it is important that you know all the features and enhancements that were done as part of this release.

Although the latest Java version is 13, it is still a good idea to get familiar with the Java 8 features. All the features discussed in this tutorial are still present in the latest version of Java and we will discuss them as individual topics later in this series.

We hope this tutorial helped you learn about various Java 8 features!!

Was this helpful?

Thanks for your feedback!

Leave a Comment