Interfaces Funcionais Java - Function
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.