0% found this document useful (0 votes)
24 views11 pages

Understanding Java Functional Interfaces

Functional interfaces in Java allow for lambda expressions and method references by containing only one abstract method. Common examples include Runnable, Comparable, and ActionListener. Functional interfaces can have default and static methods and override Object methods. They are annotated with @FunctionalInterface and allow cleaner code through lambda expressions. Important built-in functional interfaces are in the java.util.function package, including Consumer, Predicate, and Function interfaces.

Uploaded by

Ravi Kumar
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
24 views11 pages

Understanding Java Functional Interfaces

Functional interfaces in Java allow for lambda expressions and method references by containing only one abstract method. Common examples include Runnable, Comparable, and ActionListener. Functional interfaces can have default and static methods and override Object methods. They are annotated with @FunctionalInterface and allow cleaner code through lambda expressions. Important built-in functional interfaces are in the java.util.function package, including Consumer, Predicate, and Function interfaces.

Uploaded by

Ravi Kumar
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Functional Interfaces in Java

An interface that contains only one abstract method is called Functional Interface. Functional Interface is also
known as Single Abstract Method(SAM) interfaces. Runnable, ActionListener, Comparable are some of
the examples of functional interfaces. A functional interface can have any number of default and static
methods and they can declare methods of Object class.

Functional interfaces are introduced in Java SE 8 with Lambda expressions and Method references in order to
make code more readable, clean, and straightforward. Functional interfaces are used and executed by
representing the interface with an annotation called @FunctionalInterface.

Important Points/Observations:
Here are some significant points regarding Functional interfaces in Java:
1. In functional interfaces, there is only one abstract method supported. If the annotation of a
functional interface, i.e., @FunctionalInterface is not implemented or written with a function
interface, more than one abstract method can be declared inside it. However, in this situation with
more than one functional interface, that interface will not be called a functional interface. It is
called a non-functional interface.
2. There is no such need for the @FunctionalInterface annotation as it is voluntary only. This is
written because it helps in checking the compiler level. Besides this, it is optional.
3. An infinite number of methods (whether static or default) can be added to the functional interface.
In simple words, there is no limit to a functional interface containing static and default methods.
4. Overriding methods from the parent class do not violate the rules of a functional interface in Java.
5. The [Link] package contains many built-in functional interfaces in Java 8.

class Test {
public static void main(String args[]) {
// create anonymous inner class object
new Thread(new Runnable() {
@Override public void run()
{
[Link]("New thread created");
}
}).start();
}
}

Output
New thread created
Java 8 onwards, we can assign lambda expression to its functional interface object like this:

class Test {
public static void main(String args[]) {
// lambda expression to create the object
new Thread(() -> {
[Link]("New thread created");
}).start();
}
}
Output
New thread created
@FunctionalInterface Annotation
@FunctionalInterface annotation is used to ensure that the functional interface can’t have more than one
abstract method. In case more than one abstract methods are present, the compiler flags an ‘Unexpected
@FunctionalInterface annotation’ message. However, it is not mandatory to use this annotation.
// Java program to demonstrate lambda expressions to
// implement a user defined functional interface.

@FunctionalInterface

interface Square {
int calculate(int x);
}

class Test {
public static void main(String args[])
{
int a = 5;

// lambda expression to define the calculate method


Square s = (int x) -> x * x;

// parameter passed and return type must be


// same as defined in the prototype
int ans = [Link](a);
[Link](ans);
}
}
Output
25
Some Built-in Java Functional Interfaces
Since Java SE 1.8 onwards, there are many interfaces that are converted into functional interface. All these
interfaces are annotated with @FunctionalInterface. These interfaces are as follows –
 Runnable –> This interface only contains the run() method.
 Comparable –> This interface only contains the compareTo() method.
 ActionListener –> This interface only contains the actionPerformed() method.
 Callable –> This interface only contains the call() method.

Java SE 8 included four main kinds of functional interfaces which are defined in java,[Link]
package and these interfaces can be applied in multiple situations. These are:
1. Consumer
2. Predicate
3. Function
4. Supplier

Amidst the previous four interfaces, the first three interfaces,i.e., Consumer, Predicate, and Function, likewise
have additions that are provided beneath –
1. Consumer -> Bi-Consumer,
2. Predicate -> Bi-Predicate
3. Function -> Bi-Function, Unary Operator, Binary Operator

1. Consumer Interface
The consumer interface of the functional interface is the one that accepts only one argument. The consumer
interface has no return value. It returns nothing.
There are also functional variants of the Consumer — DoubleConsumer, IntConsumer, and LongConsumer.
These variants accept primitive values as arguments.
Other than these variants, there is also one more variant of the Consumer interface known as Bi-Consumer.
Methods of Consumer Interface :
1. void accept(T t)
This method Performs some operation on the given argument.
2. default Consumer<T> andThen(Consumer<? super T> after)
Parameters: This method accepts a parameter after which is the Consumer to be applied after
the current one.
Return Value: This method returns a composed Consumer that first applies the current
Consumer first and then the after operation.
Exception: This method throws NullPointerException if the after operation is null.
Example:
import [Link];
import [Link];
import [Link];
import [Link];
public class ConsumerDemo {
public static void main(String args[]) {
// Consumer to display a number
Consumer<Integer> display = a -> [Link](a);
// Implement display using accept()
[Link](10);

List<Integer> arlist = new ArrayList<Integer>();


[Link](8);
[Link](1);
[Link](3);

// create a Consumer to multiply 2 to every integer of a list


Consumer<List<Integer> > modify = list -> {
for (int i = 0; i < [Link](); i++)
[Link](i, 2 * [Link](i));
};
// Implement modify using accept()
[Link](arlist);

// Consumer to display a list of numbers


Consumer<List<Integer> > dispList = list -> [Link]().forEach(a -> [Link](a + " "));
// Implement dispList using accept()
[Link](arlist);
[Link]();
List<Integer> arlist2 = new ArrayList<Integer>();
[Link](5);
[Link](4);
[Link](9);

// create a Consumer to multiply 2 to every integer of a list


Consumer<List<Integer> > modify2 = list -> {
for (int i = 0; i < [Link](); i++)
[Link](i, 2 * [Link](i));
};
// Also we can use andThen() method
[Link](dispList).accept(arlist2);
[Link]();

List<Integer> arlist3 = new ArrayList<Integer>();


[Link](2);
[Link](7);
[Link](5);
// using null to get NullPointerException in andThen()
try {
[Link](null).accept(arlist3);
}catch (Exception e) {
[Link]("Exception: " + e);
}

List<Integer> arlist4 = new ArrayList<Integer>();


[Link](10);
[Link](12);
[Link](14);
// create a Consumer to multiply 2 to every integer of a list
Consumer<List<Integer> > modi = list -> {
for (int i = 0; i < [Link](); i++)
[Link](i, 2 * [Link](i));
};

// using addThen()
try {
[Link](modi).accept(arlist4);
}catch (Exception e) {
[Link]("Exception: " + e);
}
}
};

Example:
import [Link];
import [Link];
import [Link];
public class ConsumerDemo2 {
static void addList(List<Integer> list){
// Return sum of list values
int result = [Link]()
.mapToInt(Integer::intValue)
.sum();
[Link]("Sum of list values: "+result);
}
public static void main(String[] args) {
// Creating a list and adding values
List<Integer> list = new ArrayList<Integer>();
[Link](10);
[Link](20);
[Link](30);
[Link](40);
// Referring method to String type Consumer interface
Consumer<List<Integer>> consumer = ConsumerDemo2::addList;
[Link](list); // Calling Consumer method
}
}

Bi-Consumer Interface – Bi-Consumer is the most exciting variant of the Consumer interface. The consumer
interface takes only one argument, but on the other side, the Bi-Consumer interface takes two arguments.
Both, Consumer and Bi-Consumer have no return value. It also returns noting just like the Consumer
interface. It is used in iterating through the entries of the map.

2. Predicate Interaface: It improves manageability of code, helps in unit-testing them separately,

The functional method is test(Object).


boolean test(T t) : Evaluates this predicate on the given argument.
Parameters: t - the input argument
Returns: true if the input argument matches the predicate, otherwise false

static Predicate isEqual(Object targetRef) : Returns a predicate that tests if two arguments are equal
according to [Link](Object, Object).

T : the type of arguments to the predicate


Parameters: targetRef : the object reference with which to compare for equality, which may be null
Returns: a predicate that tests if two arguments are equal according to [Link](Object, Object)

default Predicate and(Predicate other) : Returns a composed predicate that represents a


short-circuiting logical AND of this predicate and another.
Parameters : other : a predicate that will be logically-ANDed with this predicate
Returns : a composed predicate that represents the short-circuiting logical AND of this predicate and the other
predicate
Throws : NullPointerException - if other is null
default Predicate negate() :
Returns : a predicate that represents the logical negation of this predicate

default Predicate or(Predicate other) : Returns a composed predicate that represents a short-circuiting
logical OR of this predicate and another.
Parameters : other : a predicate that will be logically-ORed with this predicate
Returns : a composed predicate that represents the short-circuiting logical OR of this predicate and the other
predicate
Throws : NullPointerException - if other is null

Just like the Consumer functional interface, Predicate functional interface also has some extensions. These are
IntPredicate, DoublePredicate, and LongPredicate. These types of predicate functional interfaces accept only
primitive data types or values as arguments.

Example:
import [Link];
public class PredicateDemo1 {
public static void main(String[] args) {
// Creating predicate
Predicate<Integer> lessthan = i -> (i < 18);

// Calling Predicate method


[Link]([Link](10));

// Predicate Chaining
Predicate<Integer> greaterThanTen = (i) -> i > 10;
Predicate<Integer> lowerThanTwenty = (i) -> i < 20;
boolean result = [Link](lowerThanTwenty).test(15);
[Link](result);

// Calling Predicate method


boolean result2 = [Link](lowerThanTwenty).negate().test(15);
[Link](result2);
}
}
Example :
import [Link].*;
import [Link];
class PredicateDemo2 {
public static void main(String args[]) {

// create a list of strings


List<String> names = [Link]( "Laxmi Sravani", "Leela Krishna", "Lasya Priya", "Nandini Siva",
"Pooja Harika", "Laxmi Sudha");

// declare the predicate type as string and use


Predicate<String> p = (s) -> [Link]("L");

// Iterate through the list


for (String st : names) {
// call the test method
if ([Link](st))
[Link](st);
}
}
}
Example:
import [Link].*;
import [Link];
class User{
String name, role;
User(String a, String b) {
name = a;
role = b;
}
String getRole() { return role; }
String getName() { return name; }
public String toString() {
return "User Name : " + name + ", Role :" + role;
}};
class PredicateDemo3 {
public static void main(String[] args) {
List<User> users = new ArrayList<User>();
[Link](new User("Samar", "admin"));
[Link](new User("Ravi", "member"));
[Link](new User("Jiban", "admin"));
[Link](new User("Sudha", "member"));

Predicate<User> predict = us -> [Link]().equals("admin");


for(User usr : users)
if([Link](usr))
[Link](usr);

/* List<User> admins = process(users, (User u) -> [Link]().equals("admin"));


[Link](admins);
}
public static List<User> process(List<User> users, Predicate<User> predicate) {
List<User> result = new ArrayList<User>();
for (User user: users)
if ([Link](user))
[Link](user);
return result;*/
}
};
The predicate functional interface can also be implemented using a class. The syntax for the implementation
of predicate functional interface using a class is given below –
public class CheckForNull implements Predicate {
@Override
public boolean test(Object o) {
return o != null;
}
}
The Java predicate functional interface can also be implemented using Lambda expressions. The example of
implementation of Predicate functional interface is given below –
Predicate predicate = (value) -> value != null;
This implementation of functional interfaces in Java using Java Lambda expressions is more manageable and
effective than the one implemented using a class as both the implementations are doing the same work, i.e.,
returning the same output.

Bi-Predicate – Bi-Predicate is also an extension of the Predicate functional interface, which, instead of one,
takes two arguments, does some processing, and returns the boolean value.

3. Function Interaface :
A function is a type of functional interface in Java that receives only a single argument and returns a value
after the required processing.

The Function interface consists of the following 4 methods as listed which are later discussed as follows:
apply()
andThen()
compose()
identity()

R apply(T t)
Parameters: This method takes in only one parameter t which is the function argument
Return Type: This method returns the function result which is of type R.

Example
import [Link];
public class FunctionDemo1{
public static void main(String args[]) {
// Function which takes in a number and returns half of it
Function<Integer, Double> half = a -> a / 2.0;

// Applying the function to get the result


[Link]([Link](10));
}
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) : It returns a composed
function wherein the parameterized function will be executed after the first one. If evaluation of either
function throws an error, it is relayed to the caller of the composed function.

where V is the type of output of the after function, and of the composed function

Parameters: This method accepts a parameter after which is the function to be applied after the current one.
Return Value: This method returns a composed function that applies the current function first and then the
after function
Exception: This method throws NullPointerException if the after function is null.

import [Link];
public class FunctionDemo2{
public static void main(String args[]) {
// Function which takes in a number and returns half of it
Function<Integer, Double> half = a -> a / 2.0;

// Now treble the output of half function


half = [Link](a -> 3 * a);

// Applying the function to get the result


[Link]([Link](10));
}
}

The compose() method is similar to andThen() method, but the difference between compose and andThen is
the order they execute the functions. While the compose function executes the caller last and the parameter
first, the andThen executes the caller first and the parameter last.

public class FunctionDemo3 {


public static void main(String[] args) {

Function<Integer, Integer> f1 = num -> (num - 4);


Function<Integer, Integer> f2 = num -> (num * 2);

// Using andThen() method


int a=[Link](f2).apply(10);
[Link](a);// Output : 12
/*[Link](f2).apply(10); => first execute [Link](10) method. based on output first method,
[Link](result_of_f1_method) method execute. */

//Using compose function


int b=[Link](f2).apply(10);
[Link](b);// Output : 16
/*[Link](f2).apply(10); => here just opposite to andThen() method. first [Link]() and then
[Link](output_of_f2_method) execute. */
}
}

There are many versions of Function interfaces because a primitive type can’t imply a general type argument,
so we need these versions of function interfaces. Many different versions of the function interfaces are
instrumental and are commonly used in primitive types like double, int, long. The different sequences of these
primitive types are also used in the argument.
These versions are:
Bi-Function – The Bi-Function is substantially related to a Function. Besides, it takes two arguments,
whereas Function accepts one argument.
The prototype and syntax of Bi-Function is given below –
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
.......
}
In the above code of interface, T, U are the inputs, and there is only one output that is R.
Unary Operator and Binary Operator – There are also two other functional interfaces which are named as
Unary Operator and Binary Operator. They both extend the Function and Bi-Function, respectively. In simple
words, Unary Operator extends Function, and Binary Operator extends Bi-Function.
The prototype of the Unary Operator and Binary Operator is given below –
1. Unary Operator
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, U>
{
……...
}
2. Binary Operator
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, U, R>
{
……...
}
We can understand front the above example that the Unary Operator accepts only one argument and returns a
single argument only. Still, in Unary Operator both the input and output values must be identical and of the
same type.
On the other way, Binary Operator takes two values and returns one value comparable to Bi- Function but
similarly like Unary Operator, the input and output value type must be identical and of the same type.
4. Supplier Interface :
The Supplier functional interface is also a type of functional interface that does not take any input or argument
and yet returns a single output. This type of functional interface is generally used in the lazy generation of
values. Supplier functional interfaces are also used for defining the logic for the generation of any sequence.
For example – The logic behind the Fibonacci Series can be generated with the help of the [Link]
method, which is implemented by the Supplier functional Interface.

T get() : This functional method does not take in any argument but produces a value of type T.
Returns: This method returns a value of type T.

Example:
import [Link];
public class SupplierDemo1 {
public static void main(String args[]) {
// This function returns a random value.
Supplier<Double> randomValue = () -> [Link]();

// Print the random value using get()


[Link]([Link]());
}
}

The different extensions of the Supplier functional interface hold many other supplier functions like
BooleanSupplier, DoubleSupplier, LongSupplier, and IntSupplier. The return type of all these further
specializations is their corresponding primitives only.

Common questions

Powered by AI

Java's built-in functional interfaces significantly simplify asynchronous programming by offering streamlined, clear, and concise ways to handle callbacks and perform operations when future values become available. Functional interfaces like Runnable are pivotal in creating and managing asynchronous tasks without the boilerplate that typically accompanies thread handling. With the advent of lambda expressions and method references, interfaces like Function, Consumer, Predicate, and Supplier, provide flexible building blocks for asynchronous pipelines, task scheduling, and event-driven programming. They enable structures like CompletableFuture to be both powerful and intuitive, managing complex asynchronous processes with cleaner syntax and improved readability. This results in more maintainable and understandable asynchronous code.

The Consumer interface in Java is a functional interface that accepts an input but does not return any result. It's integral to Java's functional programming as it allows for operations to be performed on a single argument, which can be particularly useful for iterating over collections or streams. Variants of the Consumer interface include DoubleConsumer, IntConsumer, LongConsumer, and BiConsumer, where BiConsumer accepts two arguments and performs operations on them. These variants facilitate operations on primitive data types directly. The Consumer interface's `accept` method executes the defined operation, and the `andThen` method allows chaining of Consumers, executing sequentially.

The Predicate interface in Java is a functional interface that improves code maintainability and testability by providing a standardized way to define conditions (or predicates) that evaluate to true or false for given inputs. By encapsulating conditional logic, Predicates make code more modular and reusable. They enhance unit testing since predicates can be tested in isolation for different scenarios. The interface provides methods such as `test`, `and`, `or`, and `negate`, allowing complex logical conditions to be constructed via method chaining, thus promoting clean and readable code. Extensions like IntPredicate and BiPredicate offer specialized predicates for primitive types and dual arguments, respectively.

The Supplier interface is considered powerful in Java for lazy value generation because it provides a simple contract (without parameters) for supplying a result when it is needed rather than immediately. This can optimize performance by delaying computation until its value is actually required, reducing unnecessary processing. It is often used in contexts like generating random values or when dealing with complex object creation that should be deferred until actually needed during execution. For example, using a Supplier with `Stream.generate` allows the lazy generation of sequences such as the Fibonacci series. This lazy computation aspect is critical in scenarios where resource-intensive tasks need to be deferred to optimize system performance.

Lambda expressions in Java provide a concise and functional approach to implementing the abstract methods of functional interfaces. They enhance the functionality of these interfaces by allowing single-line implementations, making the code significantly more readable and concise. This is particularly useful in creating instances of functional interfaces without the boilerplate code associated with anonymous inner classes. For example, a functional interface like Runnable, which typically requires an anonymous inner class to define the run method, can be succinctly implemented using a lambda as `() -> { System.out.println("New thread created"); }`. This approach improves not only readability but also performance by simplifying instances and invocation of interface methods.

UnaryOperator and BinaryOperator are specialized forms of the Function and BiFunction interfaces, respectively. Both are used when the type of the input and output are the same, making them convenient for operations where input and result are expected to be of the same type. UnaryOperator extends Function, taking a single argument and producing a result of the same type. BinaryOperator extends BiFunction, accepting two arguments of the same type and producing a result of the same type. These characteristics make them particularly useful for operations like reducing, accumulating, or any arithmetic operations involving identical data types.

Functional interfaces in Java are interfaces that contain only a single abstract method, also known as Single Abstract Method (SAM) interfaces. They can have multiple default and static methods but are characterized by their single abstract method. They were introduced in Java SE 8 to leverage lambda expressions and method references, thereby simplifying code and making it more readable and straightforward. Examples include interfaces like Runnable, ActionListener, and Comparable. The @FunctionalInterface annotation is used to ensure the interface conforms to the single abstract method requirement, although its use is not mandatory.

BiFunction differs from Function primarily in the number of input parameters it accepts. While the Function interface handles a single input and produces a result, BiFunction deals with two inputs and produces a result. This distinction allows BiFunction to be suitable for scenarios that require operations on pairs of data such as combining, pairing, or processing related values. Despite handling more inputs, BiFunction still produces a single output, similar to Function, maintaining a straightforward interface for processing operations that require two inputs. This makes BiFunction highly versatile for mathematical computations or combining operations like merging two data structures.

The `compose` and `andThen` methods in the Function interface provide complementary ways to create compounded functions by combining multiple functions. `compose` applies the given function first and then applies the calling function on the result, effectively in a `f(g(x))` manner. In contrast, `andThen` applies the calling function first and then applies the passed function to the result, effectively in an `g(f(x))` manner. This order of execution distinction allows developers to build specific sequences of operations suitable to the task at hand. Both methods enable more flexible and powerful function composition by precisely defining execution order in complex processing chains.

The BiPredicate interface provides enhanced capabilities for logic operations over Predicate by accepting two input parameters instead of one. This dual parameterization allows BiPredicate to address more complex scenarios where relationships between two elements need to be evaluated. For instance, checking equality, comparing values, or validating conditions involving pairs can be more intuitively handled using BiPredicate. This duality facilitates operations such as checking if two lists contain identical elements in corresponding positions. Conversely, Predicate is limited to single-element evaluations. BiPredicate thus expands the logical repertoire available to Java developers, enhancing the expressiveness of conditions applicable in multi-parameter contexts.

You might also like