terça-feira, 12 de novembro de 2019

Java Stream API - Programação funcional em Java - Parte 2


#engenhariaDeSoftware #java #programaçãoFuncional #lambda #streamapi

A programação funcional é o paradigma do futuro em Java! Com ela, você escreve código mais enxuto e seguro, baseado em funções independentes, reduzindo a complexidade ciclomática do código e até o consumo de memória (evitar criação de objetos em loops).

Veja aqui mais um exemplo bem legal!




Stream API 

A Stream API nos permite tratar coleções como streams de dados, filtrando, mapeando, executando lambdas e convertendo dados e formatos.

Ela serve para transformar as operações em objetos Collection (List, Map Set) de imperativas em declarativas, transformando seu código em programação funcional e evitando manipulações de estado constantes.

Interfaces funcionais

Desde a versão 8, foi criado o pacote java.util.function, contendo várias interfaces funcionais que podemos usar em Streams. 

Por exemplo:


Aqui temos componentes que implementam 3 das interfaces funcionais padrão: Predicate, Comparator e Function.

Gerando um Stream

Podemos obter um Stream a partir de uma lista, por exemplo:

(new ArrayList<Double>(Arrays.asList(1.45,2.10,2.10,3.67)))
.stream()
.forEach(s -> System.out.println(s));


Estranho? Sim! Coisa de Dr. Estranho, certo? Mas, se você tranquilizar seu espírito, verá que é uma chamada de função encadeada, sem loops aparentes! Como seria o código equivalente?


List<Double> lista = Arrays.asList(1.45,2.10,2.10,3.67);
for (Double d : lista) {
System.out.println(d);
}

Ué? Qual é o problema disso? É praticamente a mesma quantidade de linhas.

Sim, mas o código imperativo com loop tem complexidade ciclomática 2 e poderia ser maior. Além disso, criamos variáveis intermediárias. Está muito mais sujeito a erros e brittleness do que o código baseado em Stream.

Podemos usar stream API para gerar coleções, como Sets:

IntStream.of(1,5,3,1,1,2,5,7).boxed().collect(Collectors.toSet())
.forEach(System.out::println);

O que fizemos aqui? Cara, removemos os elementos duplicados! Olha só o código imperativo para isso:

int [] numeros = {1,5,3,1,1,2,5,7};
Arrays.sort(numeros);
System.out.println(numeros[0]);
for (int x=1; x<numeros.length; x++) {
if (numeros[x] != numeros[x-1]) {
System.out.println(numeros[x]);
}
}


Quer mais exemplos, com tipos de dados complexos? Que tal pensarmos em duas classes: "Client" e "Order". Um Client (cliente) tem uma coleção de Order (pedidos) e eles possuem valor e estado (entregue / não entregue). Queremos mostrar os pedidos ainda não entregues. Veja esse exemplo que coloquei em um Gist:


https://gist.github.com/cleuton/8512ad67a773d714d48776a76558b262


Primeiramente, temos a versão imperativa do código:

// 1 - Procedural way: 
for (Order order : client.getOrders()) {
if (!order.isDelivered()) {
System.out.println(order.value);
}
}

Ok, temos um loop com variáveis locais, e um "if". Complexidade 3.

Agora, vejamos algumas variações com stream:

// 2 - Functional way:
System.out.println(client.getOrders().stream()
.filter(o -> !o.isDelivered())
.map(o -> o.value).collect(Collectors.toList()));

// 3 - Using forEach:
client.getOrders().forEach(o -> {if (!o.isDelivered()) {System.out.println(o.getValue());}});

// 4 - Using double collon operator: 
client.getOrders().stream().filter(o -> !o.isDelivered())
.map(o -> o.value).forEach(System.out::println);

São três invocações de funções encadeadas, sem loops e "ifs" intermediários, sem variáveis temporárias, sem nada.

Conclusão

A programação funcional tem muitas vantagens, entre elas: 
  • Estado imutável: O estado dos objetos não será alterado e não precisa ser sincronizado;
  • Funções são objetos, e seu encadeamento é a lógica do programa;
  • Sem efeitos colaterais;
Eu ainda acrescento que podem ajudar a diminuir o consumo de memória. Por exemplo, usando loops encadeados, alocamos vários objetos temporários e só liberamos no final. Dividindo em várias funções, evitamos manter objetos temporários na memória por muito tempo.

Mas é um estilo diferente do que estamos acostumados.


Cleuton Sampaio, M.Sc.




Nenhum comentário:

Postar um comentário