Package java.util.function
The interfaces in this package are general purpose functional interfaces used by the JDK, and are available to be used by user code as well. While they do not identify a complete set of function shapes to which lambda expressions might be adapted, they provide enough to cover common requirements. Other functional interfaces provided for specific purposes, such as FileFilter , are defined in the packages where they are used.
The interfaces in this package are annotated with FunctionalInterface . This annotation is not a requirement for the compiler to recognize an interface as a functional interface, but merely an aid to capture design intent and enlist the help of the compiler in identifying accidental violations of design intent.
Functional interfaces often represent abstract concepts like functions, actions, or predicates. In documenting functional interfaces, or referring to variables typed as functional interfaces, it is common to refer directly to those abstract concepts, for example using «this function» instead of «the function represented by this object». When an API method is said to accept or return a functional interface in this manner, such as «applies the provided function to. «, this is understood to mean a non-null reference to an object implementing the appropriate functional interface, unless potential nullity is explicitly specified.
Функциональные интерфейсы в Java 8 → Consumer, Supplier, Predicate и Function. Что к чему и зачем нужны

Java представила поддержку функционального программирования в выпуске Java версии 8. Этот конкретный выпуск также представил несколько новых концепций, в частности лямбда-выражения, ссылки на методы и множество функциональных интерфейсов. При обсуждении последних, есть несколько функциональных интерфейсов, а именно Потребитель (Consumer), Поставщик (Supplier), Предикат (Predicat) и Функция (Function), которые являются наиболее важными. В этой статье мы о них и поговорим.
Consumer (потребитель)
Consumer — это функциональный интерфейс, который принимает один параметр на вход и не возвращает никаких выходных данных. На языке непрофессионала, как следует из названия, реализация этого интерфейса потребляет вводимые данные. Пользовательский интерфейс имеет два метода:
Метод accept является единым абстрактным методом (SAM), который принимает один аргумент типа T. Тогда как другой метод andThen является методом по умолчанию и используется для композиции.
Ниже приведен пример интерфейса consumer. Мы создали потребительскую реализацию, которая использует строку, а затем просто выводит ее на экран. Метод forEach принимает реализацию потребительского интерфейса.
В следующем примере демонстрируется использование составления нескольких реализаций интерфейса consumer для создания цепочки потребителей.
Ниже мы создали двух потребителей: один преобразует список элементов в строки верхнего регистра, а другой выводит строку верхнего регистра.
Интерфейс Consumer имеет специфические типы реализаций для типов integer, double и long -> IntConsumer, DoubleConsumer и LongConsumer, как показано ниже:
Supplier (поставщик)
Supplier — это простой интерфейс, указывающий, что данная реализация является поставщиком какого то результа. Этот интерфейс, однако, не накладывает никаких дополнительных ограничений, которые реализация поставщика должна возвращать при каждом новом получении результата.
У поставщика есть только один метод get() и нет никаких других методов по умолчанию или статических методов.
Интерфейс поставщик имеет свои примитивные варианты, такие как IntSupplier, DoubleSupplier и т. д., как показано ниже. Обратите внимание, что имя метода — get() используется для универсального интерфейса поставщика. Однако для примитивных вариантов этот метод соответствует примитивному типу.
Одно из основных применений этого интерфейса это использование для включения отложенного выполнения. Это означает отсрочку выполнения до тех пор, пока оно не понадобится. Например, в классе Optional есть метод orElseGet. Этот метод срабатывает, если у option нет данных. Это показано ниже:
Predicate (предикат)
Интерфейс Predicate представляет собой логическую функцию аргумента. Он в основном используется для фильтрации данных из потока (stream) Java. Метод фильтра потока принимает предикат для фильтрации данных и возврата нового потока, удовлетворяющего предикату. У предиката есть метод test(), который принимает аргумент и возвращает логическое значение.
В приведенном выше примере мы создали предикат, который проверяет имена, начинающиеся с S. Этот предикат передается потоку.
Predicate также предоставляет несколько стандартных и статических методов для композиции и других целей:
В следующем примере демонстрируется использование и метод для составления цепочки предикатов.
Function (функция)
Интерфейс Function — это более общий интерфейс, который принимает один аргумент и выдает результат. В нем применяется единый абстрактный метод (SAM), который принимает аргумент типа T и выдает результат типа R. Одним из распространенных вариантов использования этого интерфейса является метод Stream.map. Пример использования показан ниже:
Подведем итоги
Введение функционального программирования представило новую парадигму языка Java. И интерфейсы Consumer, Supplier, Predicate и Function играют решающую роль в том, как это реализовано в Java. Освоение этих интерфейсов и связанных с ними примитивных вариантов, безусловно, помогает писать более качественный функциональный код.
Функциональные интерфейсы
Функциональный интерфейс — это интерфейс, который определяет только один абстрактный метод.
Основное назначение – использование в лямбда выражениях и method reference.
Чтобы точно определить интерфейс как функциональный, добавлена аннотация @FunctionalInterface , работающая по принципу @Override.
Она обозначит замысел и не даст определить второй абстрактный метод в интерфейсе.
Интерфейс может включать сколько угодно default методов и при этом оставаться функциональным, потому что default методы — не абстрактные.
Встроенные функциональные интерфейсы:
public interface Predicate<T>
Проверяет соблюдение некоторого условия. Если оно соблюдается, то возвращается значение true. В качестве параметра лямбда-выражение принимает объект типа T.
public interface Function<T, R>
Представляет функцию перехода от объекта типа T к объекту типа R
public interface Supplier<T>
Не принимает никаких аргументов, но должен возвращать объект типа T
public interface Consumer<T>
Выполняет некоторое действие над объектом типа T, при этом ничего не возвращая
public interface UnaryOperator<T>
принимает в качестве параметра объект типа T, выполняет над ними операции и возвращает результат операций в виде объекта типа T
public interface BinaryOperator<T>
принимает в качестве параметра два объекта типа T, выполняет над ними бинарную операцию и возвращает ее результат также в виде объекта типа T
public class LambdaApp <
public static void main(String[] args) <
Predicate<Integer> isPositive = x -> x > 0;
public class LambdaApp <
public static void main(String[] args) <
Function<Integer, String> convert = x-> String.valueOf(x) + » долларов»;
System.out.println(convert.apply(5)); // 5 долларов
public class LambdaApp <
public static void main(String[] args) <
Scanner in = new Scanner(System.in);
String name = in.nextLine();
return new User(name);
User user1 = userFactory.get();
User user2 = userFactory.get();
System.out.println(«Имя user1: » + user1.getName());
System.out.println(«Имя user2: » + user2.getName());
public class LambdaApp <
public static void main(String[] args) <
Consumer<Integer> printer = x-> System.out.printf(«%d долларов \n», x);
printer.accept(600); // 600 долларов
public class LambdaApp <
public static void main(String[] args) <
UnaryOperator<Integer> square = x -> x*x;
public class LambdaApp <
public static void main(String[] args) <
BinaryOperator<Integer> multiply = (x, y) -> x * y;
System.out.println(multiply.apply(3, 5)); // 15
System.out.println(multiply.apply(10, -2)); // -20
Лямбда-выражения
Представляет набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы.
Образует реализацию метода, определенного в функциональном интерфейсе. При этом важно, что функциональный интерфейс должен содержать только один единственный метод без реализации.
список параметров выражения -> тело лямбда-выражения (действия)
Параметры лямбда-выражения должны соответствовать по типу параметрам метода из функционального интерфейса.
B лямбда-выражении использование обобщений не допускается. В этом случае нам надо типизировать объект интерфейса определенным типом, который потом будет применяться в лямбда-выражении:
public class LambdaApp <
public static void main(String[] args) <
Operationable<Integer> operation1 = (x, y)-> x + y;
Operationable<String> operation2 = (x, y) -> x + y;
System.out.println(operation1.calculate(20, 10)); //30
System.out.println(operation2.calculate(«20», «10»)); //2010
T calculate(T x, T y);
Важно:
Одним из ключевых моментов в использовании лямбд является отложенное выполнение (deferred execution).
То есть мы определяем в одном месте программы лямбда-выражение и затем можем его вызывать при необходимости неопределенное количество раз в различных частях программы.
Отложенное выполнение может потребоваться, к примеру, в следующих случаях:
- Выполнение кода отдельном потоке
- Выполнение одного и того же кода несколько раз
- Выполнение кода в результате какого-то события
- Выполнение кода только в том случае, когда он действительно необходим и если он необходим
Ссылка на метод
Ссылки на методы (Method References) – это компактные лямбда выражения для методов, у которых уже есть имя.
Ссылки на методы бывают четырех видов:
Ссылка на статический метод — ContainingClass::staticMethodName
Function <String, Boolean> function = e -> Boolean.valueOf(e);
Function <String, Boolean> function = Boolean::valueOf;
Ссылка на нестатический метод конкретного объекта — containingObject::instanceMethodName
Consumer <String> consumer = e -> System.out.println(e);
Consumer <String> consumer = System.out::println;
Ссылка на нестатический метод любого объекта конкретного типа — ContainingType::methodName
Guide to Functional Interfaces and Lambda Expressions in Java
Java is an object-oriented language, imperative in its essence (contrasting with the declarative practice that is functional programming). Nonetheless, it was possible to apply functional principles to Java programs prior to version 8, however it required additional work to bypass the innate structure of the language and resulted in convoluted code. Java 8 brought about ways to harness the true efficacy and ease to which functional programming aspires.
This guide aims to provide a holistic view into functional programming, a concept that appears rather esoteric to the developer of OOP background. Because of this, material is oftentimes scattered and scarce. We will first establish an understanding of the core concepts of functional programming and the ways in which Java implements them.
Because there's a lot of misunderstanding regarding Functional Programming for those with an OOP background — we'll start out with a primer on Functional Programming and its benefits.
Then, we'll dive into Lambda Expressions as Java's implementation of first-class functions, as well as functional interfaces, followed by a quick look at Java's function package.
Primer on Functional Programming
Functional programming is a programming paradigm that revolves around — well, functions. Although object-oriented programming also employs functions, the building blocks of the program are the objects. Objects are used to mediate the state and behavior patterns inside the program, while functions are there to take care of the control flow.
Functional programming separates behavior from objects.
Functions then have the liberty to act as first-class entities. They can be stored in variables and can be arguments or the return values of other functions without needing to be accompanied by an object. These discrete entities are termed first-class functions, while the functions enclosing them are named higher-order functions.
Functional programming also has a different approach towards the program state. In OOP, the desired outcome of an algorithm is achieved by manipulating the state of the program. Functional practice refrains from causing state changes altogether. The functions are generally pure, meaning that they do not cause any side effects; they don't alter global variables, perform IO or throw exceptions.
There exist purely functional languages, some of which enforce the use of immutable variables. There also exist purely object-oriented languages. Java is a multi paradigm language; it has the ability to teeter between different programming styles and utilize the benefits of multiple paradigms in the same code-base.
The Benefits of Functional Programming
Functional programming, among all else, offers flexibility. We can create layers of generalization. We can scaffold behavioral patterns and customize them by passing in additional instructions when needed.
Object-oriented programming also has ways to create these patterns, though they depend on the use of objects. Interfaces, for example, can be used to create a scaffold, and each class implementing the interface can tailor the behavior defined in its own way. Then again, an object should always be there to carry the variants. Functional programming provides a more elegant way.
Furthermore, functional programming uses pure functions. Since pure functions can not alter states outside of their scope, they do not have the power to affect one another; each function is fully independent. This gives programmers the ability to dispose of the functions when they are no longer needed, alter the execution order at will, or execute functions in parallel.
Since pure functions are not dependent on external values, re-executing the code with the same arguments will result in the same outcome every time. This supports the optimization technique called memoization (not "memorization"), the process of caching the results of an expensive execution sequence to retrieve them when needed elsewhere in the program.
Additionally, the ability to treat functions as first-class entities allows for currying — the technique of subdividing the execution sequence of a function to perform at separate times. A function with multiple parameters can be partially executed at the point where one parameter is supplied, and the rest of the operation can be stored and delayed until the next parameter is given.
Lambda Expressions in Java
Functional Interfaces and Lambda Expressions
Java implements the basic block of functional programming, the pure first-class functions, in the form of lambda expressions.
Lambda expressions are the couriers via which Java moves around a set of behavior.
Lambda expressions, by and large, have the structure of:
Then again, this structure is subject to change. Let's see the lambdas in action first and elaborate on the adapted versions of their syntax later on. We'll start off by defining a functional interface:
A functional interface is an interface that has exactly one abstract method.
We can then implement this interface's method, through a lambda expression:
With this implementation, the concat() method now has a body and can be used later on:
Let's take a step back and peel away at what we just did. The StringConcat interface holds a single abstract method ( concat() ) which takes two string parameters and is expected to return a string value.
StringConcat is an interface and can not be instantiated. On the right-hand side of the assignment, the compiler expects to find an instantiation of a class that implements StringConcat , not a function. Yet, the code works seamlessly.
Java is inherently object-oriented. Everything is an object in Java (more accurately, everything extends into an Object class), including lambda expressions.
Even though we get to treat lambdas as first-class functions, Java interprets them as objects. Intrinsic in that, the lambda expression assigned to be of type StringConcat is essentially an implementing class and therefore has to define the behavior for StringConcat 's method.
The concat() method can be called in the same way object methods are called ( lambdaConcat.concat() ), and it behaves as defined by the lambda expression:
At the end of the program execution, console should read:
Lambdas as Arguments
Lambdas shine more when they're passed in as arguments to methods, instead of used as utility classes. Let's implement a function that filters through a list of people to find a set statistically likely to be "likable" by some set standard.
Note: Our standard for "friendliness" will be set just for illustrational purposes, and doesn't reflect any real research or statistical analysis.
The function will accept a mass and bias to filter out the mass ending up with a group of people that are, according to the opinion applied, "nice people":
The bias in the parameter list will be a function — a lambda expression — that the higher-order function refers to decide the appeal of each person in the mass.
Let's start by creating a Person class to represent a person:
The Person class is assigned various fields to outline each of their characters. Each Person has a name, age, a sociability signifier, a pet preference selected among a set of constants, and a list of hobbies.
With a Person class, let's go ahead, defining a Bias functional interface with a test() function. The test() function will, naturally, be abstract and without implementation by default:
Once we implement it, the test() function will test a person for their likability, according to some set of biases. Let's go ahead and define the filter() function as well, which accepts a list of people and a Bias for filtering:
Based on the result of the test() function, we either add or skip adding a person to the filteredPeople list, which is, well, how filters work. Keep in mind that the actual implementation of the test() function still doesn't exist, and will only gain body after we define its body as a lambda function.
Since the filter() method accepts the Bias functional interface, we can anonymously create the lambda function in the filter() call:
Finally, this is where it all comes together — we've defined the body of the functional interface via a lambda expression:
The lambda expression gets evaluated and compared against the signature of Bias 's test() method and this body is then used as the test() method's check, and returns a true or false based on the value of the isExtrovert() method.
Keep in mind that we could've used anybody here, since Bias is a "plug-and-play" functional interface.
The ability to create a method that can adjust its approach in this manner is a delicacy of functional programming.
The filter() method is a higher-degree function that takes another function as its parameter according to which it alters its behavior, where the other function is fully fluid.
There exist myriad ways in which we can select a Person to hang out with. Putting the ethics of filtering like this to the side, we may choose to hang out with people of a certain age scope, prefer extroverts, or we may be desperate to find someone who would go to the gym with us yet be disinclined to share their cat stories.
Various selection criteria can be chained together as well.
Of course, it is possible to create different methods to serve each scenario — yet does it make sense to purchase different drills to use on different materials when you can simply change the drill bits?
Free eBook: Git Essentials
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
The filter() method provides flexibility. It defines the main behavior, selecting. Later on, in the program, we can use this method for any selection and just pass in "how to".
It's worth noting that the filter() method starts by creating a new ArrayList , as functional practice refrains from changing the state of the program. Instead of operating on and manipulating the original list, we start with an empty list which we later populate with desired Person s.
The list holding only the extroverts is then passed to list() to be displayed in the console:
This example showcases the flexibility and liquidity of functional interfaces and their lambda-created bodies.
Lambdas and Interfaces
So far, the lambda expressions were ascribed to an interface. This will be the norm any time we want to implement first-class functions in Java.
Consider the implementation of arrays. When an array's elements are needed somewhere in the code, we call the array by its assigned name and access its elements through that name instead of moving the actual set of data around. And since we have declared it to be an array of one type, every time we want to operate on it, the compiler knows that the variable name is referring to an array and that this array stores objects of a significant type. The compiler thus can decide the capabilities of this variable and the actions it can perform.
Java is a statically-typed language — it requires this knowledge for every variable.
Every variable must state its name and its type before it can be used (this is called declaring a variable). Lambda expressions are not an exception to this rule.
When we want to use lambda expressions, we need to let the compiler know the nature of the encapsulated behavior. The interfaces we bind to lambda expressions are there to provide this information; they act as footnotes the compiler can refer to.
We could carry the name and type information along with the lambda expression itself. However, more often than not, we will use the same type of lambdas to create a variety of particular behaviors.
It's good practice to avoid redundancy in the code; typing the same information many times over will only make our code error-prone and our fingers weary.
Lambda Expression Syntax
Lambdas come in many flavors. While the lambda operator ( -> ) is set firm, brackets and type declarations can be removed in some circumstances.
Lambda takes its simplest form when there only exists one parameter and one operation to perform inside the function body.
We no longer need parentheses around the parameter, no type declaration needed, no curly brackets enclosing the statement, and no requirement to use the return keyword.
The lambda expression can take more than one parameter or may not take any. In those cases, we are bound to include parentheses:
If the function body includes more than one statement, the curly braces and, if the return type is not void, the return keyword are also required:
The type declaration for the parameters can be omitted fully. Though if one parameter amongst many has its type declared, others are required to follow in its footsteps:
Both statements above are valid. However, the compiler would complain if the program were to use the expression below:
Functional Interfaces
@FunctionalInterface
Any interface with a single abstract method qualifies to be a functional interface; there is no additional requirement. Yet, a distinction may be necessary for large codebases.
Let's take the Bias interface from Lambdas as Arguments, and add another abstract method to it:
The Bias interface was connected to a lambda expression, yet the compiler does not complain if we add another method to the interface, which turns it from a functional interface to a regular one.
The compiler has no way of knowing that Bias was supposed to be a functional interface until it encounters the lambda expression bound to it. Since a regular interface can have many abstract methods (and since there is no indication that this interface is not like any other), the compiler will blame the lambda expression for it tries to bind to a non-functional interface.
To avoid this, Java provides a way to mark the interfaces that serve lambda expressions, explicitly:
The @FunctionalInterface annotation will let the compiler know that this interface is meant to be functional, and therefore any additional abstract method is not welcome here.
The compiler can now interfere on the spot when someone makes the mistake of adding another method to this interface, though the chances of that are lowered yet again by the @FunctionalInterface mark.
Default and Static Methods
Up until Java 8, interfaces were limited to having abstract methods and constants. Along with functional programming support came the addition of default and static methods to interface definitions.
An abstract method defines a skeleton for the method to be implemented. A default method, on the other hand, is no mere skeleton; it is explicitly defined. Yet, an implementing class is given the option to override the default methods. If they don't, the default implementation kicks in:
Let's implement this interface without implementing the bark() method:
Now, let's instantiate it and take a look at the default implementation kicking in:
A static method of an interface, on the other hand, is the private property of that interface. It can only be called via the interface name and cannot be overridden by the implementing classes:
Let's implement the interface:
And instantiate a GermanSheperd :
This results in:
The java.util.function Package
The extent of information functional interfaces provide is limited. The method definitions can easily be generalized to cover common use cases, and they can be fairly flexible in their implementations.
The return type of the abstract method can be any of the primitive types (integer, string, double, etc.) or can be void. Any classes that are defined inside the program can also be declared as the return type, though the generic type would cover all.
The same logic applies to the parameter types. Even though the number of parameters to a method can still vary, there exists a logical limit for the sake of code quality. The list of names that can be assigned to a function is also limitless, though it rarely matters.
In the end, we are left with a handful of permutations that can cover most of the common use cases.
Java employs 43 predefined functional interfaces, in the java.util.function package, to serve these scenarios. We can group them in five groups:
In their individual guides — we'll be covering each of these groups separately.
Conclusion
In this guide, we've taken a holistic look at Functional Programming in Java and its implementation. We've covered Functional Interfaces, as well as Lambda Expressions as the building blocks for functional code.