Функциональные интерфейсы
Лямбда в Java всегда имеет тип, и этот тип — функциональный интерфейс. Функциональный интерфейс — это интерфейс ровно с одним абстрактным методом. Лямбда и есть реализация этого единственного метода.
Свой функциональный интерфейс
Любой интерфейс с одним абстрактным методом подходит для лямбды. Аннотация @FunctionalInterface не обязательна, но просит компилятор проверить, что метод действительно один:
@FunctionalInterface
interface Discount {
double apply(double price);
}
void main() {
Discount half = price -> price / 2;
Discount minusFive = price -> price - 5;
IO.println(half.apply(40));
IO.println(minusFive.apply(40));
}Результат:
20.0
35.0Function и Predicate
Писать свой интерфейс приходится редко: в пакете java.util.function есть готовые. Function<T, R> принимает T и возвращает R (метод apply()), а Predicate<T> проверяет условие и возвращает boolean (метод test()):
void main() {
Function<String, Integer> length = s -> s.length();
Predicate<Integer> isAdult = age -> age >= 18;
IO.println(length.apply("Анна"));
IO.println(isAdult.test(15));
IO.println(isAdult.test(30));
}Результат:
4
false
trueConsumer и Supplier
Consumer<T> принимает значение и ничего не возвращает — он что-то делает: печатает, сохраняет (метод accept()). Supplier<T>, наоборот, ничего не принимает и выдаёт значение (метод get()):
void main() {
Consumer<String> greet = name -> IO.println("Привет, " + name + "!");
Supplier<List<String>> newList = () -> new ArrayList<>();
greet.accept("Олег");
List<String> list = newList.get();
IO.println(list.isEmpty());
}Результат:
Привет, Олег!
trueДругие стандартные интерфейсы
Для двух аргументов есть BiFunction<T, U, R> и BiPredicate<T, U>. Если типы аргумента и результата совпадают, используют UnaryOperator<T> и BinaryOperator<T>:
void main() {
BiFunction<String, Integer, String> repeat = (s, n) -> s.repeat(n);
UnaryOperator<String> trim = s -> s.strip();
BinaryOperator<Integer> max = (a, b) -> a > b ? a : b;
IO.println(repeat.apply("ха", 3));
IO.println("[" + trim.apply(" текст ") + "]");
IO.println(max.apply(7, 4));
}Результат:
хахаха
[текст]
7Есть и версии для примитивов, чтобы не упаковывать числа в объекты: IntPredicate, IntFunction, ToIntFunction и другие.
Комбинирование
У стандартных интерфейсов есть методы по умолчанию для сборки сложных функций из простых. Условия соединяют через and(), or() и negate(), а функции выстраивают в цепочку через andThen():
void main() {
Predicate<String> notEmpty = s -> !s.isEmpty();
Predicate<String> short5 = s -> s.length() <= 5;
Predicate<String> goodName = notEmpty.and(short5);
Function<Integer, Integer> doubled = x -> x * 2;
Function<Integer, String> doubledText = doubled.andThen(x -> "Результат: " + x);
IO.println(goodName.test("Анна"));
IO.println(goodName.test("Александра"));
IO.println(doubledText.apply(21));
}Результат:
true
false
Результат: 42Частая ошибка
Интерфейс с двумя абстрактными методами не может быть типом лямбды — Java не поймёт, какой из них она реализует. С аннотацией @FunctionalInterface ошибка видна сразу в объявлении:
@FunctionalInterface
interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
}
void main() {
}Результат:
Main.java:1: error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
Calculator is not a functional interface
multiple non-overriding abstract methods found in interface CalculatorОставьте в интерфейсе один абстрактный метод, а остальные сделайте default — или разделите его на два интерфейса.