What is a Functional Interface in Java ?
- A Functional Interface in java is an interface that containsonly one abstract method. You can create your own functional interface using@FunctionalInterfaceannotation as well.
- It can have multiple default or static methods, but only one abstract method
- Functional interfaces are mainly used to enable Lambda Expressions in Java.
- @FunctionalInterface annotation is optional but recommended
Creating a Functional Interface in Java
We can create a functional interface by using @FunctionalInterface. However, it is optional to use if the interface only contains one abstract method, Functional Interface should follow SAM, which stands for Single Abstract Method principle.
//Example: Simple Functional Interface
@FunctionalInterface
interface MyFunctionalInterface {
void display(); // Single abstract method
}
public class FunctionalInterfaceExample {
public static void main(String[] args) {
// Using Lambda Expression
MyFunctionalInterface obj = () -> System.out.println("Hello from Lambda!");
obj.display();
}
}
Output:
Hello from Lambda!
@FunctionalInterface Annotation
The @FunctionalInterface annotation is not mandatory, but it is recommended because:
✅ It ensures the interface has only one abstract method.
✅ It prevents accidental addition of extra abstract methods.
//Example: Using @FunctionalInterface
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}
// This will work fine
public class TestFunctional {
public static void main(String[] args) {
Calculator sum = (a, b) -> a + b;
System.out.println("Sum: " + sum.add(5, 10));
}
}
Output:
Sum: 15
Default and Static Methods in Functional Interfaces
A functional interface can have multiple default and static methods but only one abstract method.
- Default methods can be overridden
- Static methods are called with the interface name
Example: Default and Static Methods in Functional Interface
@FunctionalInterface
interface MyInterface {
void show(); // Abstract method
// Default method
default void defaultMethod() {
System.out.println("This is a default method");
}
// Static method
static void staticMethod() {
System.out.println("This is a static method");
}
}
public class DefaultStaticExample {
public static void main(String[] args) {
MyInterface obj = () -> System.out.println("Lambda for show() method");
obj.show();
obj.defaultMethod(); // Calling default method
MyInterface.staticMethod(); // Calling static method
}
}
Output:
Lambda for show() method
This is a default method
This is a static method
What are Java's Built-in Functional Interfaces (from java.util.function)
Java provides several built-in functional interfaces in the java.util.function package. Predicate, Consumer, Supplier, Function, Bi-Function are the built in functional interface in Java.
Interface Name | Abstract Method | Description |
---|---|---|
Predicate<T> | boolean test(T t) | Evaluates a condition |
Function<T, R> | R apply(T t) | takes a input of one type and then return a result in another type |
Consumer<T> | void accept(T t) | accept a parameter but does not return anything |
Supplier<T> | T get() | Returns a value without input |
BiFunction<T, U, R> | R apply(T t, U u) | Takes two arguments and returns one |
Predicate Functional Interface
- We can use the functional interface predicate whenever we want to evaluate something.
- It has a test method that returns true or false based on the condition.
Example : filter() method in stream uses predicate functional interface. It expects a predicate as input.
//Predicate – Checks a Condition
import java.util.function.Predicate;
import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
// Sample list of integers
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Predicate to check if a number is even
Predicate isEven = num -> num % 2 == 0;
System.out.println(isEven.test(20));
// Using filter with Predicate to get even numbers
List evenNumbers = numbers.stream()
.filter(isEven) // Predicate applied here
.collect(Collectors.toList());
System.out.println(evenNumbers);
}
}
Output:
true
[2, 4, 6, 8, 10]
Function Functional Inteface
- The
Function<T, R>
functional interface is used to represent a function that accepts one argument of typeT
and returns a result of typeR
. - It is commonly used for operations like transforming or mapping data in functional programming. It has an apply() method.
Example : map method in stream API uses Function. Internally apply method is invoked.
//Using Function to Transform a List of Objects
import java.util.List;
import java.util.stream.Collectors;
import java.util.function.Function;
public class FunctionListExample {
public static void main(String[] args) {
// List of integers
List numbers = List.of(1, 2, 3, 4, 5);
// Function to square a number
Function squareFunction = num -> num * num;
// Using map with the Function to transform the list
List squaredNumbers = numbers.stream()
.map(squareFunction) // Applying the function to each element
//or we can use map(num->num*num)
.collect(Collectors.toList());
System.out.println(squaredNumbers); // Output: [1, 4, 9, 16, 25]
//simple ussage of predicate
Function multiplyByTwo = num -> num * 2;
// Applying the function
System.out.println(multiplyByTwo.apply(5)); // Output: 10
System.out.println(multiplyByTwo.apply(100)); // Output: 200
//apply(): The apply() method is used to apply the function to the given argument (in this case, multiplying by 2).
}
}
Consumer Functional Interface
- The
Consumer<T>
functional interface is part of thejava.util.function
package and represents an operation that takes a single input argument and returns no result. - It’s typically used when you need to perform an action on an object (like printing, modifying, or updating an object), but don’t want to return any value.
- It has an accept() method.
- forEach() method of a list is an example of using Consumer functional interface
//Using Consumer to Print Items in a List
import java.util.List;
import java.util.ArrayList;
import java.util.function.Consumer;
public class ConsumerExample {
public static void main(String[] args) {
// List of integers
List numbers = List.of(1, 2, 3, 4, 5);
// Consumer to print each number
Consumer printConsumer = number -> System.out.println("Number: " + number);
// Applying the Consumer to each element of the list
numbers.forEach(printConsumer); // Output: Number: 1, Number: 2, ..., Number: 5
}
}
//The Consumer interface here takes an Integer and simply prints it to the console.
//The forEach() method of the List is used to apply the Consumer to each element
Supplier Functional Interface
The Supplier<T> functional interface is useful when you need to generate or supply a value without any input. It’s often used in scenarios like lazy initialization, providing default values, or when generating values for streams or other data structures
- The Supplier interface provides a result without any input.
- Supplier<T> has a single abstract method:
- T get(): It supplies a result of type T.
- Example of Supplier is orElseGet() method.
import java.util.function.Supplier;
public class SupplierOptionalExample {
public static void main(String[] args) {
// Supplier to provide a default value
Supplier defaultNameSupplier = () -> "Default Name";
System.out.println(defaultNameSupplier.get());
//output Default Name
}
}
A Supplier<String> is created with the lambda expression () -> “Default Name”. This Supplier will provide the string “Default Name” when its get() method is called.
BiFunction Functional Interface
- The BiFunction<T, U, R> functional interface represents a function that accepts two arguments of types T and U, and returns a result of type R.
- It is part of the java.util.function package in Java 8 and later.
- The BiFunction interface is commonly used in scenarios where you need to process or combine two inputs to produce a result.
- It has apply method : R apply(T t, U u);
Basic Usage of BiFunction Functional Interface
import java.util.function.BiFunction;
public class BiFunctionExample {
public static void main(String[] args) {
// Example 1: Basic BiFunction - Adding two integers
System.out.println("Example 1: Adding two integers");
BiFunction add = (a, b) -> a + b;
System.out.println("Addition of 10 and 20: " + add.apply(10, 20)); // Output: 30
System.out.println();
// Example 2: BiFunction - Concatenating two strings
System.out.println("Example 2: Concatenating two strings");
BiFunction concatenate = (str1, str2) -> str1 + str2;
System.out.println("Concatenation of 'Hello, ' and 'World!': " + concatenate.apply("Hello, ", "World!"));
// Output: Hello, World!
System.out.println();
// Example 3: BiFunction - Performing arithmetic operations (Addition and Multiplication)
System.out.println("Example 3: Performing arithmetic operations");
BiFunction addNumbers = (a, b) -> a + b;
BiFunction multiplyNumbers = (a, b) -> a * b;
// Applying the BiFunctions
System.out.println("Addition of 5 and 3: " + addNumbers.apply(5, 3)); // Output: 8
System.out.println("Multiplication of 5 and 3: " + multiplyNumbers.apply(5, 3)); // Output: 15
System.out.println();
}
}
Output:
Example 1: Adding two integers
Addition of 10 and 20: 30
Example 2: Concatenating two strings
Concatenation of 'Hello, ' and 'World!': Hello, World!
Example 3: Performing arithmetic operations
Addition of 5 and 3: 8
Multiplication of 5 and 3: 15
Functional Interface with Streams
//Example: Filtering List using Predicate
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class FunctionalWithStreams {
public static void main(String[] args) {
List names = Arrays.asList("Alice", "Bob", "Charlie", "Anna");
// Filter names starting with 'A'
Predicate startsWithA = name -> name.startsWith("A");
List filteredNames = names.stream()
.filter(startsWithA)
.collect(Collectors.toList());
System.out.println(filteredNames); // [Alice, Anna]
}
}
✔ Functional Interface + Lambda + Streams = Clean Code
Custom Functional Interface with Generics
We can create a generic, custom functional interface for reusability, In which we can support multiple data types using generics.
//Example: Generic Functional Interface
@FunctionalInterface
interface GenericFunction {
T operate(T a, T b);
}
public class GenericFunctionalInterfaceExample {
public static void main(String[] args) {
GenericFunction add = (a, b) -> a + b;
GenericFunction concatenate = (a, b) -> a + b;
System.out.println("Addition: " + add.operate(5, 10)); // 15
System.out.println("Concatenation: " + concatenate.operate("Hello", " World")); // Hello World
}
}
Functional Interface default Method
The primary benefit of functional interface default methods is that they allow you to add new methods to an interface without breaking the existing implementation of the interface.
Default methods allow you to add new methods to an interface without affecting the classes that implement the interface. Without default methods, adding a new method to an interface would break the existing classes implementing it, as they would be required to provide implementations for the new methods.
Functional Interfaces in Java before Java 8
Before Java 8, Java did not have functional interfaces. Functional interfaces guarantee that only one abstract method is defined within the interface.
Prior to Java 8, we had to implement these interfaces using concrete classes or anonymous inner classes. With the introduction of functional interfaces in Java 8, users can leverage functional programming concepts, including the use of lambda expressions.
Functional Interface vs Interface
There are several Key Differences between normal Interfaces and Java 8+ Functional Interfaces. Here is the table that shows functional interface vs interface feature-wise.
Feature | Before Java 8 | Java 8 and Beyond |
---|---|---|
Purpose | Used for creating abstractions for classes to implement | Used for functional programming and enabling lambda expressions |
Abstract Methods | Multiple abstract methods allowed | Typically one abstract method (single abstract method interface) |
Default Methods | Not allowed | Allowed using the default keyword |
Static Methods | Not allowed in interfaces | Allowed using the static keyword |
Lambda Expressions | Not supported | Supported, allowing a compact way to implement functional interfaces |
@FunctionalInterface | Not used or supported | Can be used to explicitly indicate a functional interface |
Example Usage | Implemented through classes | Used primarily with lambdas and method references |
Functional Interface vs abstract Class
Functional Interfaces are highly suited for scenarios requiring single behavior or operations that can be expressed as functions, especially in functional programming style (e.g., lambda expressions).
Abstract Classes are useful when there is a need for common functionality and shared state across a family of classes, where you can leverage inheritance and constructors.
Each serves a different purpose, so choosing between them depends on your design needs. Below is the table that compares functional interface and abstract class.
Functional Interface vs abstract Class
Feature | Functional Interface | Abstract Class |
---|---|---|
Purpose | Represents a single behavior or function that can be implemented using lambda expressions or method references. | Provides a foundation for other classes to extend, enabling shared functionality and code reuse. |
Number of Abstract Methods | Must contain exactly one abstract method (Single Abstract Method or SAM). | Can contain multiple abstract methods that subclasses must implement. |
Lambda Expression Support | Supported: Functional interfaces can be implemented using lambda expressions or method references. | Not Supported: Abstract classes cannot be instantiated with lambda expressions because they may have multiple abstract methods and require subclassing. |
Methods | Can have default methods (methods with an implementation) and static methods, in addition to the single abstract method. | Can have both abstract methods (without implementation) and concrete methods (with implementation). |
State | Stateless: Functional interfaces cannot have instance variables (no state). They are only concerned with behavior. | Stateful: Abstract classes can have instance variables to store object state, providing a way to maintain state across subclasses. |
Constructors | Cannot have constructors because they do not maintain state. | Can have constructors to initialize object state in the abstract class or its subclasses. |
Inheritance | Can extend other interfaces, but it can only have one abstract method. | Can extend other classes (abstract or concrete) and implement multiple interfaces, allowing more complex inheritance hierarchies. |
Instantiation | Cannot be instantiated directly. They are intended to be implemented using lambda expressions or method references. | Cannot be instantiated directly, but can be instantiated indirectly through concrete subclasses. |
Use Case | Ideal for functional programming tasks such as handling callbacks, event handling, or operations involving lambda expressions and streams. | Best suited for creating a common base class that provides shared functionality and state across related classes. |
Functional Interface use in Java
- A functional interfaceis an interface that has exactly one abstract method. It can have multiple default or static methods, but it is primarily designed to have a single behavior that can be implemented, often using lambda expressions or method references
- It Enables functional programming in Java using lambda expressions & streams
- Functional interfaces are used extensively in the Streams API to perform operations like map, filter, and reduce.
- Functional interfaces can be used for asynchronous programming, where tasks are executed asynchronously. For example, Java’s
CompletableFuture
can work with functional interfaces for callback operations.
Functional Interface Advantages
Simplifies Code: Functional interfaces allow you to use lambda expressions, making the code more concise and readable by eliminating boilerplate code (like anonymous classes).
Promotes Functional Programming: They enable functional programming in Java, allowing you to treat behavior as parameters and pass functions around.
Enhances Code Reusability: Functional interfaces make it easy to reuse functions and pass them as arguments, improving modularity.
Enables Method References: They allow you to use method references, which provide a shorthand for lambda expressions, enhancing readability.
Better Integration with Streams: Functional interfaces are integral to the Streams API, enabling powerful and expressive operations like filtering, mapping, and reducing collections.
Support for Parallelism: Functional interfaces, in conjunction with Streams, support parallel execution, improving performance for large data sets.
Encourages Cleaner, More Readable Code: By focusing on single method behavior, they promote cleaner and more understandable code compared to traditional interfaces or classes.
Conclusion on Functional Interfaces
- Functional Interfaces enables Lambda Expressions
- Can have default and static methods
- Java provides built-in functional interfaces
- Works well with Streams and Collections
- Reduces boilerplate code
Functional Interface Interview Questions
Top 20 functional interface interview questions
There so many tricky and important functional interface interview questions asked in the interview for freshers as well as experienced. Here are 20 interview questions, including some tricky questions for experienced Java professionals, along with their answers and coding examples.
What is a functional interface in java ?
Answer: A functional interface in Java is an interface with exactly one abstract method. It can have multiple default and static methods. Functional interfaces are used to represent single functionality or behavior, and they can be implemented using lambda expressions or method references.
Can you give an example of a built-in functional interface in java ?
Answer: One of the common built-in functional interfaces is Predicate<T>
. It represents a condition, taking an argument and returning a boolean result.
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4)); // true
What does @FunctionalInterface
annotation do?
Answer: The @FunctionalInterface
annotation is used to indicate that an interface is intended to be a functional interface. It is optional but helps to ensure that the interface adheres to the single abstract method rule.
Can a functional interface have more than one abstract method?
Answer: No, a functional interface can have only one abstract method. If it has more than one abstract method, it will not be considered a valid functional interface.
Can a functional interface have default methods?
Answer: Yes, a functional interface can have default methods. Default methods have a body and can provide a default implementation, allowing flexibility while maintaining the contract of a single abstract method.
Can a functional interface have static methods?
Answer: Yes, a functional interface can have static methods. These methods belong to the interface itself and cannot be overridden by implementing classes.
Can we use lambda expressions with a functional interface?
Answer: Yes, lambda expressions are specifically used to provide the implementation of the single abstract method of a functional interface.
What is the difference between a functional interface and an abstract class?
A functional interface can only have one abstract method, while an abstract class can have multiple abstract methods.
A functional interface is meant for functional programming and often used with lambda expressions, while an abstract class is typically used for class hierarchies with shared behavior.
Can functional interfaces extend other interfaces?
Answer: Yes, a functional interface can extend another functional interface (with exactly one abstract method). It cannot extend interfaces that have more than one abstract method.
What are some examples of built-in functional interfaces in java.util.function
package?
Answer:
Predicate<T>
: Represents a condition.Function<T, R>
: Transforms one type to another.Consumer<T>
: Performs an action without returning a result.Supplier<T>
: Provides a result without input.BiFunction<T, U, R>
: Takes two arguments and returns a result.
What is a Function interface ?
Answer: Function<T, R>
takes an argument of type T
and returns a result of type R
.
Function<Integer, String> intToString = i -> “Number: “ + i;
System.out.println(intToString.apply(5)); // Output: Number: 5
What is a Predicate interface ?
Answer: Predicate<T>
takes a single argument and returns a boolean.
Predicate<Integer> isPositive = num -> num > 0;
System.out.println(isPositive.test(5)); // Output: true
Can a functional interface have constructors?
Answer: No, a functional interface cannot have constructors. It is only used for behavior implementation and doesn’t deal with object instantiation.
What is Consumer Interface ?
Answer: Consumer<T> performs an action on a given input without returning a result.
Consumer<String> print = str -> System.out.println(str);
print.accept(“Hello, World!”); // Output: Hello, World!
What is a Supplier interface?
Answer: Supplier<T> provides a result of type T
without taking any input.
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get()); // Output: random value
Can functional interfaces have multiple abstract methods if they are inherited from other interfaces?
Answer: No, if a functional interface inherits another interface that has more than one abstract method, it will no longer be considered a valid functional interface. A functional interface must have only one abstract method.
What is a BiFunction Interface ?
Answer: BiFunction<T,U,R> takes two arguments and returns a result.
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println(add.apply(3, 4)); // Output: 7
Can we use method references with functional interfaces?
Answer: Yes, method references are often used with functional interfaces as a shorthand for lambda expressions.
public class MethodReferenceExample {
public static void printMessage(String message) {
System.out.println(message);
}
public static void main(String[] args) {
Consumer<String> printer = MethodReferenceExample::printMessage;
printer.accept(“Hello!”); // Output: Hello!
}
}
What will happen if you try to add a second abstract method to a functional interface?
Answer: If a second abstract method is added to a functional interface, it will no longer be considered a functional interface and will not compile because it violates the rule of having exactly one abstract method.
What is a tricky use case of functional interfaces?
Answer: One tricky case is when you have an abstract class implementing a functional interface. This can sometimes lead to unexpected results because the abstract class can have concrete methods and state.
Below example shows that while the abstract class implements the interface, it does not affect the lambda’s behavior when the abstract methods are implemented using lambdas.
@FunctionalInterface
interface MyFunction {
void apply();
}
abstract class MyAbstractClass implements MyFunction {
void extraMethod() {
System.out.println("Extra method");
}
}
public class TrickyExample {
public static void main(String[] args) {
MyFunction func = () -> System.out.println("Functional Interface");
func.apply(); // Output: Functional Interface
}
}