4. Лямбда-функції в Java 8
4.1. Приклад-01 — Функціональні інтерфейси та лямбда-функції
![]() |
Розглянемо такий код:
package dvp.java8.lambdas;
public class Exemple01 {
public static void main(String[] args) {
// анонімні класи
I1 ia1 = new I1() {
@Override
public void doSomething() {
System.out.println("ia1.doSomething");
}
};
I2 ia2 = new I2() {
@Override
public String getSomething(double value) {
return String.format("ia2.getSomething(%s)", value);
}
};
// лямбда-вирази
I1 ib1 = () -> System.out.println("ib1.lambda");
I2 ib2 = (value) -> String.format("ib2.lambda(%s)", value);
I1 ib3 = () -> {
System.out.println("ib3.lambda");
};
I2 ib4 = (double value) -> {
return String.format("ib4.lambda(%s)", value);
};
// додаток
ia1.doSomething();
System.out.println(ia2.getSomething(4.3));
ib1.doSomething();
System.out.println(ib2.getSomething(5.8));
ib3.doSomething();
System.out.println(ib4.getSomething(10.1));
}
}
@FunctionalInterface
interface I1 {
void doSomething();
}
@FunctionalInterface
interface I2 {
String getSomething(double value);
}
- рядки 41–44: визначають функціональний інтерфейс I1. Функціональний інтерфейс — це інтерфейс, що має лише один метод. Він не пов’язаний із наявністю анотації [@FunctionalInterface] у рядку 41, яка є необов’язковою;
- рядки 46–49: другий функціональний інтерфейс I2;
- рядки 6–19: інтерфейси I1 та I2 реалізовано за допомогою анонімних класів — це було найпоширенішим рішенням до впровадження лямбда-функцій;
- рядки 21–29: інтерфейси I1 та I2 реалізовано за допомогою лямбда-функцій;
- рядки 7–12: реалізація інтерфейсу I1 за допомогою анонімного класу. Синтаксис реалізації інтерфейсу I за допомогою анонімного класу такий:
де m1, m2, ... — методи, визначені інтерфейсом I.
- рядки 14–19: реалізація інтерфейсу I2 за допомогою анонімного класу;
- рядок 22: реалізація інтерфейсу I1 за допомогою лямбда-функції. Тут використовується той факт, що функціональний інтерфейс має лише один метод. Лямбда-функція реалізує цей єдиний метод M. Її синтаксис такий:
Типи T1, T2, Tn можна опустити, якщо компілятор може вивести їх із контексту (виведення типу).
- рядок 22: реалізація методу [I1.doSomething] із сигнатурою:
void doSomething();
[doSomething] — це метод, який не має параметрів і не повертає результату. Його лямбда-реалізацію можна записати так, як у рядку 22, або ж так, як у рядках 24–26, тобто можна поставити фігурні дужки навколо коду лямбда-функції. Якщо цей код містить лише одну інструкцію, як у цьому випадку, ці фігурні дужки можна опустити;
- рядок 23: реалізація методу [I1.getSomething] із сигнатурою:
String getSomething(double value);
[getSomething] приймає параметр типу [double] і повертає результат типу [String]. Його лямбда-реалізація може бути такою, як у рядку 23, або такою, як у рядках 27–29. У реалізації з рядка 23:
- тип параметра [value] опущено. У такому разі буде використано тип [double], знайдений у сигнатурі [getSomething];
- код лямбда-виразу не взято в дужки. У результаті лямбда-вираз дорівнює значенню єдиного виразу в цьому коді, а саме: String.format("ib2.lambda(%s)", value);
У реалізації рядків 27–29:
- явно оголошується тип параметра [value];
- Для повернення результату лямбда-виразу використовується [return]. У цьому випадку потрібно вказати фігурні дужки;
- рядки 32–37: викликаються різні анонімні функції та лямбда-вирази;
Отримано такий результат:
4.2. Приклад-02 — функціональний інтерфейс Predicate<T>
![]() |
Здебільшого ми маємо справу з функціональними інтерфейсами бібліотек, а не з тими, які ми визначаємо самостійно. Тут нас цікавить функціональний інтерфейс [Predicate], визначений у пакеті [java.util.function], який об’єднує більшість функціональних інтерфейсів Java 8. Він визначений таким чином:

Ми вже зазначали, що функціональний інтерфейс має лише один метод. Однак тут їх чотири. Ще одним нововведенням Java 8 стало введення поняття методу за замовчуванням в інтерфейсі, що позначається ключовим словом [default]. Тут ми маємо три таких методи. Особливістю цих методів є наявність реалізації за замовчуванням. Отже, клас, що реалізує інтерфейс із методами за замовчуванням, не зобов’язаний реалізовувати ці методи. Таким чином, клас, який хоче реалізувати інтерфейс [Predicate], повинен обов’язково реалізувати лише один метод: метод [test]. Отже, інтерфейс [Predicate] є саме функціональним інтерфейсом. Таким чином, функціональний інтерфейс — це інтерфейс, реалізація якого містить лише один обов’язковий метод. Якщо він має більше одного методу, інші методи повинні мати ключове слово [default].
Метод [Predicate<T>.test] приймає параметр типу T і повертає логічне значення. Цей інтерфейс зазвичай використовується для фільтрування колекцій. Щоб проілюструвати його використання, ми скористаємося такими даними:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Personne {
public enum Sexe {HOMME,FEMME};
// дані
private String nom;
private int age;
private double poids;
private Sexe sexe;
// конструктори
public Personne() {
}
public Personne(String nom, Sexe sexe, int age, double poids) {
this.nom = nom;
this.sexe=sexe;
this.age = age;
this.poids = poids;
}
// геттери та сеттери
...
// toString
@Override
public String toString(){
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- рядки 32–38: метод [toString] повертає рядок jSON, що відповідає особі;
Клас [Personnes] визначає список із 3 осіб:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
public class Personnes {
private static List<Personne> personnes = Arrays.asList(new Personne("jean", Personne.Sexe.HOMME, 20, 70),
new Personne("marie", Personne.Sexe.FEMME, 10, 30), new Personne("camille", Personne.Sexe.FEMME, 30, 55));
public static List<Personne> get() {
return personnes;
}
public static String toString(List<Personne> liste) {
try {
return new ObjectMapper().writeValueAsString(liste);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- рядки 10–11: список із 3 осіб;
- рядки 13–15: статичний метод для отримання цього списку;
- рядки 17–23: статичний метод, що дозволяє отримати рядок jSON зі списку осіб, переданого як параметр;
Ці дані будуть оброблятися наступним кодом:
package dvp.java8.lambdas;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple02 {
public static void main(String[] args) {
// предикат, реалізований анонімним класом
Predicate<Personne> filterPoids=new Predicate<Personne>() {
@Override
public boolean test(Personne personne) {
return personne.getPoids()<50;
}
};
// предикат, реалізований за допомогою лямбда-виразу
Predicate<Personne> filterAge = p -> p.getAge() < 28;
// список осіб
List<Personne> personnes = Personnes.get();
// перегляди
System.out.println(Personnes.toString(filterPersonnes(personnes, filterAge)));
System.out.println(Personnes.toString(filterPersonnes(personnes, filterPoids)));
}
private static List<Personne> filterPersonnes(List<Personne> personnes, Predicate<Personne> filter) {
// [filter] фільтрує список [personnes]
List<Personne> personnesFiltrées = new ArrayList<>();
for (Personne p : personnes) {
if (filter.test(p)) {
personnesFiltrées.add(p);
}
}
return personnesFiltrées; }
}
- рядки 13–18: реалізація інтерфейсу Predicate<Personne> за допомогою анонімного класу. Це фільтр за вагою особи;
- рядок 20: реалізація інтерфейсу Predicate<Personne> за допомогою лямбда-функції. Це фільтр за віком особи. Згідно з тим, що було сказано, її також можна було б написати таким чином:
Predicate<Personne> filterAge = (Personne p) -> {
return (p.getAge() < 28);
};
але варіант у рядку 20 є більш лаконічним. Тип параметра p виводиться з контексту. Тут створюється тип [Predicate<Personne>]. Тоді сигнатура реалізованого методу має вигляд [boolean test(Personne param)]. Отже, неявним типом p у рядку 20 є тип [Personne];
- рядок 22: отримуємо заздалегідь визначений список осіб;
- рядок 24: їх фільтрують за віком;
- рядок 25: фільтруємо їх за вагою. В обох випадках виводимо рядок jSON із відфільтрованого списку;
- рядки 28–37: статичний метод, який
- приймає як параметри: список осіб, що підлягають фільтруванню, та фільтр. Останній є екземпляром інтерфейсу [Predicate<Personne>]. У неформальному вживанні терміна тут та в інших частинах документа під екземпляром інтерфейсу I мається на увазі екземпляр класу C, що реалізує I;
- повертає у результаті відфільтрований список;
- рядок 32: використовується метод [test] інтерфейсу [Predicate]. Залежно від фільтра, переданого методу, метод [test] буде:
return personne.getPoids()<50;
або
return p.getAge() < 28
Виконання класу [Exemple02] дає такий результат:
4.3. Приклад-03 — функціональний інтерфейс Function<T,R>
![]() |
Функціональний інтерфейс Function<T,R> визначається наступним чином:

Єдиний метод інтерфейсу має сигнатуру R apply(T t). Зазвичай його використовують для створення нового типу Collection<R> на основі типу Collection<T>. Для ілюстрації цього інтерфейсу ми використаємо такий код:
package dvp.java8.lambdas;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dvp.data.Personne;
import dvp.data.Personnes;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class Exemple03 {
public static void main(String[] args) throws JsonProcessingException {
// реалізація з використанням анонімного класу
Function<Personne, String> mapToName = new Function<Personne, String>() {
@Override
public String apply(Personne personne) {
return personne.getNom();
}
};
// реалізація з використанням лямбда-виразу
Function<Personne, Integer> mapToAge = p -> p.getAge();
// список осіб
List<Personne> personnes = Personnes.get();
// jSON
ObjectMapper jsonMapper = new ObjectMapper();
// відображення
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
}
// перетворення List<Personne> --> List<T>
private static <T> List<T> mapPersonnes(List<Personne> personnes, Function<Personne, T> mapper) {
List<T> maps = new ArrayList<>();
for (Personne p : personnes) {
maps.add(mapper.apply(p));
}
return maps;
}
}
- рядки 15–20: реалізуємо інтерфейс [Function<Personne, String>] за допомогою анонімного класу, який здійснюватиме перетворення Personne --> String;
- рядок 22: реалізуємо інтерфейс [Function<Personne, Integer>] за допомогою лямбда-функції, яка здійснюватиме перетворення Personne --> Integer;
- рядки 33–39: статичний метод, який
- приймає два параметри: перший типу List<Personne> — це список осіб, які підлягають перетворенню. Другий типу Function<Personne, T> — це функція, яка на основі кожної особи зі списку створює об’єкт типу T;
- повертає у результаті тип List<T>, де кожен елемент є результатом перетворення «Особа» → T;
- рядки 35–37: застосовується перетворення «Особа» → T. Якщо другим параметром методу є об’єкт типу [mapToName], то буде виконано перетворення «Особа» → String. Якщо це об’єкт [mapToAge], буде виконано перетворення «Особа» → «Ціле»;
Отриманий результат такий:
4.4. Приклад-04 — функціональний інтерфейс Consumer<T>
![]() |
Функціональний інтерфейс Consumer<T> визначається наступним чином:

Єдиний метод інтерфейсу має сигнатуру: void accept(T t). Цей метод обробляє (споживає) свій параметр і не повертає жодного результату. Для ілюстрації ми використаємо такий код:
package dvp.java8.lambdas;
import java.util.List;
import java.util.function.Consumer;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple04 {
public static void main(String[] args) {
// список осіб
List<Personne> personnes = Personnes.get();
// анонімна реалізація
Consumer<Personne> consumerAge = new Consumer<Personne>() {
@Override
public void accept(Personne personne) {
System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// реалізація лямбда
Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());
// виведення
for (Personne p : personnes) {
consumerAge.accept(p);
}
System.out.println("--------");
for (Personne p : personnes) {
consumerPoids.accept(p);
}
}
}
- рядки 14–19: інтерфейс [Consumer<Personne>] реалізовано анонімним класом, метод [accept] якого виводить ім’я та вік особи;
- рядок 21: інтерфейс [Consumer<Personne>] реалізовано за допомогою лямбда-функції, неявний метод якої [accept] виводить ім’я та вагу людини;
- рядки 23–25: список осіб використовується реалізацією [consumerAge];
- рядки 27–29: список осіб використовується реалізацією [consumerPoids];
Результати такі:
4.5. Приклад-05 — функціональний інтерфейс BiConsumer<T,U>
![]() |
Функціональний інтерфейс BiConsumer<T,U> визначено таким чином:

Єдиний метод інтерфейсу має сигнатуру: void accept(T t, U u). Він приймає тип T із додатковою інформацією U u. Проілюструємо його використання за допомогою такого коду:
package dvp.java8.lambdas;
import dvp.data.Personne;
import dvp.data.Personnes;
import java.util.List;
import java.util.function.BiConsumer;
public class Exemple05 {
public static void main(String[] args) {
// список осіб
List<Personne> personnes = Personnes.get();
// анонімна реалізація
BiConsumer<Personne, Integer> biconsumerAge = new BiConsumer<Personne, Integer>() {
@Override
public void accept(Personne personne, Integer integer) {
personne.setAge(personne.getAge() + integer);
System.out.printf("age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// реалізація лямбда
BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
p.setPoids(p.getPoids() + i);
System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
};
// перегляди
for (Personne p : personnes) {
biconsumerAge.accept(p, 100);
}
System.out.println("--------");
for (Personne p : personnes) {
biconsumerPoids.accept(p, 200);
}
}
}
- рядки 14–20: реалізація інтерфейсу BiConsumer<T,U> за допомогою анонімного класу. Метод [apply] використовує свій другий параметр для оновлення віку особи, переданої як перший параметр. Потім він виводить результат;
- рядки 22–25: реалізація інтерфейсу BiConsumer<T,U> за допомогою лямбда-функції. Неявний метод [apply] використовує свій другий параметр для оновлення ваги особи, переданої як перший параметр. Потім він виводить результат;
- рядки 27–29: список осіб обробляється за допомогою реалізації [biconsumerAge];
- рядки 31–33: список осіб обробляється за допомогою реалізації [biconsumerPoids];
Отримано такі результати:
4.6. Приклад-06 — функціональний інтерфейс BiFunction<T,U,R>
![]() |
Функціональний інтерфейс BiFunction<T,U,R> визначається наступним чином:

Єдиний метод інтерфейсу має сигнатуру: R apply(T t, U u). Цей метод схожий на метод [BiConsumer.apply], але на відміну від останнього, який не повертає результату, метод [BiFunction.apply] повертає результат. Проілюструємо його використання за допомогою такого коду:
package dvp.java8.lambdas;
import java.util.List;
import java.util.function.BiFunction;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple06 {
public static void main(String[] args) {
// список осіб
List<Personne> personnes = Personnes.get();
// анонімна реалізація
BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
@Override
public Integer apply(Personne personne, Integer integer) {
return personne.getAge() + integer;
}
};
// реалізація лямбда
BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
return p.getPoids() + i;
};
// перегляди
for (Personne p : personnes) {
System.out.printf("age de %s = %s%n", p.getNom(), biFunctionAge.apply(p, 100));
}
System.out.println("--------");
for (Personne p : personnes) {
System.out.printf("poids de %s = %s%n", p.getNom(), biFunctionPoids.apply(p, 200));
}
}
}
- рядки 14–19: інтерфейс BiFunction<Personne, Integer, Integer> реалізовано за допомогою анонімного класу. Метод [apply] цього класу повертає вік особи, переданої як перший параметр, збільшений на значення другого параметра;
- рядки 21–23: інтерфейс BiFunction<Personne, Integer, Double> реалізовано за допомогою лямбда-функції. Метод [apply] повертає вагу особи, переданої в першому параметрі, збільшену на значення другого параметра;
- рядки 25–27: до осіб застосовується реалізація [biFunctionAge];
- рядки 29–31: до осіб застосовується реалізація [biFunctionPoids];
Отримано такі результати:
age de jean = 120
age de marie = 110
age de camille = 130
--------
poids de jean = 270.0
poids de marie = 230.0
poids de camille = 255.0
Окрім лямбда-функцій, у Java 8 було введено тип Stream<T>, який моделює потік елементів типу T. Ці елементи можуть піддаватися послідовним перетворенням, реалізованим за допомогою лямбда-функцій. Якщо це можливо і є кілька процесорів, ці перетворення іноді можуть виконуватися паралельно.
4.7. Приклад-07 — функціональний інтерфейс Supplier<T>
![]() |
Функціональний інтерфейс Supplier<T> визначається наступним чином:

Єдиний метод інтерфейсу має сигнатуру: T get(), роль якого полягає в наданні об’єкта типу T.
Проілюструємо цей функціональний інтерфейс за допомогою такого коду:
package dvp.java8.lambdas;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
import dvp.data.Personne;
import dvp.data.Personnes;
public class Exemple07 {
public static void main(String[] args) {
// анонімна реалізація
Supplier<Personne> supplier = new Supplier<Personne>() {
// список осіб
List<Personne> personnes = Personnes.get();
// реалізація інтерфейсу
@Override
public Personne get() {
int i = new Random().nextInt(personnes.size());
return personnes.get(i);
}
};
// обробка
for (int i = 0; i < 5; i++) {
affiche(supplier);
}
}
// перегляд особи
public static void affiche(Supplier<Personne> supplier) {
System.out.println(supplier.get());
}
}
- рядки 13–28: реалізація типу Supplier<Personne>;
- рядки 31–33: статичний метод [affiche] очікує параметр типу Supplier<Personne>;
- рядки 25–27: виконання екземпляра Supplier<Personne>;
Отримуємо такі результати:






