JAVA 8 Main Features (Top 10) + Examples

Java 8 provides multiple good features for Java Programming. In this article we will learn Java 8 core features along with working examples. Java 8 provides good features to make developers much more productive.

Before starting I have very Basic Quesitons:

  • Why Functional Programming in Object-Oriented Programming?

Since, Functional Programming has no state, there’s no blocking or concurrency issue. Functional Programming removes moving parts and optimizes operation for concise and error-free code. Wouldn’t adopting this particular way of coding (no state and no mutable variables) in OOP make code more effective and efficient?

  • What is the role of Anonymous Class, Functional Interface, and Lambda Expression?
  • In simple words, an Anonymous Class allows us to declare and instantiate a class at the same time.
  • Functional Interface is simply an interface that has exactly one abstract method.
  • When implementing a functional interface, we can use Lambda Expression. Lambda Expression makes my code cleaner.
What is a Functional Interface in Java 8?

A functional interface in Java is an interface that contains only a single abstract (unimplemented) method. A functional interface can contain default and static methods which do have an implementation, in addition to the single unimplemented method.

What is Method References in Java 8?

The :: operator is used in method reference to separate the class or object from the method name. In simple words, it is a shorthand notation of a lambda expression to call a method.

What is basic difference between an Abstract Class and Java 8 Functional Interface?

An interface and an abstract class is almost similar except that we can create constructor in the abstract class whereas we can’t create constructor in an interface.

Lambda expression vs method in Java 8?

A method in java has name, parameter list, body and its return type. While, lambda expression only has a body and a parameter list. It makes my code cleaner.

  • Here is the list of Java 8 core features along with working examples:

1. Why Lambda Expressions? How we can use Lambda Expressions in our application?

I like using Lambda Expression because it makes my code cleaner. Lambda expression is a new feature which is introduced in Java 8. A lambda expression is an anonymous function. A function that doesn’t have a name and doesn’t belong to any class.

Basically, a method (or function) in Java has these main parts:

  • Name
  • Parameter list
  • Body
  • Return type

A Lambda Expression in Java has these main parts:

  • No name – function is anonymous, so we don’t care about the name
  • Parameter list
  • Body – This is the main part of the function.
  • No return type – The java 8 compiler is able to infer the return type by checking the code. You need not to mention it explicitly.
//Syntax of lambda expression

(parameter_list) -> {function_body}
  • Note: Before getting into lambdas, we first need to understand Functional Interfaces in Java 8.

The term Java functional interface was introduced in Java 8. A functional interface in Java is an interface that contains only a single abstract (unimplemented) method. A functional interface can contain default and static methods which do have an implementation, in addition to the single unimplemented method. For example, the Runnable interface from package java.lang; is a functional interface because it constitutes only one method i.e. run().

Predefined Functional Interfaces: Java has provided us with several functional interfaces.

  • Supplier<T> represents a supplier, which returns a value, type T.
  • Consumer<T> represents an operation that takes one parameter, type T, and consumes T. It returns void.
  • Function<T, R> represents a function that takes one parameter, type T, and returns a value, type R.
  • Predicate<T> represents a boolean-valued function that takes one parameter, type T, and returns a boolean.
  • BiFunction<T, U, R> represents a function that takes two parameters, type T and type U, and returns a value, type R.

Here is a Java functional interface example:

public interface MyFunctionalInterface {
 public void execute();
}

Only one method is there named execute()

  • Functional Interfaces Can Be Implemented by a Lambda Expression. E.g. Syntax
MyFunctionalInterface lambda = () -> {
 System.out.println("Executing…");
}

Example 1: Java Lambda Expression with no parameter

@FunctionalInterface
interface MyFunctionalInterface {
//A method with no parameter
public String printMessage();
}
public class Example {
public static void main(String args[]) {
// lambda expression
MyFunctionalInterface msg = () -> {
return "Hello Developer";
};
System.out.println(msg.printMessage());
}
}

Output: Hello Developer

Example 2: Java Lambda Expression with single parameter

public class Java8Features {

	@FunctionalInterface
	interface MyFunctionalInterface {

		// A method with a single parameter
		public int incrementTheNumber(int a);
	}

	public static void main(String[] args) {
		
		// lambda expression with a single parameter number

		MyFunctionalInterface f = (number) -> number + 50;
		System.out.println(f.incrementTheNumber(20));
	}

}

Output: 70

Example 3: Java Lambda Expression with Multiple Parameters

@FunctionalInterface
interface MyFunctionalInterface {
	public String strconcat(String str1, String str2);
}

public class Java8Features {
	public static void main(String args[]) {
		// lambda expression with multiple arguments
		MyFunctionalInterface f = (str1, str2) -> str1 + str2;
		System.out.println(f.strconcat("Hello ", "Developer"));
	}
}

Output: Hello Developer

3. Method References (object::instanceMethod, Class::staticMethod, Class::instanceMethod, Class::new)

It is a shorthand notation of a lambda expression to call a method.

If your lambda expression is like this:
str -> System.out.println(str)

then you can replace it with a method reference like this:
System.out::println
The :: operator is used in method reference to separate the class or object from the method name.
In Java 8 there are 4 types of Method references:

1. Method reference to an instance method of an object – object::instanceMethod
2. Method reference to a static method of a class – Class::staticMethod
3. Method reference to an instance method of an arbitrary object of a particular type – Class::instanceMethod
4. Method reference to a constructor – Class::new


public static class Car {
    public static Car create( final Supplier< Car > supplier ) {
        return supplier.get();
    }              
        
    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }
        
    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }
        
    public void repair() {   
        System.out.println( "Repaired " + this.toString() );
    }
}

Examples:

1. Method reference to an instance method of an object::instanceMethod

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

2. Method reference to a static method of a class – Class::staticMethod

cars.forEach( Car::collide );

3. Method reference to an instance method of an arbitrary object of a particular type – Class::instanceMethod

cars.forEach( Car::repair );

4. Method reference to a constructor – Class::new

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

4. Difference between Default method and Static method in Functional Interface? How we can use these?

  • Default method in Interface: The method newMethod() in MyFunctionalInterface is a default method, which means we need not to implement this method in the implementation class Example. This way we can add the default methods to existing interfaces without bothering about the classes that implements these interfaces.
@FunctionalInterface
interface MyFunctionalInterface {
	/*
	 * This is a default method so we need not to implement this method in the
	 * implementation classes
	 */
	default void newMethod() {
		System.out.println("Newly added default method");
	}

	/*
	 * Already existing public and abstract method We must need to implement this
	 * method in implementation classes.
	 */
	void existingMethod(String str);
}

public class DefaultMethodExample implements MyFunctionalInterface {
	// implementing abstract method
	public void existingMethod(String str) {
		System.out.println("Existing Method String is: " + str);
	}

	public static void main(String[] args) {
		DefaultMethodExample obj = new DefaultMethodExample();
		// calling the default method of interface
		obj.newMethod();
		// calling the abstract method of interface
		obj.existingMethod("Default Method Test.");
	}
}


Output:
Newly added default method
Existing Method String is: Default Method Test.
  • Static method in Interface: As mentioned above, the static methods in MyFunctionalInterface are similar to default method so we need not to implement them in the implementation classes. We can safely add them to the existing interfaces without changing the code in the implementation classes. Since these methods are static, we cannot override them in the implementation classes.
@FunctionalInterface
interface MyFunctionalInterface{  
	    /* This is a default method so we need not
	     * to implement this method in the implementation 
	     * classes  
	     */
	    default void newMethod(){  
	        System.out.println("Newly added default method");  
	    }  
	    
	    /* This is a static method. Static method in interface is
	     * similar to default method except that we cannot override 
	     * them in the implementation classes.
	     * Similar to default methods, we need to implement these methods
	     * in implementation classes so we can safely add them to the 
	     * existing interfaces.
	     */
	    static void anotherNewMethod(){
	    	System.out.println("Newly added Static Method");
	    }
	    /* Already existing public and abstract method
	     * We must need to implement this method in 
	     * implementation classes.
	     */
	    void existingMethod(String str);  
	}  
	public class StaticMethodExample implements MyFunctionalInterface{ 
		// implementing abstract method
	    public void existingMethod(String str){           
	        System.out.println("Existing Method String is: "+str);  
	    }  
	    public static void main(String[] args) {  
	    	StaticMethodExample obj = new StaticMethodExample();	    	
	    	//calling the default method of interface
	        obj.newMethod();     
	        //calling the static method of interface
	        MyFunctionalInterface.anotherNewMethod();
	        //calling the abstract method of interface
	        obj.existingMethod("Static Method Test."); 
	  
	    }  
	}

Output:
Newly added default method
Newly added Static Method
Existing Method String is: Static Method Test.

5. How to solve the Diamond problem using default methods in Java?

  • The multiple inheritance problem can occur, when we have two interfaces with the default methods of same signature. For example.
interface MyFunctionalInterface {

	default void newMethod() {
		System.out.println("Newly added default method");
	}

	void existingMethod(String str);
}

interface MyFunctionalInterface2 {

	default void newMethod() {
		System.out.println("Newly added default method");
	}

	void disp(String str);
}

public class DiamondProblemExample implements MyFunctionalInterface, MyFunctionalInterface2 {
	// implementing abstract methods
	public void existingMethod(String str) {
		System.out.println("Existig Method String is: " + str);
	}

	public void disp(String str) {
		System.out.println("Disp Method String is: " + str);
	}

	public static void main(String[] args) {
		DiamondProblemExample obj = new DiamondProblemExample();

		// calling the default method of interface
		obj.newMethod();

	}
}


Output: Compilation Error: Duplicate default methods named newMethod with the parameters () and () are inherited from the types MyFunctionalInterface2 and MyFunctionalInterface
  • To solve this problem, we can implement this method in the implementation class like this:
interface MyFunctionalInterface {

	default void newMethod() {
		System.out.println("Newly added default method");
	}

	void existingMethod(String str);
}

interface MyFunctionalInterface2 {

	default void newMethod() {
		System.out.println("Newly added default method");
	}

	void disp(String str);
}

public class DiamondProblemExample implements MyFunctionalInterface, MyFunctionalInterface2 {
	// implementing abstract methods
	public void existingMethod(String str) {
		System.out.println("Existing Method String is: " + str);
	}

	public void disp(String str) {
		System.out.println("Disp Method String is: " + str);
	}

	// Implementation of duplicate default method
	public void newMethod() {
		System.out.println("Implementation of default method");
	}

	public static void main(String[] args) {
		DiamondProblemExample obj = new DiamondProblemExample();

		// calling the default method of interface
		obj.newMethod();

	}

}

Output:
Implementation of default method

6. Optional Class Example – How we can handle an empty String in our application to avoid NullPointerException?

Optional Class – java.util package – This class is introduced to avoid NullPointerException that we frequently encounters if we do not perform null checks in our code. Optional is just a container: it can hold a value of some type T or just be null.

  • Example: Without using Optional class
public class OptionalClassExample {
	public static void main(String[] args) {
		// String array of length 5
		String[] str = new String[5];
		// Getting the substring
		String str2 = str[4].substring(1, 3);
		// Displaying substring
		System.out.print(str2);
	}

}

Output
Exception in thread "main" java.lang.NullPointerException
  • Example: Using Optional class
import java.util.Optional;
public class OptionalClassExample {
	public static void main(String[] args) {
		// String array of length 5
		String[] str = new String[5];
		Optional<String> isNull = Optional.ofNullable(str[4]);
		if (isNull.isPresent()) {
			// Getting the substring
			String str2 = str[4].substring(1, 3);
			// Displaying substring
			System.out.print(str2);
		} else {
			// else part
			System.out.println("Cannot get the substring from an empty string");
		}
	}
}


Output
Cannot get the substring from an empty string

7. How to Filter null values from a stream using Stream APIs?

  • Example: A stream with null values
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {

	public static void main(String[] args) {

		List<String> list = Arrays.asList("Java8", "StreamAPI", null, "Test", null);
		List<String> result = list.stream().collect(Collectors.toList());
		result.forEach(System.out::println);
	}

}

Output
Java8
StreamAPI
null
Test
null
  • Example: A stream without null values
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {

	public static void main(String[] args) {

		List<String> list = Arrays.asList("Java8", "StreamAPI", null, "Test", null);
		List<String> result = list.stream().filter(str -> str != null).collect(Collectors.toList());
		result.forEach(System.out::println);
	}

}

Output
Java8
StreamAPI
Test

8. What is difference between an Abstract Class and a Functional Interface?

After having default and static methods inside the interface, we think about the need of abstract class in Java. An interface and an abstract class is almost similar except that you can create constructor in the abstract class whereas you can’t do this in interface.

abstract class AbstractClass{    
    public AbstractClass() {        // constructor    
        System.out.println("You can create constructor in abstract class");    
    }    
    abstract int add(int a, int b); // abstract method    
    int sub(int a, int b){      // non-abstract method    
        return a-b;    
    }    
    static int multiply(int a, int b){  // static method    
        return a*b;    
    }    
}    
public class AbstractTest extends AbstractClass{    
    public int add(int a, int b){        // implementing abstract method    
        return a+b;    
    }    
    public static void main(String[] args) {    
        AbstractTest a = new AbstractTest();    
        int result1 = a.add(20, 10);    // calling abstract method    
        int result2 = a.sub(20, 10);    // calling non-abstract method    
        int result3 = AbstractClass.multiply(20, 10); // calling static method    
        System.out.println("Addition: "+result1);    
        System.out.println("Substraction: "+result2);    
        System.out.println("Multiplication: "+result3);    
    }    
}    

Result (Output):

You can create constructor in abstract class
Addition: 30
Substraction: 10
Multiplication: 200

9. How we can implement Base64 Encode Decode functionality in Java 8?

package com.the.basic.tech.info.java8.base64;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
 
public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";
        
        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );
        
        final String decoded = new String( 
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}

The console output from program run shows both encoded and decoded text:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

10. New Features in Java runtime (JVM) – Metaspace (JEP 122)

The PermGen space is gone and has been replaced with Metaspace (JEP 122). The JVM options -XX:PermSize and –XX:MaxPermSize have been replaced by -XX:MetaSpaceSize and -XX:MaxMetaspaceSize respectively.

Have a great day. Happy learning 🙂