4. Expressões Lambda do Java 8
4.1. Exemplo-01 - Interfaces funcionais e lambdas
![]() |
Considere o seguinte código:
package dvp.java8.lambdas;
public class Exemple01 {
public static void main(String[] args) {
// anonymous classes
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);
}
};
// lambdas
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);
};
// app
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);
}
- linhas 41–44: definem uma interface funcional I1. Uma interface funcional é uma interface com apenas um método. Não depende da presença da anotação [@FunctionalInterface] na linha 41, que é opcional;
- linhas 46–49: uma segunda interface funcional I2;
- linhas 6–19: as interfaces I1 e I2 são implementadas utilizando classes anónimas, a solução mais comum antes da introdução das funções lambda;
- linhas 21–29: as interfaces I1 e I2 são implementadas utilizando funções lambda;
- linhas 7–12: implementação da interface I1 com uma classe anónima. A sintaxe para implementar uma interface I com uma classe anónima é a seguinte:
onde m1, m2, ... são os métodos definidos pela interface I.
- linhas 14–19: implementação da interface I2 com uma classe anónima;
- linha 22: implementação da interface I1 com uma função lambda. Aqui, tiramos partido do facto de a interface funcional ter apenas um método. A função lambda implementa então este único método M. A sua sintaxe é a seguinte:
Os tipos T1, T2, Tn podem ser omitidos se o compilador os conseguir inferir a partir do contexto (inferência de tipos).
- linha 22: implementação do método [I1.doSomething] com assinatura:
void doSomething();
[doSomething] é um método que não tem parâmetros e não retorna nenhum resultado. A sua implementação lambda pode ser escrita como na linha 22 ou como nas linhas 24–26, ou seja, podem ser colocadas chaves à volta do código da função lambda. Se este código contiver apenas uma única instrução, como aqui, estas chaves podem ser omitidas;
- Linha 23: Implementação do método [I1.getSomething] com a seguinte assinatura:
String getSomething(double value);
[getSomething] recebe um parâmetro do tipo [double] e devolve um resultado do tipo [String]. A sua implementação lambda pode ser a da linha 23 ou a das linhas 27–29. Na implementação da linha 23:
- o tipo do parâmetro [value] é omitido. Será então utilizado o tipo [double] encontrado na assinatura de [getSomething];
- o código lambda não está entre parênteses. O resultado do lambda é, então, o valor da única expressão nesse código, aqui: String.format("ib2.lambda(%s)", value);
Na implementação das linhas 27–29:
- o tipo do parâmetro [value] é explicitamente declarado;
- usamos um [return] para devolver o resultado da lambda. Neste caso, devem ser utilizadas chaves;
- Linhas 32–37: chamamos as várias funções anónimas e lambdas;
O resultado obtido é o seguinte:
4.2. Exemplo-02 - A interface funcional Predicate<T>
![]() |
Na maioria das vezes, lidamos com interfaces funcionais de bibliotecas, em vez de interfaces funcionais que nós próprios definimos. Aqui, estamos interessados na interface funcional [Predicate] definida no pacote [java.util.function], que contém a maioria das interfaces funcionais do Java 8. Está definida da seguinte forma:

Mencionámos que uma interface funcional tem apenas um método. Aqui, no entanto, existem quatro. Outra inovação introduzida pelo Java 8 foi o conceito de método padrão numa interface, marcado pela palavra-chave [default]. Aqui temos três métodos desse tipo. Estes métodos têm a particularidade de possuírem uma implementação padrão. Não há, portanto, qualquer exigência de que uma classe que implemente uma interface com métodos padrão os implemente. Assim, uma classe que pretenda implementar a interface [Predicate] tem apenas um método que deve implementar: o método [test]. A interface [Predicate] é, portanto, de facto, uma interface funcional. Podemos assim dizer que uma interface funcional é uma interface cuja implementação contém apenas um método obrigatório. Se tiver mais do que um método, os outros devem ter a palavra-chave [default].
O método [Predicate<T>.test] recebe um parâmetro do tipo T e devolve um booleano. Esta interface é geralmente utilizada para filtrar coleções. Para ilustrar a sua utilização, utilizaremos os seguintes dados:
package dvp.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Personne {
public enum Sexe {HOMME,FEMME};
// data
private String nom;
private int age;
private double poids;
private Sexe sexe;
// manufacturers
public Personne() {
}
public Personne(String nom, Sexe sexe, int age, double poids) {
this.nom = nom;
this.sexe=sexe;
this.age = age;
this.poids = poids;
}
// getters and setters
...
// toString
@Override
public String toString(){
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
return e.getMessage();
}
}
}
- linhas 32–38: o método [toString] devolve a cadeia JSON da pessoa;
A classe [People] define uma lista de 3 pessoas:
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();
}
}
}
- linhas 10–11: a lista de 3 pessoas;
- linhas 13–15: um método estático para recuperar esta lista;
- linhas 17–23: um método estático para obter a cadeia JSON de uma lista de pessoas passada como parâmetro;
Estes dados serão utilizados pelo código seguinte:
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 implemented by anonymous class
Predicate<Personne> filterPoids=new Predicate<Personne>() {
@Override
public boolean test(Personne personne) {
return personne.getPoids()<50;
}
};
// predicate implemented by a lambda
Predicate<Personne> filterAge = p -> p.getAge() < 28;
// list of persons
List<Personne> personnes = Personnes.get();
// displays
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] filters the [people] list
List<Personne> personnesFiltrées = new ArrayList<>();
for (Personne p : personnes) {
if (filter.test(p)) {
personnesFiltrées.add(p);
}
}
return personnesFiltrées; }
}
- linhas 13–18: implementação da interface Predicate<Person> utilizando uma classe anónima. Trata-se de um filtro baseado no peso da pessoa;
- linha 20: implementação da interface Predicate<Person> utilizando uma função lambda. Trata-se de um filtro baseado na idade da pessoa. Com base no que foi dito, também poderia ter sido escrito da seguinte forma:
Predicate<Personne> filterAge = (Personne p) -> {
return (p.getAge() < 28);
};
mas a versão na linha 20 é mais concisa. O tipo do parâmetro p é inferido a partir do contexto. Aqui, estamos a construir um tipo [Predicate<Person>]. O método implementado tem então a assinatura [boolean test(Person param)]. Portanto, o tipo implícito de p na linha 20 é o tipo [Person];
- linha 22: recuperamos uma lista predefinida de pessoas;
- linha 24: filtramo-las por idade;
- linha 25: filtramo-las por peso. Em ambos os casos, exibimos a cadeia JSON da lista filtrada;
- linhas 28–37: um método estático que
- recebe como parâmetros: uma lista de pessoas a filtrar e o filtro. O filtro é uma instância da interface [Predicate<Person>]. Por conveniência, referimo-nos aqui e noutras partes do documento a uma instância de uma interface I como uma instância de uma classe C que implementa I;
- retorna a lista filtrada como resultado;
- linha 32: utilizamos o método [test] da interface [Predicate]. Dependendo do filtro passado ao método, o método [test] será:
return personne.getPoids()<50;
ou
return p.getAge() < 28
A execução da classe [Exemple02] produz o seguinte resultado:
4.3. Exemplo-03 - A Interface Funcional Função<T,R>
![]() |
A interface funcional Function<T,R> é definida da seguinte forma:

O único método da interface tem a assinatura R apply(T t). É geralmente utilizado para criar um novo tipo Collection<R> a partir de um tipo Collection<T>. Para ilustrar esta interface, utilizaremos o seguinte código:
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 {
// implementation with anonymous class
Function<Personne, String> mapToName = new Function<Personne, String>() {
@Override
public String apply(Personne personne) {
return personne.getNom();
}
};
// implementation with lambda
Function<Personne, Integer> mapToAge = p -> p.getAge();
// list of persons
List<Personne> personnes = Personnes.get();
// jSON
ObjectMapper jsonMapper = new ObjectMapper();
// displays
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
}
// transformation List<Person> --> 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;
}
}
- linhas 15–20: implementamos a interface [Function<Person, String>] com uma classe anónima que realiza a transformação de Pessoa → String;
- linha 22: implementamos a interface [Function<Person, Integer>] com uma função lambda que realiza a transformação de Person para Integer;
- Linhas 33–39: um método estático que
- recebe dois parâmetros: o primeiro, do tipo List<Person>, é uma lista de pessoas a serem transformadas. O segundo, do tipo Function<Person, T>, é uma função que recebe cada pessoa da lista e cria um objeto do tipo T;
- retorna uma List<T> onde cada elemento resulta da transformação de Pessoa para T;
- linhas 35–37: a transformação Pessoa -> T é aplicada. Se o segundo parâmetro do método for o objeto [mapToName], é realizada uma transformação Pessoa -> String. Se for o objeto [mapToAge], é realizada uma transformação Pessoa -> Integer;
O resultado é o seguinte:
4.4. Exemplo-04 - A interface funcional Consumer<T>
![]() |
A interface funcional Consumer<T> é definida da seguinte forma:

O único método da interface tem a assinatura: void accept(T t). Este método processa (consome) o seu parâmetro e não devolve qualquer resultado. Para ilustrar isto, utilizaremos o seguinte código:
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 of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
Consumer<Personne> consumerAge = new Consumer<Personne>() {
@Override
public void accept(Personne personne) {
System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// immplémentaton lambda
Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());
// displays
for (Personne p : personnes) {
consumerAge.accept(p);
}
System.out.println("--------");
for (Personne p : personnes) {
consumerPoids.accept(p);
}
}
}
- linhas 14–19: a interface [Consumer<Person>] é implementada por uma classe anónima cujo método [accept] exibe o nome e a idade da pessoa;
- linha 21: a interface [Consumer<Person>] é implementada por uma função lambda cujo método implícito [accept] exibe o nome e o peso da pessoa;
- linhas 23–25: a lista de pessoas é consumida pela implementação [consumerAge];
- linhas 27–29: a lista de pessoas é consumida pela implementação [consumerWeight];
Os resultados são os seguintes:
4.5. Exemplo-05 - A interface funcional BiConsumer<T,U>
![]() |
A interface funcional BiConsumer<T,U> é definida da seguinte forma:

O único método da interface tem a assinatura: void accept(T t, U u). Ele consome o tipo T com a informação adicional U u. Iremos ilustrar a sua utilização com o seguinte código:
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 of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
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());
}
};
// lambda implementation
BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
p.setPoids(p.getPoids() + i);
System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
};
// displays
for (Personne p : personnes) {
biconsumerAge.accept(p, 100);
}
System.out.println("--------");
for (Personne p : personnes) {
biconsumerPoids.accept(p, 200);
}
}
}
- linhas 14–20: implementação da interface BiConsumer<T,U> utilizando uma classe anónima. O método [apply] utiliza o seu segundo parâmetro para atualizar a idade da pessoa passada como primeiro parâmetro. Em seguida, apresenta o resultado;
- linhas 22–25: implementação da interface BiConsumer<T,U> utilizando uma função lambda. O método implícito [apply] utiliza o seu segundo parâmetro para atualizar o peso da pessoa passada como primeiro parâmetro. Em seguida, apresenta o resultado;
- linhas 27–29: a lista de pessoas é consumida utilizando a implementação [biconsumerAge];
- linhas 31–33: a lista de pessoas é consumida utilizando a implementação [biconsumerWeight];
Os resultados obtidos são os seguintes:
4.6. Exemplo-06 - A interface funcional BiFunction<T,U,R>
![]() |
A interface funcional BiFunction<T,U,R> é definida da seguinte forma:

O único método da interface tem a assinatura: R apply(T t, U u). Este método é semelhante ao método [BiConsumer.apply], mas enquanto este último não devolve um resultado, o método [BiFunction.apply] devolve um resultado. Iremos ilustrar a sua utilização com o seguinte código:
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 of persons
List<Personne> personnes = Personnes.get();
// anonymous implementation
BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
@Override
public Integer apply(Personne personne, Integer integer) {
return personne.getAge() + integer;
}
};
// lambda implementation
BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
return p.getPoids() + i;
};
// displays
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));
}
}
}
- linhas 14–19: A interface BiFunction<Person, Integer, Integer> é implementada utilizando uma classe anónima. O seu método [apply] devolve a idade da pessoa passada como primeiro parâmetro, aumentada pelo valor do segundo parâmetro;
- linhas 21–23: A interface BiFunction<Person, Integer, Double> é implementada utilizando uma função lambda. O método [apply] devolve o peso da pessoa passada como primeiro parâmetro, acrescido do valor do segundo parâmetro;
- linhas 25–27: a implementação [biFunctionAge] é aplicada às pessoas;
- linhas 29–31: a implementação [biFunctionWeight] é aplicada às pessoas;
Os resultados obtidos são os seguintes:
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
Para além das funções lambda, o Java 8 introduziu o tipo Stream<T>, que modela um fluxo de elementos do tipo T. Estes elementos podem ser submetidos a transformações sucessivas implementadas por funções lambda. Quando possível, e quando existem vários processadores, estas transformações podem, por vezes, ser realizadas em paralelo.
4.7. Exemplo-07 - A interface funcional Supplier<T>
![]() |
A interface funcional Supplier<T> é definida da seguinte forma:

O único método da interface tem a assinatura: T get(), cuja função é devolver um objeto do tipo T.
Iremos ilustrar esta interface funcional com o seguinte código:
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) {
// anonymous implementation
Supplier<Personne> supplier = new Supplier<Personne>() {
// list of persons
List<Personne> personnes = Personnes.get();
// interface implementation
@Override
public Personne get() {
int i = new Random().nextInt(personnes.size());
return personnes.get(i);
}
};
// operation
for (int i = 0; i < 5; i++) {
affiche(supplier);
}
}
// person display
public static void affiche(Supplier<Personne> supplier) {
System.out.println(supplier.get());
}
}
- linhas 13–28: implementação de um tipo Supplier<Person>;
- linhas 31–33: o método estático [display] espera um parâmetro do tipo Supplier<Person>;
- linhas 25–27: utilização da instância Supplier<Person>;
São obtidos os seguintes resultados:






