Interfaces Funcionais Java - Function

Por Gaspar Barancelli Junior em 11 de outubro de 2022

A partir da versão 8 do java foi introduzido o conceito de expressão lambda, que nada mais é que um bloco de código que pode receber parâmetros e também pode retornar um determinado valor.

Expressões lambdas são bem semelhantes a métodos, mas não precisam ter um nome e contém apenas o corpo da implementação.

Mas para que seja possível a utilização de lambdas, o java teve que introduzir o conceito de interfaces funcionais que por definição é uma interface que possui apenas um único método abstrato.

É importante lembrar que uma interface funcional pode possuir mais métodos, mas eles precisam ter uma implementação default.

Também podemos fazer com que o compilador do java garanta que determinada interface seja funcional. Com isso evitamos que nenhum outro desenvolvedor no futuro adicione outro método abstrato nessa interface e quebre o contrato de interface funcional, para isso basta adicionar a seguinte anotação na interface @FunctionalInterface.

Function

Muitos dos métodos que escrevemos em nosso dia a dia recebem um parâmetro de objeto X, uma lógica é aplicada, e por fim retorna um objeto do tipo Y.

Ao observarmos o código da classe Function veremos que ela existe para tratar este tipo de cenário.

@FunctionalInterface
public interface Function<T, R> {

   R apply(T t);

}

Você já deve ter implementado a interface Function mas talvez nunca tenha reparado, como por exemplo realizar a operação de map em uma stream. Ao observarmos a assinatura do método map, veremos que ele recebe por parâmetro uma Function.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

O método map é utilizado para aplicar uma transformação nos objetos do stream.

Segue um simples exemplo de uma transformação utilizando map.

List.of(1_000, 2_340, 7_314, 9_119)
   .stream()
   .map(it -> it * 0.1)
   // restante do código foi ignorado

Perceba que no exemplo estamos utilizando lambda, que implicitamente implementa uma Function, a qual recebe um objeto do tipo Double, aplica uma lógica para obter um valor percentual e o retorna para o stream.

Vamos a mais um exemplo, onde recebemos um objeto do tipo Usuario e convertemos para um objeto do tipo UsuarioResponse.

package function.exemplo1;

public class Usuario {

    private final long codigo;
    private final String nome;
    private final String senha;
    private final String email;

    public Usuario(long codigo, String nome, String senha, String email) {
        this.codigo = codigo;
        this.nome = nome;
        this.senha = senha;
        this.email = email;
    }

    public long getCodigo() {
        return codigo;
    }

    public String getNome() {
        return nome;
    }

    public String getSenha() {
        return senha;
    }

    public String getEmail() {
        return email;
    }

}
package function.exemplo1;

public class UsuarioResponse {

    private final String nome;
    private final String email;

    public UsuarioResponse(String nome, String email) {
        this.nome = nome;
        this.email = email;
    }

    public String getNome() {
        return nome;
    }

    public String getEmail() {
        return email;
    }

}
import function.exemplo1.Usuario;
import function.exemplo1.UsuarioConverter;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.function.Function;

import static org.junit.jupiter.api.Assertions.*;

public class UsuarioConverterTest {

    @Test
    void converterListaDeUsuarios() {
        Usuario usuario = new Usuario(
            1L,
            "Gaspar Barancell Junior",
            "123",
            "gasparbarancelli@gmail.com"
        );

        List.of(usuario)
            .stream()
            .map(new Function<Usuario, UsuarioResponse>() {
                @Override
                public UsuarioResponse apply(Usuario it) {
                    return new UsuarioResponse(
                            it.getNome(),
                            it.getEmail()
                    );
                }
            })
            .forEach(new Consumer<UsuarioResponse>() {
                @Override
                public void accept(UsuarioResponse it) {
                    assertEquals(usuario.getNome(), it.getNome());
                    assertEquals(usuario.getEmail(), it.getEmail());
                }
            });
    }
}

Em nossa classe de testes não utilizamos lambda, deixando o nosso código mais verboso, justamente para demonstrar a implementação de Function.

Nos próximos exemplos vamos aos poucos melhorando a nossa classe de testes.

Agora vamos extrair a implementação do map para uma variável de escopo local.

Function<Usuario, UsuarioResponse> usuarioConverter = new Function<>() {
    @Override
    public UsuarioResponse apply(Usuario it) {
        return new UsuarioResponse(
            it.getNome(),
            it.getEmail()
        );
    }
};

List.of(usuario)
    .stream()
    .map(usuarioConverter)
    // restante do código foi ignorado

Dessa vez vamos utilizar lambda na criação da Function.

Function<Usuario, UsuarioResponse> usuarioConverter = it -> new UsuarioResponse(
        it.getNome(),
        it.getEmail()
);

List.of(usuario)
    .stream()
    .map(usuarioConverter)
    // restante do código foi ignorado

Por fim, vamos utilizar lambda na implementação do map, removendo a variável de escopo local, para que possamos constatar como nosso código fica muito mais limpo e legível utilizando expressões lambdas.

List.of(usuario)
    .stream()
    .map(it -> new UsuarioResponse(
        it.getNome(),
        it.getEmail()
    ))
    // restante do código foi ignorado

Conclusão

Neste post, descrevemos a interface funcional Function que está presente na API do Java 8, e que pode ser usada como expressão lambda, deixando o código muito mais limpo e legível.

O código fonte deste post está disponível neste repoistório no github.

// Compartilhe esse Post

💫
🔥 NOVO APP

Domine o Inglês em 30 dias!

Inteligência Artificial + Repetição Espaçada • Método cientificamente comprovado

✅ Grátis para começar 🚀 Resultados rápidos
×