4. As funções lambda do Java 8
4.1. Exemplo-01 - Interfaces funcionais e lambdas
![]() |
Consideremos o seguinte código:
package dvp.java8.lambdas;
public class Exemple01 {
public static void main(String[] args) {
// classes anónimas
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);
};
// aplicação
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 que possui apenas um método. Não está ligada à presença da anotação [@FunctionalInterface] da linha 41, que é opcional;
- linhas 46-49: uma segunda interface funcional I2;
- linhas 6-19: as interfaces I1 e I2 são implementadas com 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 com 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. Aproveita-se aqui o 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 e Tn podem ser omitidos se o compilador os conseguir deduzir a partir do contexto (inferência de tipos).
- linha 22: implementação do método [I1.doSomething] com a seguinte assinatura:
void doSomething();
[doSomething] é um método que não tem parâmetros e que não devolve qualquer resultado. A sua implementação lambda pode ser escrita como na linha 22 ou como nas linhas 24-26, ou seja, é possível colocar chaves à volta do código da função lambda. Se esse código contiver apenas uma instrução, como neste caso, essas chaves podem ser omitidas;
- linha 23: implementação do método [I1.getSomething] com a seguinte assinatura:
String getSomething(double value);
[getSomething] aceita 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 desse código, neste caso: String.format("ib2.lambda(%s)", value);
Na implementação das linhas 27-29:
- declara-se explicitamente o tipo do parâmetro [value];
- Utiliza-se um [return] para apresentar o resultado da função lambda. Neste caso, é necessário colocar chaves;
- linhas 32-37: chamam-se as várias funções anónimas e lambda;
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 a analisar a interface funcional [Predicate], definida no pacote [java.util.function], que reúne a maioria das interfaces funcionais do Java 8. Esta interface é definida da seguinte forma:

Já referimos que uma interface funcional tem apenas um método. No entanto, neste caso, existem quatro. Outra inovação introduzida pelo Java 8 foi a noção de método por predefinição numa interface, caracterizada pela palavra-chave [default]. Temos aqui três métodos desse tipo. Estes métodos têm a particularidade de possuírem uma implementação por predefinição. Não há, portanto, qualquer obrigação de uma classe que implemente uma interface com métodos por defeito ter de implementar esses métodos. Assim, uma classe que pretenda implementar a interface [Predicate] tem apenas um método que deve implementar obrigatoriamente: o método [test]. A interface [Predicate] é, portanto, uma interface funcional. Dir-se-á, assim, que uma interface funcional é uma interface cuja implementação inclui apenas um método obrigatório. Se tiver mais do que um método, os restantes devem ter a palavra-chave [default].
O método [Predicate<T>.test] recebe um parâmetro do tipo T e devolve um valor 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};
// dados
private String nom;
private int age;
private double poids;
private Sexe sexe;
// construtores
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 e 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 de caracteres jSON da pessoa;
A classe [Personnes] 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 obter essa lista;
- linhas 17-23: um método estático que permite obter a cadeia jSON a partir 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) {
// predicado implementado por uma classe anónima
Predicate<Personne> filterPoids=new Predicate<Personne>() {
@Override
public boolean test(Personne personne) {
return personne.getPoids()<50;
}
};
// predicado implementado por um lambda
Predicate<Personne> filterAge = p -> p.getAge() < 28;
// lista de pessoas
List<Personne> personnes = Personnes.get();
// visualizações
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] filtra a lista [personnes]
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<Pessoa> através de uma classe anónima. Trata-se de um filtro com base no peso da pessoa;
- linha 20: implementação da interface Predicate<Pessoa> por uma função lambda. Trata-se de um filtro com base na idade da pessoa. De acordo com o que foi dito, poderia também ter sido escrita da seguinte forma:
Predicate<Personne> filterAge = (Personne p) -> {
return (p.getAge() < 28);
};
mas a versão da linha 20 é mais concisa. O tipo do parâmetro p é deduzido a partir do contexto. Construímos aqui um tipo [Predicate<Personne>]. O método implementado tem, então, a assinatura [boolean test(Personne param)]. Assim, o tipo implícito de p, linha 20, é o tipo [Personne];
- linha 22: recupera-se uma lista pré-definida de pessoas;
- linha 24: filtram-se as pessoas de acordo com a sua idade;
- linha 25: filtram-se de acordo com o peso. Em ambos os casos, exibe-se a cadeia jSON da lista assim filtrada;
- linhas 28-37: um método estático que
- aceita como parâmetros: uma lista de pessoas a filtrar e o filtro. Este último é uma instância da interface [Predicate<Personne>]. Por convenção, designamos, aqui e noutras partes do documento, por instância de uma interface I, uma instância de uma classe C que implementa I;
- retorna como resultado a lista assim filtrada;
- linha 32: utiliza-se 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 Function<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, a partir de um tipo Collection<T>, um novo tipo Collection<R>. 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 {
// implementação com classe anónima
Function<Personne, String> mapToName = new Function<Personne, String>() {
@Override
public String apply(Personne personne) {
return personne.getNom();
}
};
// implementação com lambda
Function<Personne, Integer> mapToAge = p -> p.getAge();
// lista de pessoas
List<Personne> personnes = Personnes.get();
// jSON
ObjectMapper jsonMapper = new ObjectMapper();
// visualizações
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
}
// transformação de List<Pessoa> para 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: implementa-se a interface [Function<Personne, String>] com uma classe anónima que fará a transformação Pessoa --> String;
- linha 22: implementa-se a interface [Function<Personne, Integer>] com uma função lambda que fará a transformação Pessoa --> Integer;
- linhas 33-39: um método estático que
- aceita dois parâmetros: o primeiro, de tipo List<Personne>, é uma lista de pessoas a transformar. O segundo, de tipo Function<Pessoa, T>, é uma função que, a partir de cada pessoa da lista, cria um objeto de tipo T;
- retorna como resultado um tipo List<T>, em que cada elemento provém da transformação Pessoa -> T;
- linhas 35-37: aplica-se a transformação Pessoa -> T. Se o segundo parâmetro do método for o objeto [mapToName], será efetuada uma transformação Pessoa -> String. Se for o objeto [mapToAge], será efetuada uma transformação Pessoa -> Integer;
O resultado obtido é 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 seguinte assinatura: void accept(T t). Este método utiliza (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) {
// lista de pessoas
List<Personne> personnes = Personnes.get();
// implementação anónima
Consumer<Personne> consumerAge = new Consumer<Personne>() {
@Override
public void accept(Personne personne) {
System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
}
};
// implementação lambda
Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());
// visualizações
for (Personne p : personnes) {
consumerAge.accept(p);
}
System.out.println("--------");
for (Personne p : personnes) {
consumerPoids.accept(p);
}
}
}
- linhas 14-19: a interface [Consumer<Personne>] é implementada por uma classe anónima cujo método [accept] apresenta o nome e a idade da pessoa;
- linha 21: a interface [Consumer<Personne>] é implementada por uma função lambda cujo método implícito [accept] apresenta o nome e o peso da pessoa;
- linhas 23-25: a lista de pessoas é utilizada pela implementação [consumerAge];
- linhas 27-29: a lista de pessoas é utilizada pela implementação [consumerPoids];
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). Este método recebe o tipo T com a informação adicional U u. Iremos ilustrar a sua utilização com o código seguinte:
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 de pessoas
List<Personne> personnes = Personnes.get();
// implementação anónima
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());
}
};
// implementação lambda
BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
p.setPoids(p.getPoids() + i);
System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
};
// visualizações
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> com 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> com 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 é processada com a implementação [biconsumerAge];
- linhas 31-33: a lista de pessoas é processada com a implementação [biconsumerPoids];
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 qualquer resultado, o método [BiFunction.apply] devolve um resultado. Iremos ilustrar a sua utilização com o código seguinte:
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 de pessoas
List<Personne> personnes = Personnes.get();
// implementação anónima
BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
@Override
public Integer apply(Personne personne, Integer integer) {
return personne.getAge() + integer;
}
};
// implementação lambda
BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
return p.getPoids() + i;
};
// visualizações
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<Pessoa, Integer, Integer> é implementada com uma classe anónima. O método [apply] desta classe devolve a idade da pessoa passada como primeiro parâmetro, aumentada pelo valor do segundo parâmetro;
- linhas 21-23: a interface BiFunction<Pessoa, Inteiro, Duplo> é implementada com 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: aplica-se a implementação [biFunctionAge] às pessoas;
- linhas 29-31: aplica-se a implementação [biFunctionPoids] à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 caso existam 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 é fornecer um objeto do tipo T.
Iremos ilustrar esta interface funcional com o código seguinte:
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) {
// implementação anónima
Supplier<Personne> supplier = new Supplier<Personne>() {
// lista de pessoas
List<Personne> personnes = Personnes.get();
// implementação de interface
@Override
public Personne get() {
int i = new Random().nextInt(personnes.size());
return personnes.get(i);
}
};
// análise
for (int i = 0; i < 5; i++) {
affiche(supplier);
}
}
// visualização de pessoa
public static void affiche(Supplier<Personne> supplier) {
System.out.println(supplier.get());
}
}
- linhas 13-28: implementação de um tipo Supplier<Personne>;
- linhas 31-33: o método estático [affiche] espera um parâmetro do tipo Supplier<Personne>;
- linhas 25-27: execução da instância Supplier<Personne>;
Obtêm-se os seguintes resultados:






