Functional Interfaces of Java 8

In this article, we will learn about the functional interfaces of Java 8. Java has always been an object-oriented programming language. This means that Java programming is all about objects (except for a few primitive types for simplicity). Not only do we have functions in Java, but they are also part of Class and we need to use the class/object to call each function.

If we look at other programming languages like C ++, JavaScript; They are called functional programming languages because we can write functions and use them when we need them. Some of these languages support both object-oriented programming and functional programming.

Being object oriented isn’t bad, but it does add a lot of verbosity to the program. For example, let’s say we need to create an instance of Runnable. We usually do this with anonymous classes like below.

Runnable r = new Runnable(){
			@Override
			public void run() {
				System.out.println("My Runnable");
			}};

If you look at the code above, the real useful part is the code in the run () method. The rest of the code is due to the way Java programs are structured.

Java 8 functional interfaces and lambda expressions help us write smaller, cleaner code by removing a lot of boilerplate code.

Java 8 functional interface

An interface with absolutely one abstract method is known as a functional interface. The @FunctionalInterface annotation was added so that we can mark an interface as a functional interface.

It is not necessary to use it, but it is better to use it with a practical interface so as not to accidentally add additional methods. If the interface is interpreted using the @FunctionalInterface interpretation and we try to use more than one abstract method, a defined error is returned.

The biggest advantage of Java 8’s functional interface is that we allow Lambda Feedback to use them quickly and avoid cumbersome anonymous class processes.

The Java 8 Collection API has been rewritten and a new Stream API has been introduced that uses many functional interfaces.Java 8 has many functional interfaces that are defined in the java.util.function package. Some of the useful functional interfaces in Java 8 are Consumer, Vendor, Function, and Predicate.

See the sample Java 8 flow for more details.

java.lang.Runnable is a great example of a working interface with a single abstract method run().

The following code snippet provides some guidelines for functional interfaces:


interface Foo { boolean equals(Object obj); }
// Not functional because equals is already an implicit member (Object class)

interface Comparator<T> {
 boolean equals(Object obj);
 int compare(T o1, T o2);
}
// Functional because Comparator has only one abstract non-Object method

interface Foo {
  int m();
  Object clone();
}
// Not functional because method Object.clone is not public

interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<String> arg); }
interface Z extends X, Y {}
// Functional: two methods, but they have the same signature

interface X { Iterable m(Iterable<String> arg); }
interface Y { Iterable<String> m(Iterable arg); }
interface Z extends X, Y {}
// Functional: Y.m is a subsignature & return-type-substitutable

interface X { int m(Iterable<String> arg); }
interface Y { int m(Iterable<Integer> arg); }
interface Z extends X, Y {}
// Not functional: No method has a subsignature of all abstract methods

interface X { int m(Iterable<String> arg, Class c); }
interface Y { int m(Iterable arg, Class<?> c); }
interface Z extends X, Y {}
// Not functional: No method has a subsignature of all abstract methods

interface X { long m(); }
interface Y { int m(); }
interface Z extends X, Y {}
// Compiler error: no method is return type substitutable

interface Foo<T> { void m(T arg); }
interface Bar<T> { void m(T arg); }
interface FooBar<X, Y> extends Foo<X>, Bar<Y> {}
// Compiler error: different signatures, same erasure

Lambda Expression

Lambda Expression is the means by which we can visualize functional programming in the object-oriented Java world. Objects are the basis of the Java programming language and without an object, we can never have a function, which is why the Java language only supports the use of lambda expressions with functional interfaces.

Since in the functional interfaces, there is only one abstract function, So there is no confusion when applying the lambda expression to the method. The syntax for lambda expressions is (argument) -> (text). Now let’s see how we can write the above anonymous runnable with a lambda expression.

Runnable r1 = () -> System.out.println("My Runnable");

Let’s try to figure out what’s going on in the lambda expression above.

  • Runnable is a functional interface, so we can use a lambda expression to create its instance.
  • Since the run () method takes no arguments, our lambda expression doesn’t have any arguments either.
  • Just like with if-else blocks, we can avoid braces ({}) because we have only one statement in the body of the method. For any statements, we should use braces like any other method.

Why do we need a lambda expression?

1. Reduced lines of code

One of the obvious advantages of using the lambda expression is that it reduces the amount of code. We have already seen how easily we can create instances of a functional interface using a lambda expression instead of an anonymous class.

2. Support for sequential and parallel execution

Another benefit of using the lambda expression is that we can take advantage of the Stream API’s support for sequential and parallel operations.
To explain this, let’s take a simple example where we need to write a method to test whether a passed number is prime or not.
Traditionally, we would write its code like this. The code isn’t fully optimized, but it’s fine for example purposes, so please give in to me.

//Traditional approach
private static boolean isPrime(int number) {		
	if(number < 2) return false;
	for(int i=2; i<number; i++){
		if(number % i == 0) return false;
	}
	return true;
}

The problem with the above code is that it is inherently sequential. If the number is very large, it will take a long time. Another problem with the code is that there are so many exit points and it is unreadable. Let’s see how we can write the same method using lambda expressions and the Stream API.

//Declarative approach
private static boolean isPrime(int number) {		
	return number > 1
			&& IntStream.range(2, number).noneMatch(
					index -> number % index == 0);
}

IntStream is a sequence of primitive elements of int value that support sequential and parallel aggregation operations. This is the primitive int specialization of Stream.

You can also write a method like this for better readability.

private static boolean isPrime(int number) {
	IntPredicate isDivisible = index -> number % index == 0;
	
	return number > 1
			&& IntStream.range(2, number).noneMatch(
					isDivisible);
}

If you are not familiar with IntStream, the range() method returns an IntStream sequentially from startInclusive (inclusive) to endExclusive (exclusive) in an incremental step of 1.

The noneMatch() method returns if nothing in this stream matches the specified predicate. It cannot evaluate the predicate for all elements unless it is needed to determine the result.

3. Switch from behaviors to methods

Let’s look at a simple example of how we can use lambda expressions to pass the behavior of a method. Let’s say we need to write a method to sum the numbers in a list when they meet certain criteria. Below we can use Predicate and write a method like.

public static int sumWithCondition(List<Integer> numbers, Predicate<Integer> predicate) {
	    return numbers.parallelStream()
	    		.filter(predicate)
	    		.mapToInt(i -> i)
	    		.sum();
	}

Example of use:

//sum of all numbers
sumWithCondition(numbers, n -> true)
//sum of all even numbers
sumWithCondition(numbers, i -> i%2==0)
//sum of all numbers greater than 5
sumWithCondition(numbers, i -> i>5)

4. More efficiency with laziness

Another benefit of using the lambda expression is lazy evaluation, which means we need to write a method to find the maximum odd number in the range 3 to 11 and return its square.

Usually, we will write the code for this method like this:

private static int findSquareOfMaxOdd(List<Integer> numbers) {
		int max = 0;
		for (int i : numbers) {
			if (i % 2 != 0 && i > 3 && i < 11 && i > max) {
				max = i;
			}
		}
		return max * max;
	}

The above program will still run in sequential order, but we can use the Stream API to achieve this and take advantage of laziness search. Let’s see how we can functionally rewrite this code using the Stream API and lambda expressions.

public static int findSquareOfMaxOdd(List<Integer> numbers) {
		return numbers.stream()
				.filter(NumberTest::isOdd) 		//Predicate is functional interface and
				.filter(NumberTest::isGreaterThan3)	// we are using lambdas to initialize it
				.filter(NumberTest::isLessThan11)	// rather than anonymous inner classes
				.max(Comparator.naturalOrder())
				.map(i -> i * i)
				.get();
	}

	public static boolean isOdd(int i) {
		return i % 2 != 0;
	}
	
	public static boolean isGreaterThan3(int i){
		return i > 3;
	}
	
	public static boolean isLessThan11(int i){
		return i < 11;
	}

If you’re surprised by the colon operator (::) it was introduced in Java 8 and is used for method references. The Java compiler ensures that the arguments are assigned to the called method. It is the short form of the lambda expressions i -> isGreaterThan3(i) or i -> NumberTest.isGreaterThan3(i).

Lambda Expression Examples

Below I provide some code snippets for lambda expressions with little comments that explain them.

() -> {}                     // No parameters; void result

() -> 42                     // No parameters, expression body
() -> null                   // No parameters, expression body
() -> { return 42; }         // No parameters, block body with return
() -> { System.gc(); }       // No parameters, void block body

// Complex block body with multiple returns
() -> {
  if (true) return 10;
  else {
    int result = 15;
    for (int i = 1; i < 10; i++)
      result *= i;
    return result;
  }
}                          

(int x) -> x+1             // Single declared-type argument
(int x) -> { return x+1; } // same as above
(x) -> x+1                 // Single inferred-type argument, same as below
x -> x+1                   // Parenthesis optional for single inferred-type case

(String s) -> s.length()   // Single declared-type argument
(Thread t) -> { t.start(); } // Single declared-type argument
s -> s.length()              // Single inferred-type argument
t -> { t.start(); }          // Single inferred-type argument

(int x, int y) -> x+y      // Multiple declared-type parameters
(x,y) -> x+y               // Multiple inferred-type parameters
(x, final y) -> x+y        // Illegal: can't modify inferred-type parameters
(x, int y) -> x+y          // Illegal: can't mix inferred and declared types

Method and constructor references

A constructor reference is similarly used to refer to a constructor without creating a new instance of the named class or array type.

Examples of method and constructor references:

System::getProperty
System.out::println
"abc"::length
ArrayList::new
int[]::new

That’s it for Java 8 Functional Interfaces and Lambda Expression Tutorial. I would highly recommend considering how to use it as this syntax is new to Java and it will take some time to understand.

You should also take a look at the features of Java 8 to learn more about all the improvements and changes in the Java 8 version.

Next Articles

1. Java 8 Features 

Please share this post:
Posts created 57

Ask a Question

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

%d bloggers like this: