Java 8 Key Concepts: Functional Interfaces

Saurabh Kundu
5 min readApr 14, 2023

--

This is part one of a four — part series exploring the important concepts in Java 8.

Java Logo
istockphoto.com

Java 8 revolutionised the world of Java programming with groundbreaking features that changed the way we write code forever. With improved readability, performance, and maintainability, these features have become essential tools for Java developers looking to take their skills to the next level.

Whether you’re a seasoned developer looking to brush up on your Java 8 knowledge or a newcomer to the language, this blog post has everything you need. I’ve compiled a one-stop guide to help you master the most important Java 8 concepts, from streams and lambdas to functional interfaces and method references.

But this isn’t just another technical guide — I’ve designed it to be an engaging, easy-to-follow resource that you can use as a reference while coding, or as a comprehensive review before an interview. Plus, with real-world examples, you’ll be able to see how these concepts can be applied in the real world.

So if you’re ready to take your Java skills to the next level, join us as we explore the groundbreaking features of Java 8!

Topics that we’ll discuss over a 4 part series:

  1. Functional Interfaces
  2. Lambdas
  3. Stream API
  4. Optional

Let’s first understand what is Functional Programming and it’s benefits.

Functional Programming

It is a programming paradigm that focuses on the use of functions to solve problems and build software systems. In this paradigm, functions are treated as first-class citizens, which means they can be passed as arguments to other functions, returned as results from functions, and stored in variables. This enables the creation of higher-order functions, which are functions that take other functions as input or output.

The benefits of functional programming include:

  • Code that is easier to reason about and test
  • Better support for parallel and concurrent programming
  • Increased modularity and code reuse due to the use of higher-order functions and functional abstractions

Functional programming is a powerful paradigm that is gaining in popularity due to its ability to solve complex problems with elegant and concise code.

With this in mind, now let’s dive into the concepts of the Functional Interfaces.

Functional Interfaces

In Java, a functional interface is an interface that has only one abstract method.

The Functional Interface should satisfy the following a.k.a Functional Interface Contract:

  • It should have one and only one abstract method
  • It can have any number of static and default methods
  • It may or may not be annotated with @FunctionalInterface annotation

Abstract method: A method with a definition but no implementation.

Static methods: A method that belongs to a class rather than an instance of the class.

Default methods: A method defined in an interface that provides a default implementation for the method i.e a class that implements the interface doesn’t have to provide its own implementation of the method if it doesn’t want to.

Valid examples:

public interface SampleFunctionalInterface {

void helloWorld();
}

This is a normal interface in Java, but has only one abstract method thus it’s a functional interface.

public interface SampleFunctionalInterface {

void helloWorld();

default void helloPeople() {
System.out.println("hello people, what's up ? ");
}

static void helloAll() {
System.out.println("Hello All, what's up ? ");
}
}

This interface has only one abstract method and has a default and static methods, thus it’s a functional interface.

Invalid examples:

public interface NotAFunctionalInterface {

void helloWorld();

void helloGalaxy();

}

This interface has two abstract methods (breaking the definition/contract), this can’t be considered as a functional Interface.

@FunctionalInterface — It’s an informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.

The compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not it’s annotated with @FunctionalInterface .

If an interface marked with @FunctionalInterface has more than one abstract method, there will be compilation error.

Example:

@FunctionalInterface
public interface SampleFunctionalInterface {

void helloWorld();
}

This is an example of a valid Functional Interface as it’s annotated with @FunctionalInterface and satisfies the definition i.e has exactly one abstract method.

@FunctionalInterface
public interface SampleFunctionalInterface {

void helloWorld();
void helloGalaxy();
}

This Functional Interface will give compilation error as it’s annotated with @FunctionalInterface and it violates the definition by having two abstract methods.

compiler error: Multiple non-overriding abstract methods found in interface com.example.SampleFunctionalInterface.

Note: It’s always a good practice to annotate a Functional Interface with @FunctionalInterface annotation.

Functional interfaces are commonly used with lambda expressions to provide a concise and expressive way to write functional code. For example, the java.util.function package provides several built-in functional interfaces that can be used with lambda expressions.

Let’s take a look at the some of the built in Functional Interfaces provided by Java. We’ll discuss their uses later when we discuss about Lambdas in detail.

Comparator

Comparator is used to compare two objects based on certain criteria.

@FunctionalInterface
public interface Comparator<T> {

int compare(T o1, T o2);
}

Runnable

Runnable interface should be implemented by any class whose instances are intended to be executed by a thread

@FunctionalInterface
public interface Runnable {

public abstract void run();
}

Callable

Callable interface should be implemented by any class whose instances are intended to be executed by a thread and returns a result or may throw an exception

@FunctionalInterface
public interface Callable<V> {

V call() throws Exception;
}

Consumer

Represents an operation that accepts a single input argument and returns no result.

@FunctionalInterface
public interface Consumer<T> {

void accept(T t);
}

BiConsumer

Represents an operation that accepts two input arguments and returns no result.

@FunctionalInterface
public interface BiConsumer<T, U> {

void accept(T t, U u);
}

Predicate

Represents an operation that accepts one input arguments and returns a boolean.

@FunctionalInterface
public interface Predicate<T> {

boolean test(T t);
}

BiPredicate

Represents an operation that accepts two input arguments and returns a boolean.

@FunctionalInterface
public interface BiPredicate<T, U> {

boolean test(T t, U u);
}

Function

Represents a function that accepts one argument and produces a result.

@FunctionalInterface
public interface Function<T, R> {

R apply(T t);
}

BiFunction

Represents a function that accepts two arguments and produces a result.

@FunctionalInterface
public interface BiFunction<T, U, R> {

R apply(T t, U u);
}

Supplier

Represents a function that accepts no arguments and returns a result.

@FunctionalInterface
public interface Supplier<T> {

T get();
}

Summary:

In this blog post, we’ve explored the definition of functional interfaces, why they’re important, and how to recognise them in Java. But we’re just getting started. In the next part of this series, we’ll show you how to use functional interfaces effectively to write powerful lambda functions.

Don’t want to miss out on the rest of the series? Subscribe to our newsletter or connect with us on LinkedIn. We’ll keep you up-to-date on the latest Java tips, tricks, and best practices so you can stay ahead of the game.

Thanks for reading, and we’ll see you in the next part !

--

--