
Функциональное программирование стало неотъемлемой частью экосистемы Java благодаря появлению в Java 8 лямбд, потоков и функциональных интерфейсов Predicate и Function. С этими концепциями пишется более выразительный, модульный и переиспользуемый код. Даже опытными программистами, так называемыми сеньорами, этот новый подход освоен не полностью, в их коде не раскрывается весь его потенциал.
Рассмотрим, как создается динамический класс с Predicate и Function для валидации и преобразования данных. И продемонстрируем, как эти интерфейсы используются со спецификациями JPA, коллекциями и потоками для реальных приложений.
Что такое Predicate и Function?
Predicate
Predicate — это функциональный интерфейс, которым вычисляется логическое условие по одному аргументу.
- Сигнатура:
boolean test(T t). - Стандартное применение: фильтры, валидации, удаление элементов из коллекций.
Function
Function — это функциональный интерфейс, которым вводимое значение преобразуется выводимое.
- Сигнатура:
R apply(T t). - Стандартное применение: преобразование данных, сопоставление в потоках.
Настройка процессов при помощи Predicate и Function
В примере ниже продемонстрируем, как Predicate и Function используются с перечислениями для моделирования реальной бизнес-логики, определим разновидности кредитных карт — GOLD, BLACK и PLATINUM — и применим правила для:
- Проверки с помощью
Predicate, принята ли транзакция. - Подсчета с помощью
Functionбаллов лояльности за транзакции.
Перечисление CreditCardType:
import java.math.BigDecimal;
import java.util.function.Function;
import java.util.function.Predicate;
public enum CreditCardType {
GOLD {
@Override
Function<BigDecimal, BigDecimal> formulaPoints() {
return amount -> amount.multiply(new BigDecimal("0.10"));
}
@Override
Predicate<BigDecimal> acceptAmount() {
return amount -> amount.compareTo(new BigDecimal("1000")) <= 0;
}
},
BLACK {
@Override
Function<BigDecimal, BigDecimal> formulaPoints() {
return amount -> amount.multiply(new BigDecimal("0.20"));
}
@Override
Predicate<BigDecimal> acceptAmount() {
return amount -> amount.compareTo(new BigDecimal("5000")) <= 0;
}
},
PLATINUM {
@Override
Function<BigDecimal, BigDecimal> formulaPoints() {
return amount -> amount.multiply(new BigDecimal("0.50"));
}
@Override
Predicate<BigDecimal> acceptAmount() {
return amount -> amount.compareTo(new BigDecimal("10000")) <= 0;
}
};
abstract Function<BigDecimal, BigDecimal> formulaPoints();
abstract Predicate<BigDecimal> acceptAmount();
}
Пример использования: валидация и подсчет баллов лояльности
public class Main {
public static void main(String[] args) {
calculatePoints(CreditCardType.GOLD, new BigDecimal("1000"));
calculatePoints(CreditCardType.BLACK, new BigDecimal("1000"));
calculatePoints(CreditCardType.PLATINUM, new BigDecimal("1000"));
calculatePoints(CreditCardType.GOLD, new BigDecimal("5000"));
calculatePoints(CreditCardType.BLACK, new BigDecimal("5000"));
calculatePoints(CreditCardType.PLATINUM, new BigDecimal("5000"));
calculatePoints(CreditCardType.GOLD, new BigDecimal("10000"));
calculatePoints(CreditCardType.BLACK, new BigDecimal("10000"));
calculatePoints(CreditCardType.PLATINUM, new BigDecimal("10000"));
}
private static void calculatePoints(CreditCardType creditCardType, BigDecimal amountTransaction) {
System.out.println(creditCardType);
if (creditCardType.acceptAmount().test(amountTransaction)) {
System.out.println("Transaction accepted!");
System.out.println("Bonus points: " + creditCardType.formulaPoints().apply(amountTransaction));
} else {
System.out.println("Transaction rejected!");
}
System.out.println("------------------------------");
}
}
Объяснение
- Перечисление
CreditCardType:
- Каждой разновидностью кредитной карты определяются собственные правила для:
acceptAmount(): этоPredicate, которым проверяется соответствие суммы транзакции критериям карты.formulaPoints(): этоFunction, которым по сумме транзакции подсчитываются баллы лояльности.
2. Валидация с Predicate:
- Методом
acceptAmount()проверяется допустимость транзакции для разновидности карты. Например: GOLDпринимаются транзакции суммой до 1000;BLACK— до 5000;PLATINUM— до 10 000.
3. Преобразование с Function:
- Методом
formulaPoints()подсчитываются баллы лояльности за принятые транзакции. Например: GOLD: 10 % от суммы транзакции;BLACK: 20 % от суммы транзакции;PLATINUM: 50 % от суммы транзакции.
4. Метод calculatePoints:
- Им при помощи
Predicateпроверяется допустимость транзакции. - Если она допустима, при помощи
Functionподсчитываются и отображаются баллы лояльности.
Пример вывода
GOLD
Transaction accepted!
Bonus points: 100.0
------------------------------
BLACK
Transaction accepted!
Bonus points: 200.0
------------------------------
PLATINUM
Transaction accepted!
Bonus points: 500.0
------------------------------
GOLD
Transaction rejected!
------------------------------
BLACK
Transaction accepted!
Bonus points: 1000.0
------------------------------
PLATINUM
Transaction accepted!
Bonus points: 2500.0
------------------------------
GOLD
Transaction rejected!
------------------------------
BLACK
Transaction rejected!
------------------------------
PLATINUM
Transaction accepted!
Bonus points: 5000.0
------------------------------
Преимущества Predicate и Function
- Модульность:
- Каждой разновидностью карты инкапсулируется собственная логика валидации и подсчета.
- Легко добавляются новые разновидности карт с уникальными правилами.
2. Переиспользуемость:
- Интерфейсами
PredicateиFunctionпредоставляется переиспользуемая логика валидации и преобразования.
3. Удобство восприятия:
- В разновидностях карт используются перечисления, поэтому логика структурирована и легко отслеживается.
4. Расширяемость:
- Чтобы добавить новые правила для разновидностей карт, достаточно реализовать абстрактные методы.
Реальные сценарии
1. Спецификации JPA
В Spring Data JPA Predicate важен при построении динамических запросов с помощью спецификаций, он применяется интерфейсом Specification для определения фильтров запросов:
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
public class CustomerSpecification {
public static Specification<Customer> hasName(String name) {
return (Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder builder) ->
builder.equal(root.get("name"), name);
}
public static Specification<Customer> hasAgeGreaterThan(int age) {
return (Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder builder) ->
builder.greaterThan(root.get("age"), age);
}
}
Пример использования:
Specification<Customer> spec = CustomerSpecification.hasName("John")
.and(CustomerSpecification.hasAgeGreaterThan(25));
List<Customer> customers = customerRepository.findAll(spec);
Так Predicate-подобным поведением динамически фильтруются запросы к базе данных.
2. Коллекции
Классами коллекций Java Predicate активно используется для фильтрации, а Function — для преобразования данных.
- Фильтрация выполняется с помощью
removeIf:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
names.removeIf(name -> name.startsWith("B"));
System.out.println(names); // Вывод: [Alice, Charlie]
- Преобразование — с помощью
replaceAll:
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
names.replaceAll(name -> name.toUpperCase());
System.out.println(names); // Вывод: [ALICE, BOB, CHARLIE]
3. Потоки
Для таких операций, как объединение элементов в один результат, фильтрация и сопоставление, потоками активно применяются и Predicate, и Function.
- Фильтрация с
Predicate:
List<String> names = List.of("Alice", "Bob", "Charlie");
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.toList();
System.out.println(filteredNames); // Вывод: [Alice]
- Сопоставление при помощи
Function:
List<String> names = List.of("Alice", "Bob", "Charlie");
List<Integer> nameLengths = names.stream()
.map(String::length)
.toList();
System.out.println(nameLengths); // Вывод: [5, 3, 7]
Этими методами коллекции обрабатываются декларативно, без циклов, а код делается более удобным для восприятия и выразительным.
Заключение
Predicate и Function — это фундаментальные инструменты функционального программирования на Java, которыми предоставляются выразительные способы валидации, фильтрации и преобразования данных.
От реальных сценариев вроде спецификаций JPA и потоковой обработки до асинхронных операций с CompletableFuture — благодаря этим интерфейсам разработчики пишут более чистый, модульный и переиспользуемый код.
Усовершенствуйте проектирование приложений, включив эти шаблоны в свои проекты. Освойте Predicate и Function и раскройте весь потенциал функционального программирования на Java.
Читайте также:
- JAVA: разница между параметрами JVM -D, -X, -XX
- Java: оператор try-with-resources
- Альтернатива Java 8: что умеет VAVR
Читайте нас в Telegram, VK и Дзен
Перевод статьи Lucas Fernandes: Master Functional Programming in Java: Using Predicate and Function Elegantly





