| Bellacosa Mainframe e a lambda expressions no Java |
☕ Um Café no Bellacosa Mainframe
Lambda Expressions no Java: O Manual Definitivo para um Programador COBOL Padawan Entender o Operador -> sem Medo (e Descobrir que Ele Não é Magia)
"Todo programador COBOL já passou por isso: você abre um código Java moderno e encontra algo como
(x) -> x * 2. Sua primeira reação é pensar: 'Quem apagou metade do programa?' A boa notícia é que ninguém apagou nada. Você está olhando para uma Lambda Expression."
Introdução
Se você trabalha há anos no universo IBM Z, provavelmente sua realidade é composta por tecnologias como:
COBOL
JCL
CICS
IMS
Db2
MQ
VSAM
z/OS
REXX
PL/I
Neste mundo, tudo costuma ser muito explícito.
Você declara.
Você identifica.
Você define.
Você documenta.
Você implementa.
Já no mundo Java moderno acontece exatamente o contrário.
Quanto menos código existir, melhor.
É aí que surgem as Lambda Expressions, introduzidas oficialmente no Java 8 (2014).
Para muitos profissionais Mainframe, elas parecem um idioma alienígena.
Mas a verdade é outra.
Você já conhece o conceito.
Só não conhece a sintaxe.
Antes de tudo…
Existe Arrow Function em Java?
Tecnicamente...
Não.
O nome Arrow Function pertence ao JavaScript.
No Java o nome correto é:
Lambda Expression
O símbolo é exatamente o mesmo conceito visual:
->
Por isso muita gente chama informalmente de Arrow Function.
Por que a IBM adicionou Lambdas ao Java?
A resposta é simples:
Performance + produtividade.
Na década de 2000, Java era conhecido por ser extremamente verboso.
Para executar uma simples ação era comum escrever dezenas de linhas.
Exemplo antigo:
Collections.sort(lista, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
Mais de dez linhas.
Hoje:
lista.sort((a,b) -> a.compareTo(b));
Uma linha.
Muito mais legível.
Mas o que é uma Lambda?
A definição acadêmica diz:
Uma função anônima.
Mas para um COBOL Padawan eu prefiro outra definição.
Pense nela como um PARÁGRAFO temporário.
No COBOL fazemos isso:
PERFORM CALCULAR-TOTAL
E em algum lugar existe:
CALCULAR-TOTAL.
COMPUTE WS-TOTAL =
WS-VALOR1 +
WS-VALOR2.
EXIT.
No Java moderno, às vezes não vale a pena criar um método inteiro.
Você simplesmente entrega a lógica diretamente.
Algo parecido com:
(a,b) -> a+b
É como dizer:
"Aqui está o código. Execute quando precisar."
O significado do operador ->
Observe:
(a,b) -> a+b
Lendo em português:
Receba A
Receba B
Execute
Retorne A+B
Visualmente:
ENTRADA
│
▼
(a,b)
│
▼
->
│
▼
a+b
│
▼
SAÍDA
O operador -> separa:
lado esquerdo
↓
parâmetros
↓
lado direito
↓
implementação
Comparando com COBOL
COBOL:
ADD A TO B GIVING C
Java:
(a,b) -> a+b
A intenção é praticamente igual.
A diferença é que no Java essa operação pode ser passada como parâmetro.
Uma mudança de paradigma
Durante décadas escrevemos programas assim:
Programa
↓
Métodos
↓
Chamadas
Com Lambdas passamos a escrever:
Programa
↓
Comportamentos
↓
Funções
↓
Funções recebendo funções
Isso é programação funcional.
Um exemplo simples
Sem Lambda:
Runnable tarefa = new Runnable() {
@Override
public void run() {
System.out.println("Executando");
}
};
Com Lambda:
Runnable tarefa =
() -> System.out.println("Executando");
Mesmo comportamento.
Muito menos código.
O que mudou?
Antes era necessário criar uma classe anônima.
Agora basta informar:
parâmetros
comportamento
Quando usar ()
Sem parâmetros:
() -> ...
Um parâmetro:
x -> ...
Dois parâmetros:
(a,b) -> ...
Três parâmetros:
(a,b,c) -> ...
Quando usar chaves?
Uma linha:
x -> x*2
Mais de uma:
x -> {
System.out.println(x);
return x*2;
}
O retorno implícito
Observe:
x -> x+1
Não existe:
return
O Java entende automaticamente.
Já quando usamos bloco:
x -> {
return x+1;
}
O return passa a ser obrigatório.
Interfaces Funcionais
Aqui existe uma diferença enorme para JavaScript.
No Java você não cria uma Lambda do nada.
Ela precisa implementar uma interface.
Exemplo:
@FunctionalInterface
interface Soma {
int executar(int a,int b);
}
Uso:
Soma s =
(a,b) -> a+b;
Pronto.
Sua Lambda implementou automaticamente a interface.
O segredo está na @FunctionalInterface
Ela permite apenas:
um único método abstrato.
Isso faz o compilador saber exatamente qual método será implementado.
Onde as Lambdas aparecem no Java moderno?
Praticamente em toda parte.
Principalmente em:
Streams
Comparator
Runnable
CompletableFuture
ExecutorService
Optional
Collections
APIs REST
Spring Boot
Jakarta EE
IBM Semeru Runtime
Open Liberty
Ou seja...
Se você pretende trabalhar com Java no IBM Z...
Vai encontrá-las diariamente.
Streams
Imagine uma lista.
List<Integer> numeros =
List.of(1,2,3,4,5);
Filtrar pares:
numeros.stream()
.filter(n -> n%2==0)
.forEach(System.out::println);
A Lambda diz apenas:
Receba um número
↓
Se for par
↓
Retorne verdadeiro
Comparando com COBOL
No COBOL seria algo como:
PERFORM VARYING IDX
FROM 1 BY 1
UNTIL IDX > MAX
IF NUMERO(IDX)
MOD 2 = ZERO
DISPLAY NUMERO(IDX)
END-IF
END-PERFORM
No Java:
.filter(n->n%2==0)
O comportamento é exatamente o mesmo.
Comparator
Ordenar nomes.
Antes:
mais de quinze linhas.
Hoje:
lista.sort(
(a,b)->a.compareTo(b)
);
Method Reference
Existe algo ainda menor.
Em vez de:
x -> System.out.println(x)
Podemos escrever:
System.out::println
Isso se chama:
Method Reference.
É praticamente uma Lambda reduzida.
Predicate
Representa perguntas.
Predicate<Integer> par =
n -> n%2==0;
É equivalente a:
É par?
↓
Sim ou Não
Function
Transforma algo.
Entrada:
Nome
Saída:
Maiúsculo
nome -> nome.toUpperCase()
Consumer
Recebe.
Consome.
Não devolve nada.
x -> System.out.println(x)
Supplier
Não recebe nada.
Mas produz.
() -> UUID.randomUUID()
Curiosidade
O nome Lambda vem da matemática.
Mais especificamente do Lambda Calculus, criado por Alonzo Church, em 1936.
Muito antes da invenção do COBOL.
Muito antes do Java.
Muito antes do C.
Alan Turing conhecia Lambdas?
Sim.
Church e Turing trabalharam praticamente na mesma época.
Os dois provaram matematicamente que era possível criar computação universal.
Então Java virou linguagem funcional?
Não.
Java continua sendo:
Orientado a Objetos
Mas ganhou recursos funcionais.
O que acontece na memória?
Muita gente imagina que uma Lambda cria um objeto enorme.
Na verdade não.
O compilador gera bytecode especial usando:
invokedynamic
Introduzido na JVM 7.
Depois entra em ação:
LambdaMetafactory
Ela cria dinamicamente uma implementação da interface funcional.
Ou seja...
Não existe necessariamente uma classe física como antigamente.
O papel do invokedynamic
Antes do Java 8:
Classe anônima
↓
Compilador
↓
Novo .class
Depois:
Lambda
↓
invokedynamic
↓
JVM
↓
Objeto criado dinamicamente
Muito mais eficiente.
Performance
Na maioria dos casos:
Lambda é mais rápida que classe anônima.
Por quê?
Porque:
menos bytecode
menos classes
melhor otimização JIT
HotSpot entende melhor o fluxo
No IBM Semeru Runtime isso também acontece.
Existe custo?
Sim.
Toda abstração possui custo.
Mas normalmente ele é insignificante.
Captura de variáveis
Exemplo:
int valor = 10;
Function<Integer,Integer> soma =
n -> n + valor;
Funciona.
Agora:
valor++;
Erro.
Por quê?
Porque a variável precisa ser:
effectively final.
Por que isso existe?
Para evitar problemas de concorrência.
Imagine:
100 Threads.
Todas alterando a mesma variável.
Seria um desastre.
O coletor de lixo
As Lambdas normalmente vivem pouco.
São excelentes candidatas para o Young Generation da JVM.
Resultado:
coleta extremamente rápida.
Java no IBM Z
Se você usa:
IBM Semeru Runtime
OpenJ9
Liberty
Spring Boot
z/OS Connect
Você está usando Lambdas o tempo inteiro.
Mesmo sem perceber.
Limitações
Nem tudo são flores.
Algumas limitações:
não possuem estado próprio
não substituem classes
não substituem objetos complexos
podem reduzir legibilidade quando abusadas
debugging pode ficar mais difícil
stack traces às vezes ficam estranhos
Pontos fortes
✔ Menos código
✔ Mais legibilidade
✔ Melhor integração com Streams
✔ Melhor paralelismo
✔ Melhor manutenção
✔ Melhor otimização da JVM
✔ Código moderno
Pontos fracos
✘ Curva de aprendizado
✘ Sintaxe inicialmente estranha
✘ Pode esconder lógica demais
✘ Debug mais complexo
✘ Não substitui arquitetura
Dicas para quem vem do COBOL
Não tente decorar a sintaxe.
Entenda a ideia.
Sempre pergunte:
"Esse trecho está apenas descrevendo um comportamento?"
Se sim...
Provavelmente cabe uma Lambda.
Streams + Lambdas são inseparáveis.
Aprenda ambos juntos.
Evite Lambdas gigantes.
Se passou de cinco ou seis linhas...
Crie um método.
Prefira Method Reference
Quando possível.
Em vez de:
x -> System.out.println(x)
Faça:
System.out::println
Easter Eggs
1. Nem toda Lambda gera um novo objeto.
A JVM pode reutilizar instâncias.
2. Uma Lambda pode não existir como classe.
Ao contrário das antigas Anonymous Classes.
3. O compilador gera métodos sintéticos.
Eles aparecem como:
lambda$0
lambda$1
lambda$2
Dentro do bytecode.
4. javap revela a mágica
Compile:
javac Exemplo.java
Depois:
javap -c -v Exemplo.class
Você verá instruções como:
invokedynamicBootstrapMethodsreferências ao
LambdaMetafactory
É uma ótima forma de entender o que a JVM realmente executa.
5. Lambdas e paralelismo
Elas combinam naturalmente com:
parallelStream()
Mas atenção: paralelo não significa automaticamente mais rápido. Em cargas pequenas, o custo de dividir o trabalho pode superar o ganho.
Fazendo a ponte para o mundo Mainframe
Se você é um Programador COBOL Padawan, pense assim:
Um
PERFORMexecuta um bloco identificado pelo nome.Uma Lambda entrega esse bloco diretamente para quem vai executá-lo.
Um
IFdo COBOL pode virar umPredicate.Uma rotina de transformação de registros pode virar uma
Function.Um
DISPLAYpode ser representado por umConsumer.
O paradigma muda, mas o raciocínio continua familiar: dados entram, uma lógica é aplicada e um resultado é produzido.
Conclusão
As Lambda Expressions não vieram para substituir a Programação Orientada a Objetos, muito menos os princípios sólidos que sustentam aplicações corporativas. Elas surgiram para reduzir código repetitivo, aumentar a expressividade e permitir que o desenvolvedor descreva comportamentos da mesma forma que descreve dados.
Para quem vem do universo IBM Z, elas representam mais uma evolução do que uma revolução. Você já domina lógica estruturada, modularização, processamento em lote, concorrência, desempenho e confiabilidade. O desafio agora é aprender uma nova forma de expressar essas mesmas ideias.
No fim das contas, a Lambda é apenas uma ferramenta. Ela não torna um sistema melhor por si só. Assim como um bom PERFORM, ela precisa ser usada no lugar certo, com clareza e equilíbrio.
E talvez essa seja a maior lição para todo COBOL Padawan que começa a explorar Java no Mainframe: a tecnologia muda, a boa engenharia de software permanece.
Sem comentários:
Enviar um comentário