4. Funkcje lambda w Javie 8
4.1. Przykład 01 – Interfejsy funkcjonalne i lambda
![]() |
Rozważmy następujący kod:
package dvp.java8.lambdas;
public class Exemple01 {
public static void main(String[] args) {
// klasy anonimowe
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);
}
};
// lambdy
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);
};
// aplikacja
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);
}
- wiersze 41–44: definiują interfejs funkcjonalny I1. Interfejs funkcjonalny to interfejs posiadający tylko jedną metodę. Nie jest on powiązany z obecnością adnotacji [@FunctionalInterface] w wierszu 41, która jest opcjonalna;
- wiersze 46–49: drugi interfejs funkcjonalny I2;
- wiersze 6–19: interfejsy I1 i I2 są zaimplementowane za pomocą klas anonimowych, co było najczęstszym rozwiązaniem przed wprowadzeniem funkcji lambda;
- wiersze 21–29: interfejsy I1 i I2 są zaimplementowane za pomocą funkcji lambda;
- wiersze 7–12: implementacja interfejsu I1 za pomocą klasy anonimowej. Składnia implementacji interfejsu I za pomocą klasy anonimowej jest następująca:
gdzie m1, m2, ... to metody zdefiniowane przez interfejs I.
- wiersze 14–19: implementacja interfejsu I2 za pomocą klasy anonimowej;
- wiersz 22: implementacja interfejsu I1 za pomocą funkcji lambda. Wykorzystujemy tutaj fakt, że interfejs funkcjonalny ma tylko jedną metodę. Funkcja lambda implementuje zatem tę jedyną metodę M. Jej składnia jest następująca:
Typy T1, T2, Tn można pominąć, jeśli kompilator może je wywnioskować z kontekstu (wnioskowanie typu).
- wiersz 22: implementacja metody [I1.doSomething] o sygnaturze:
void doSomething();
[doSomething] to metoda, która nie ma żadnych parametrów i nie zwraca wyniku. Jej implementację lambda można zapisać tak jak w wierszu 22 lub tak jak w wierszach 24–26, tzn. można umieścić kody funkcji lambda w nawiasach klamrowych. Jeśli kod ten zawiera tylko jedną instrukcję, jak w tym przypadku, nawiasy klamrowe można pominąć;
- wiersz 23: implementacja metody [I1.getSomething] o sygnaturze:
String getSomething(double value);
[getSomething] przyjmuje parametr typu [double] i zwraca wynik typu [String]. Jej implementacja lambda może być taka jak w wierszu 23 lub taka jak w wierszach 27–29. W implementacji z linii 23:
- typ parametru [value] jest pominięty. W takim przypadku zostanie użyty typ [double] znaleziony w sygnaturze funkcji [getSomething];
- kod lambda nie jest ujęty w nawiasy. Wynikiem lambda jest wówczas wartość jedynego wyrażenia w tym kodzie, w tym przypadku: String.format("ib2.lambda(%s)", value);
W implementacji w wierszach 27–29:
- wyraźnie deklaruje się typ parametru [value];
- Do zwrócenia wyniku funkcji lambda używa się [return]. W tym przypadku należy użyć nawiasów klamrowych;
- wiersze 32–37: wywołuje się różne funkcje anonimowe i lambda;
Otrzymany wynik jest następujący:
4.2. Przykład 02 – interfejs funkcjonalny Predicate<T>
![]() |
W większości przypadków mamy do czynienia z interfejsami funkcjonalnymi bibliotek, a nie z interfejsami funkcjonalnymi, które sami definiujemy. W tym przypadku interesuje nas interfejs funkcjonalny [Predicate] zdefiniowany w pakiecie [java.util.function], który zawiera większość interfejsów funkcjonalnych z Javy 8. Jest on zdefiniowany w następujący sposób:

Wspomnieliśmy, że interfejs funkcjonalny ma tylko jedną metodę. Tutaj jednak są cztery. Kolejną innowacją wprowadzoną przez Javę 8 było pojęcie metody domyślnej w interfejsie, oznaczonej słowem kluczowym [default]. Mamy tu trzy takie metody. Cechą charakterystyczną tych metod jest to, że mają one domyślną implementację. Nie ma zatem obowiązku, aby klasa implementująca interfejs z metodami domyślnymi musiała je implementować. W ten sposób klasa, która chce implementować interfejs [Predicate], musi obowiązkowo zaimplementować tylko jedną metodę: metodę [test]. Interfejs [Predicate] jest zatem interfejsem funkcjonalnym. Można zatem stwierdzić, że interfejs funkcjonalny to taki, którego implementacja zawiera tylko jedną obowiązkową metodę. Jeśli posiada więcej niż jedną metodę, pozostałe muszą mieć słowo kluczowe [default].
Metoda [Predicate<T>.test] przyjmuje parametr typu T i zwraca wartość logiczną. Interfejs ten jest zazwyczaj wykorzystywany do filtrowania kolekcji. Aby zilustrować jego zastosowanie, posłużymy się następującymi danymi:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Personne {
public enum Sexe {HOMME,FEMME};
// dane
private String nom;
private int age;
private double poids;
private Sexe sexe;
// konstruktory
public Personne() {
}
public Personne(String nom, Sexe sexe, int age, double poids) {
this.nom = nom;
this.sexe=sexe;
this.age = age;
this.poids = poids;
}
// metody pobierające i ustawiające
...
// toString
@Override
public String toString(){
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- wiersze 32–38: metoda [toString] zwraca ciąg znaków jSON danej osoby;
Klasa [Personnes] definiuje listę trzech osób:
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();
}
}
}
- wiersze 10–11: lista trzech osób;
- wiersze 13–15: metoda statyczna służąca do uzyskania tej listy;
- wiersze 17–23: metoda statyczna umożliwiająca uzyskanie ciągu znaków jSON z listy osób przekazanej jako parametr;
Dane te zostaną wykorzystane przez następujący kod:
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) {
// predykat zaimplementowany przez klasę anonimową
Predicate<Personne> filterPoids=new Predicate<Personne>() {
@Override
public boolean test(Personne personne) {
return personne.getPoids()<50;
}
};
// predykat zaimplementowany przez lambda
Predicate<Personne> filterAge = p -> p.getAge() < 28;
// lista osób
List<Personne> personnes = Personnes.get();
// wyświetlenia
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] filtruje listę [personnes]
List<Personne> personnesFiltrées = new ArrayList<>();
for (Personne p : personnes) {
if (filter.test(p)) {
personnesFiltrées.add(p);
}
}
return personnesFiltrées; }
}
- wiersze 13–18: implementacja interfejsu Predicate<Osoba> za pomocą klasy anonimowej. Jest to filtr oparty na wadze osoby;
- wiersz 20: implementacja interfejsu Predicate<Osoba> za pomocą funkcji lambda. Jest to filtr oparty na wieku osoby. Zgodnie z powyższym wyjaśnieniem można by go również zapisać w następujący sposób:
Predicate<Personne> filterAge = (Personne p) -> {
return (p.getAge() < 28);
};
ale wersja z linii 20 jest bardziej zwięzła. Typ parametru p jest wywnioskowany z kontekstu. W tym miejscu tworzony jest typ [Predicate<Personne>]. Zaimplementowana metoda ma zatem sygnaturę [boolean test(Personne param)]. Zatem typ domyślny dla p w wierszu 20 to typ [Personne];
- wiersz 22: pobieramy wstępnie zdefiniowaną listę osób;
- wiersz 24: filtruje się je według wieku;
- wiersz 25: filtruje się je według wagi. W obu przypadkach wyświetla się ciąg znaków jSON z tak przefiltrowanej listy;
- wiersze 28–37: metoda statyczna, która
- przyjmuje jako parametry: listę osób do przefiltrowania oraz filtr. Ten ostatni jest instancją interfejsu [Predicate<Personne>]. W sensie przenośnym, zarówno tutaj, jak i w innych miejscach dokumentu, przez instancję interfejsu I rozumiemy instancję klasy C implementującej I;
- zwraca jako wynik tak przefiltrowaną listę;
- wiersz 32: wykorzystuje się metodę [test] z interfejsu [Predicate]. W zależności od filtru przekazanego do tej metody, metoda [test] będzie:
return personne.getPoids()<50;
lub
return p.getAge() < 28
Wywołanie klasy [Exemple02] daje następujący wynik:
4.3. Przykład 03 – interfejs funkcjonalny Function<T,R>
![]() |
Interfejs funkcjonalny Function<T,R> jest zdefiniowany w następujący sposób:

Jedyna metoda tego interfejsu ma sygnaturę R apply(T t). Zazwyczaj służy ona do tworzenia nowego typu Collection<R> na podstawie typu Collection<T>. Aby zilustrować działanie tego interfejsu, wykorzystamy następujący kod:
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 {
// implementacja z wykorzystaniem klasy anonimowej
Function<Personne, String> mapToName = new Function<Personne, String>() {
@Override
public String apply(Personne personne) {
return personne.getNom();
}
};
// implementacja z użyciem lambda
Function<Personne, Integer> mapToAge = p -> p.getAge();
// lista osób
List<Personne> personnes = Personnes.get();
// jSON
ObjectMapper jsonMapper = new ObjectMapper();
// wyświetlenia
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
}
// konwersja List<Osoba> --> 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;
}
}
- wiersze 15–20: implementujemy interfejs [Function<Personne, String>] za pomocą klasy anonimowej, która dokona transformacji Personne --> String;
- wiersz 22: implementujemy interfejs [Function<Personne, Integer>] za pomocą funkcji lambda, która przeprowadzi transformację Personne --> Integer;
- wiersze 33–39: metoda statyczna, która
- przyjmuje dwa parametry: pierwszy typu List<Personne> to lista osób do przekształcenia. Drugi typu Function<Osoba, T> to funkcja, która na podstawie każdej osoby z listy tworzy obiekt typu T;
- zwraca wynik typu List<T>, gdzie każdy element pochodzi z transformacji Osoba -> T;
- wiersze 35–37: stosuje się transformację Personne -> T. Jeśli drugim parametrem metody jest obiekt typu [mapToName], zostanie przeprowadzona transformacja Personne -> String. Jeśli jest to obiekt [mapToAge], zostanie przeprowadzona transformacja Osoba -> Integer;
Otrzymany wynik jest następujący:
4.4. Przykład-04 – interfejs funkcjonalny Consumer<T>
![]() |
Interfejs funkcjonalny Consumer<T> jest zdefiniowany w następujący sposób:

Jedyna metoda tego interfejsu ma sygnaturę: void accept(T t). Metoda ta przetwarza (konsumuje) swój parametr i nie zwraca żadnego wyniku. Aby to zilustrować, użyjemy następującego kodu:
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) {
// lista osób
List<Personne> personnes = Personnes.get();
// anonimowa implementacja
Consumer<Personne> consumerAge = new Consumer<Personne>() {
@Override
public void accept(Personne personne) {
System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// implementacja lambda
Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());
// wyświetlenia
for (Personne p : personnes) {
consumerAge.accept(p);
}
System.out.println("--------");
for (Personne p : personnes) {
consumerPoids.accept(p);
}
}
}
- wiersze 14–19: interfejs [Consumer<Personne>] jest zaimplementowany przez klasę anonimową, której metoda [accept] wyświetla imię i wiek osoby;
- wiersz 21: interfejs [Consumer<Personne>] jest zaimplementowany przez funkcję lambda, której domyślna metoda [accept] wyświetla imię i wagę osoby;
- wiersze 23–25: lista osób jest wykorzystywana przez implementację [consumerAge];
- wiersze 27–29: lista osób jest przetwarzana przez implementację [consumerPoids];
Wyniki są następujące:
4.5. Przykład-05 – interfejs funkcjonalny BiConsumer<T,U>
![]() |
Interfejs funkcjonalny BiConsumer<T,U> jest zdefiniowany w następujący sposób:

Jedyna metoda tego interfejsu ma sygnaturę: void accept(T t, U u). Przyjmuje ona typ T wraz z dodatkową informacją U u. Jej zastosowanie zilustrujemy za pomocą poniższego kodu:
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) {
// lista osób
List<Personne> personnes = Personnes.get();
// implementacja anonimowa
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());
}
};
// implementacja lambda
BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
p.setPoids(p.getPoids() + i);
System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
};
// wyświetlenia
for (Personne p : personnes) {
biconsumerAge.accept(p, 100);
}
System.out.println("--------");
for (Personne p : personnes) {
biconsumerPoids.accept(p, 200);
}
}
}
- wiersze 14–20: implementacja interfejsu BiConsumer<T,U> za pomocą klasy anonimowej. Metoda [apply] wykorzystuje swój drugi parametr do aktualizacji wieku osoby przekazanej jako pierwszy parametr. Następnie wyświetla wynik;
- wiersze 22–25: implementacja interfejsu BiConsumer<T,U> za pomocą funkcji lambda. Metoda domyślna [apply] wykorzystuje swój drugi parametr do aktualizacji wagi osoby przekazanej jako pierwszy parametr. Następnie wyświetla wynik;
- wiersze 27–29: lista osób jest przetwarzana przy użyciu implementacji [biconsumerAge];
- wiersze 31–33: lista osób jest przetwarzana za pomocą implementacji [biconsumerPoids];
Uzyskane wyniki są następujące:
4.6. Przykład-06 – interfejs funkcjonalny BiFunction<T,U,R>
![]() |
Interfejs funkcjonalny BiFunction<T,U,R> jest zdefiniowany w następujący sposób:

Jedyna metoda tego interfejsu ma sygnaturę: R apply(T t, U u). Metoda ta jest podobna do metody [BiConsumer.apply], jednak podczas gdy ta ostatnia nie zwraca wyniku, metoda [BiFunction.apply] zwraca wynik. Jej zastosowanie zilustrujemy za pomocą następującego kodu:
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) {
// lista osób
List<Personne> personnes = Personnes.get();
// implementacja anonimowa
BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
@Override
public Integer apply(Personne personne, Integer integer) {
return personne.getAge() + integer;
}
};
// implementacja lambda
BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
return p.getPoids() + i;
};
// wyświetlenia
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));
}
}
}
- wiersze 14–19: interfejs BiFunction<Personne, Integer, Integer> jest zaimplementowany za pomocą klasy anonimowej. Metoda [apply] tej klasy zwraca wiek osoby przekazanej jako pierwszy parametr, powiększony o wartość drugiego parametru;
- wiersze 21–23: interfejs BiFunction<Osoba, Integer, Double> jest zaimplementowany za pomocą funkcji lambda. Metoda [apply] zwraca masę osoby przekazanej jako pierwszy parametr powiększoną o wartość drugiego parametru;
- wiersze 25–27: stosuje się implementację [biFunctionAge] do osób;
- wiersze 29–31: stosuje się implementację [biFunctionPoids] do osób;
Uzyskane wyniki są następujące:
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
Oprócz funkcji lambda w Javie 8 wprowadzono typ Stream<T>, który modeluje strumień elementów typu T. Elementy te mogą być poddawane kolejnym transformacjom zaimplementowanym przez funkcje lambda. Jeśli to możliwe i dostępnych jest wiele procesorów, transformacje te mogą czasami przebiegać równolegle.
4.7. Przykład-07 – interfejs funkcjonalny Supplier<T>
![]() |
Interfejs funkcjonalny Supplier<T> jest zdefiniowany w następujący sposób:

Jedyna metoda tego interfejsu ma sygnaturę: T get(), której zadaniem jest zwracanie obiektu typu T.
Zilustrujemy ten interfejs funkcjonalny za pomocą następującego kodu:
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) {
// implementacja anonimowa
Supplier<Personne> supplier = new Supplier<Personne>() {
// lista osób
List<Personne> personnes = Personnes.get();
// implementacja interfejsu
@Override
public Personne get() {
int i = new Random().nextInt(personnes.size());
return personnes.get(i);
}
};
// analiza
for (int i = 0; i < 5; i++) {
affiche(supplier);
}
}
// wyświetlanie osoby
public static void affiche(Supplier<Personne> supplier) {
System.out.println(supplier.get());
}
}
- wiersze 13–28: implementacja typu Supplier<Personne>;
- wiersze 31–33: metoda statyczna [affiche] oczekuje parametru typu Supplier<Personne>;
- wiersze 25–27: uruchomienie instancji Supplier<Personne>;
Otrzymujemy następujące wyniki:






