A Programação Funcional está introduzindo conceitos novos para os desenvolvedores que trabalham com a Orientação a Objetos, como as mônadas, programação declarativa, imutabilidade, higher-order functions, first-class function, transparência referencial, dentre outros. Nesse artigo abordaremos as mônadas.

A Programação funcional, em linhas gerais, é um paradigma onde a computação é centrada nas transformações de dados através da aplicação de funções. Neste modelo de computação, as funções são cidadãs de primeira classe e devem ser livres de efeitos colaterais (não produzir estados mutáveis).

Já a mônada é um conceito oriundo da Teoria das Categorias, conforme podemos ver na Figura 1. Essa teoria é um ramo da matemática que tem muita influência na programação funcional.

Teoria das Categorias
Figura 1. Teoria das Categorias

A definição formal de mônada, segundo a Teoria das Categorias é que ela é uma tripla (T,n,u), onde T é um endo-functor C => C e n e u são duas transformações naturais associadas a esse functor. n é uma transformação natural de 1[C] => T (1[C] é o functor identidade em C) e u é uma transformação natural TxT => T (TxT é o functor T o T) ... " (Ver Ref[1] – I/O Haskell)

Felizmente não precisamos entender matematicamente esse conceito para usá-lo no Java 8. Martin Odersky, criador da linguagem Scala, tem a seguinte definição: "Mônadas são tipos parametrizados com duas operações principais, flatMap e unit, e que seguem algumas leis algébricas". Ou seja, as mônadas são contêiner que encapsulam valores e/ou computações.

Para ficar mais claro vamos para um exemplo, onde teremos operações de soma entre números e "não-números", como a seguir:


double soma = "A" + 1 + 3.2 + Carro + 0.3 + laranja + "string" + "10"
double soma = 4.5

Como números devemos considerar apenas 1, 3.2 e 0.3. E "10" é uma String, portanto um não-número (valor 0), assim como Carro e laranja.

A codificação dessa operação está na Listagem 1.


import java.util.Date;
 
public class Teste1 {
    
    public static double soma(Object obj1, Object obj2, Object obj3) {
        
        double valor = 0.0;
        
        if(obj1 instanceof Number) {
            valor += ((Number)obj1).doubleValue();
        }
        if(obj2 instanceof Number) {
            valor += ((Number)obj2).doubleValue();
        }
        if(obj3 instanceof Number) {
            valor += ((Number)obj3).doubleValue();
        }        
        
        return valor;
    }
    
    public static void main(String ...args) {
        
        double valor = soma("hello", 1.0, new Date());
        System.out.println("valor = " + valor);
    }    
}
Listagem 1. Somando Números e Não-Números

Para a computação de números e não-números, podemos definir número, nesse contexto, como sendo qualquer classe que estenda Number, e tudo que não estende será considerado como elemento neutro (0).

Ao analisar o código da Listagem 1, é possível aplicar uma refatoração para eliminar a duplicação de código, como vemos na Listagem 2.


import java.util.Date;
 
public class Teste2 {
    
    private static double converte(Object obj) {
        return (obj instanceof Number) ? ((Number)obj).doubleValue() : 0.0;
    }
    
    public static double soma(Object obj1, Object obj2, Object obj3) {
        return converte(obj1) + converte(obj2) + converte(obj3);
    }
    
    public static void main(String ...args) {
        
        double valor = soma("hello", 1.0, new Date());
        System.out.println("valor = " + valor);
    }    
}
Listagem 2. Código refatorado da Listagem 1

Veja que, independentemente dos códigos apresentados:

  1. Lidamos com tipos numéricos e "não-numéricos";
  2. Temos sempre um teste para validar o tipo(número vs não número);
  3. Não-números são considerados elementos neutros (0).

No código da Listagem 2 eliminamos a duplicação de código. Porém, se fosse pedido para dois ou mais desenvolvedores criarem um método que aceite três argumentos Object e só somassem os de tipo Number, provavelmente haveria lógica em comum na solução deles, que são os conceitos mencionados anteriormente. É a chamada "duplicação entre desenvolvedores" (Programador Pragmático), que fere o princípio DRY (Don't Repeat Yourself).

Qual seria a solução para evitar duplicações de lógica inadvertidas entre os desenvolvedores? Nesse caso, precisaríamos "conter" tanto os dados (numéricos e não-numéricos), como suas operações de teste e tipo, numa única representação e fazer os desenvolvedores usarem esse novo módulo. E isso nos lembra a definição de Mônada, que são contêiner que encapsulam valores e/ou computações.

O código de uma Mônada para esse problema pode ser visto na Listagem 3.


import java.util.Objects;
import java.util.function.Function;

public final class MaybeNumber<T> {

   private final T value;

   private static final MaybeNumber<?> EMPTY = new MaybeNumber<>();

 private MaybeNumber() {
     this.value = null;
 }

 private MaybeNumber(T value) {
     this.value = value;
 }
 
 public static <T> MaybeNumber<T> empty() {
     @SuppressWarnings("unchecked")
     MaybeNumber<T> t = (MaybeNumber<T>) EMPTY;
     return t;
 }

 public static <T> MaybeNumber<T> unit(T value) {
     if (value instanceof Number) {
         return new MaybeNumber<>(value);
     } else {
         return empty();
     }
 }

 public <R> MaybeNumber<R> flatMap(Function<T, MaybeNumber<R>> mapper) {
       if(value == null) {
           return empty();
       } else {
           return mapper.apply(this.value);
       }
   }

   @Override
   public int hashCode() {
       int hash = 7;
       hash = 43 * hash + Objects.hashCode(this.getValue());
       return hash;
   }

   @Override
   public boolean equals(Object obj) {
       if (obj == null) {
           return false;
       }
       if (getClass() != obj.getClass()) {
           return false;
       }
       final MaybeNumber<?> other = (MaybeNumber<?>) obj;
       return Objects.equals(this.getValue(), other.getValue());
   }
   
   public <R> MaybeNumber<R> map(Function<? super T, ? extends R> mapper) {
       if (value == null)
           return empty();
       else {
           return MaybeNumber.unit(mapper.apply(value));
       }
   }    
   
   public Double getValue() {
       return value == null ? 0.0 : ((Number)value).doubleValue();
   }
}
Listagem 3. Mônada MaybeNumber

Em linhas gerais, é como se os conceitos 1, 2 e 3 da questão dos números e não-números estivessem contidos numa única unidade computacional. Vejamos na Listagem 4 como fica o código da Listagem 2 utilizando a mônada MaybeNumber.


import java.util.Date;
 
public class Teste3 {
    
    public static double soma(MaybeNumber<Object> obj1, MaybeNumber
    <Object> obj2, MaybeNumber<Object> obj3) {
        return obj1.getValue() + obj2.getValue() + obj3.getValue();
    }
    
    public static void main(String ...args) {        
        double valor = soma(MaybeNumber.unit("hello"), MaybeNumber.unit(1.0),  
        MaybeNumber.unit(new Date()));
        System.out.println("valor = " + valor);
    }    
}
Listagem 4. Usando MaybeNumber

Encapsulamos os três conceitos da classe MaybeNumber que vimos na Listagem 3 numa mônada, eliminando uma possível redundância de lógica (e código) entre desenvolvedores. Assim, temos:

  1. Temos que lidar com tipos numéricos e "não-numéricos":
    Agora só lidamos com um tipo: MaybeNumber;
  2. Temos sempre um teste para validar o tipo:
    MaybeNumber cuida disso;
  3. Não-números são considerados elementos neutros (0):
    MaybeNumber cuida disso.

Uma mônada sempre tem duas operações fundamentais: unit e flatMap.

O método unit "coloca" o dado (tanto numérico como não-numérico) dentro do contêiner.

E a flatMap faz duas coisas:

  1. Permite chamadas encadeadas, favorecendo um estilo de programação mais declarativo do que imperativo (veremos em breve um exemplo disso). Uma forma mais declarativa de expressar computações é um dos benefícios das linguagens funcionais;
  2. Permite recuperar o valor contido numa mônada e aplicar uma transformação sem sair da mônada.
    Na Listagem 5 temos um exemplo do uso da flatMap.

public class Teste4 {
    
    public static MaybeNumber<Object> multiplicacao(Object value1, double value2) {
        return MaybeNumber.unit(MaybeNumber.unit(value1).getValue() * value2);
    }
    
    public static MaybeNumber<Object> divisao(Object value1, double value2) {
        return MaybeNumber.unit(MaybeNumber.unit(value1).getValue() / value2);
    }
    
    public static void main(String ...args) {        
        MaybeNumber<Double> obj1 = MaybeNumber.unit(10.0);        
        double valor1 = obj1.flatMap(x -> multiplicacao(x, 100)).flatMap(x -
        > divisao(x, 100)).getValue();
        System.out.println("Valor = " + valor1); // Igual a 10.0
        
        MaybeNumber<String> obj2 = MaybeNumber.unit("10.0");
        double valor2 = obj2.flatMap(x -> multiplicacao(x, 100)).flatMap(x -
        > divisao(x, 100)).getValue();
        System.out.println("Valor = " + valor2);  // Igual a 0, pois "10.0" é uma string (valor = 0)      
    }    
}
Listagem 5. Usando flatMap

Para calcular o valor1 e valor2 usamos encadeamento de chamadas usando flatMap e transformações (a partir de lambdas). Esse é o estilo funcional.

E quantos as leis algébricas, há três que uma mônada deve seguir:

  1. Identidade:
    m flatMap unit = m // onde m = unit(x);
  2. Unidade:
    unit(x) flatMap f = f(x);
  3. Associativa:
    m flatMap f flatMap g = m flatMap (x -> f(x) flatMap g) // onde m = unit(x).

Vamos verificar se a classe MaybeNumber segue essas leis:

Lei da Identidade

Pela lei da Identidade -m flatMap unit = m // onde m = unit(x) - temos, em Java:


MaybeNumber<Integer> m = MaybeNumber.unit(10);        
// Lei da Identidade
m.flatMap(MaybeNumber::unit) == m 

Repare nesse código e no resto da demonstração, o sinal de igual == é usado matematicamente, e não para comparar referências.

Como a entrada é 10 (um número válido), o retorno do flatMap pode ser traduzido para:


m.flapMap(MaybeNumber::unit) == mapper.apply(10)   // O código do else do flapMap
mapper == MaybeNumber::unit  //  MaybeNumber::unit é o argumento da função flatMap
mapper.apply(10) == MaybeNumber.unit(10)

Logo, provamos a identidade:


m.flatMap(MaybeNumber::unit) == m
MaybeNumber.unit(10) == MaybeNumber.unit(10)

Isso vale para qualquer entrada (inclusive não números), e com isso verificamos a validade da primeira lei. Para o caso de não-números, temos:


m.flatMap(MaybeNumber::unit) == m   // onde m = MaybeNumber.unit("x")
MaybeNumber.unit("x") == EMPTY        
m.flatMap(MaybeNumber::unit) == EMPTY  // o código do if da função flatMap
// Logo
MaybeNumber.unit("x") == m.flatMap(MaybeNumber::unit)
Listagem 1. NOME

Lei da Unidade

Veremos agora a Lei da Unidade - unit(x) flatMap f = f(x) - como a descrita em Java na Listagem 6.


import java.util.function.Function;
 
public class TestMaybeNumber {
    
    public static MaybeNumber<Integer> dobrar(Integer value1) {
        return MaybeNumber.unit(value1 * 2);
    }    
      
    public static void main(String ...args) {        
        MaybeNumber<Integer> m = MaybeNumber.unit(10);
        final Function<Integer, MaybeNumber<Integer>> f = TestMaybeNumber::dobrar;
        // Segunda Lei
        m.flatMap(f) == f(x);  //onde x = 10
    }    
}
Listagem 6. Lei da Unidade em Java

Como a entrada é um integer com valor 10, o retorno do flatMap pode ser traduzido para:


m.flatMap(f) == mapper.apply(x)
mapper == f
mapper.apply(10) == f.apply(10) 
f.apply(10) == f(10)

Logo:


m.flatMap(f) == f(x)
TestMaybeNumber.dobrar(10) == f(x) , onde x = 10
TestMaybeNumber.dobrar(10) == TestMaybeNumber.dobrar(10), 
onde x = 10

Com isso verificamos a validade da segunda lei para números. Para os não-números temos o código da Listagem 7 provando a lei.


import java.util.function.Function;
 
public class TestMaybeNumber {
    
    public static MaybeNumber<Object> dobrar(Object value1) {
        return MaybeNumber.unit(MaybeNumber.unit(value1).getValue() * 2);
    }    
    
    public static void main(String ...args) {        
        MaybeNumber m = MaybeNumber.unit("10");
        final Function<Object, MaybeNumber<Object>> f = TestMaybeNumber::dobrar;        
        MaybeNumber m1 = m.flatMap(f);   // retorna EMTPY
        MaybeNumber m2 = f.apply("10");  // retorna MaybeNumber(0)
        System.out.println("equals == " + m1.equals(m2));  // true
    }    
}
Listagem 7. Provando a lei da Unidade para não-números

Vejam que, para m.flatMap(f), retornamos EMPTY e para f.apply("10"), retornamos MaybeNumber.unit(0), que não é EMPTY. Isso não viola a segunda Lei?

Tecnicamente não, pois o que define se dois objetos são "idênticos" em Java são os métodos equals e hashCode. Para MayberNumber(0) e EMPTY, o método equals é true (hashCode também).

Portanto, a Segunda Lei se aplica também para não números.

Observe que a classe MaybeNumber poderia ser modificada, tornando o campo value sempre do tipo Double (e não parametrizado), sendo 0.0 para não números e n para qualquer Number n. Essa classe foi desenvolvida assim apenas para explorarmos os conceitos de Mônada (e também porque é semelhante a implementação da classe Optional do Java 8).

Lei Associativa

Para a terceira lei, temos:


(m flatMap f) flatMap g = m flatMap (x -> f(x) flatMap g) // onde m = unit(x)

Que é traduzida em Java para de acordo com a Listagem 8.


import java.util.function.Function;
 
public class TesteLei3 {
    
    public static MaybeNumber<Integer> dobrar(Integer value1) {
        return MaybeNumber.unit(value1 * 2);
    }    
    
    public static MaybeNumber<Integer> dividir(Integer value1) {
        return MaybeNumber.unit(value1 / 5);
    }    
    
    public static void main(String[] args) {      
        
        MaybeNumber<Integer> m = MaybeNumber.unit(10);        
        
        final Function<Integer, MaybeNumber<Integer>> f = TesteLei3::dobrar;
        final Function<Integer, MaybeNumber<Integer>> g = TesteLei3::dividir;
 
        // A terceira Lei:                
        m.flatMap(x -> f.apply(x)).flatMap(y -> g.apply(y));    // Isso deve ser igual a linha abaixo
        m.flatMap(x -> (f.apply(x).flatMap(y -> g.apply(y)))); // Isso deve ser igual a linha acima
    }    
}
Listagem 8. Lei Associativa em Java

Para provar que a mônada MaybeNumber segue essa lei, aplicamos a Segunda Lei (Lei da Unidade), conforme a Listagem 9.


import java.util.function.Function;
 
public class Lei3 {
    
    public static MaybeNumber<Integer> dobrar(Integer value1) {
        return MaybeNumber.unit(value1 * 2);
    }    
    
    public static MaybeNumber<Integer> dividir(Integer value1) {
        return MaybeNumber.unit(value1 / 5);
    }    
    
    public static void main(String[] args) {      
        
        MaybeNumber<Integer> m = MaybeNumber.unit(10);        
        
        final Function<Integer, MaybeNumber<Integer>> f = TesteLei3::dobrar;
        final Function<Integer, MaybeNumber<Integer>> g = TesteLei3::dividir;
        
        // Primeiro lado da comparacao
        m.flatMap(x -> f.apply(x)).flatMap(y -> g.apply(y));  
        // Aplicando a Segunda Lei        
        m.flatMap(x -> f.apply(x))  == f.apply(x) 
        // Temos(Lado 1):     
        f.apply(x).flatMap(y -> g.apply(y));  // x = 10
                        
        // Segundo lado da comparacao
        m.flatMap(x -> (f.apply(x).flatMap(y -> g.apply(y))));
        m.flatMap(x -> f''(x)), onde f''(x) == f.apply(x).flatMap(y -> g.apply(y))        
        // Aplicando a Segunda Lei   
        m.flatMap(x -> f''(x)) == f"(x)
        m.flatMap(x -> f''(x)) == f.apply(x).flatMap(y -> g.apply(y)) 
        // Temos(Lado 2):
        f.apply(x).flatMap(y -> g.apply(y));  // x = 10
    }    
}
Listagem 9. Provando a Lei Associativa

Logo, os dois lados chegaram a mesma equação:


f.apply(x).flatMap(y -> g.apply(y));  // x = 10

Para não-números, basta pensar que a entrada é 0, visto que MaybeNumber.unit(0) e EMPTY são "iguais".

Essa lei nada mais é que: (x + y) + z = x + (y + z) -> Lei matemática da associação

Para testar a terceira lei, use o código da Listagem 10, trocando o valor de b.


import java.util.function.Function;
 
/**
 *
 * @author marcelo
 */
public class TesteLei3 {
    
    public static MaybeNumber<Integer> dobrar(Integer value1) {
        return MaybeNumber.unit(value1 * 2);
    }    
    
    public static MaybeNumber<Integer> dividir(Integer value1) {
        return MaybeNumber.unit(value1 / 5);
    }    
    
    public static void main(String[] args) {      
        
        int b = -10;
        MaybeNumber<Integer> m = MaybeNumber.unit(b);        
        
        final Function<Integer, MaybeNumber<Integer>> f = TesteLei3::dobrar;
        final Function<Integer, MaybeNumber<Integer>> g = TesteLei3::dividir;
        
        // Primeiro lado da comparacao
        double value1 = m.flatMap(x -> f.apply(x)).flatMap(y -> g.apply(y)).getValue();
        // Segundo lado da comparacao
        double value2 = m.flatMap(x -> (f.apply(x).flatMap(y -> g.apply(y)))).getValue();
        
        System.out.println("value 1 = " + value1);
        System.out.println("value 2 = " + value2);
    }    
}
Listagem 10. Testando a Lei Associativa via código

As leis monádicas tem maiores efeitos práticos na linguagem Scala. Por exemplo, uma mônada que satisfaça essas leis pode ser usada com o "for-expression":


for {
  a <- List("a")
  b <- List("b")
  c <- List("c")
} yield (a, b, c)

Esse código Scala seria equivalente a escrever:


List("a").flatMap(a => List("b").flatMap(b => List("c").map(c => (a, b, c))))

Notem que o código com o "for-expression"tem um aspecto muito mais limpo que o encadeamento de flatMap's e map (repare que um List em Scala é uma mônada).

A linguagem Scala possui diversas mônadas relevantes: Option, Try, Future, List, Set, Either, etc.

Apesar da classe MaybeNumber ser útil para explorar alguns conceitos sobre Mônadas, veremos a seguir um exemplo mais prático e que pode ser usado no dia a dia.

A ameaça do NullPointerException

Bem, se o próprio criador da referência nula, Tony Hoare, disse que esse conceito foi um erro, como então nos protegermos desse flagelo?

Vejamos um exemplo típico na Listagem 11.


import java.util.Random;
 
public class Empresa {
    
    static class Endereco {
        private Cidade cidade;
        public void setCidade(Cidade cidade) { this.cidade = cidade; }
        public Cidade getCidade() { return this.cidade; }
    }
 
    static class Cidade {
        private String nome;
        public void setNome(String nome) { this.nome = nome; }
        public String getNome() { return this.nome; }        
    }
    
    private Endereco endereco;
    
    public void setEndereco(Endereco endereco) { this.endereco = endereco; }
    public Endereco getEndereco() { return this.endereco; }
    
    // Cria objeto ou nao, depende da sorte
    private static Object createRandom(Class clazz) throws InstantiationException, IllegalAccessException {
        if(new Random().nextInt(2) == 1) {
            return clazz.newInstance();
        } else {
            return null;
        }
    }
    
    public static void main(String ... args) throws InstantiationException, IllegalAccessException {
        Empresa empresa = (Empresa)createRandom(Empresa.class);
        Endereco endereco =  (Endereco)createRandom(Endereco.class);
        Cidade cidade =  (Cidade)createRandom(Cidade.class);
        
        // Atribuicoes        
        if(empresa != null) {
            empresa.setEndereco(endereco);
        }
        if(endereco != null) {
            endereco.setCidade(cidade);
        }        
        if(cidade != null) {
            cidade.setNome("Sao Paulo");
        }
        
        // Leituras
        if(empresa != null) {
            Endereco endereco1 = empresa.getEndereco();
            if(endereco1 != null) {
                Cidade cidade1 = endereco1.getCidade();
                if(cidade1 != null) {
                    System.out.println(cidade1.getNome());
                }
            }
        }        
    }
}
Listagem 11. Código "seguro"

Observem a quantidade de testes que temos que realizar para garantir a segurança em relação ao null. Sim, todo método que não retorna uma primitiva, devolve sempre duas opções: um objeto ou null (null pode ser considerado um não-objeto).

Esse conceito de Objeto e não-objeto nos lembra a questão do numérico e não-numérico, certo?

Um null deveria ser considerado como os não-numéricos (valor 0) nos exemplos anteriores que vimos. Garantimos isso com os testes de if (igual aos testes de if com instanceof que fora feito na classe MaybeNumber).

Então temos três conceitos:

  1. Objetos e não-objetos;
  2. Testes para validar objetos e não-objetos;
  3. null deveria ser como o elemento nulo, assim como no conceito matemático, que não tem efeitos colaterais.

Podemos encapsular esse conceito numa mônada, assim como os projetistas da linguagem Java fizeram na versão 8, criando assim a classe Optional, como vemos na Listagem 12.


package java.util;
 
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
 
public final class Optional<T> {
 
    private static final Optional<?> EMPTY = new Optional<>();
 
    private final T value;
 
    private Optional() {
        this.value = null;
    }
 
    public static<T> Optional<T> empty() {
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    }
 
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }
 
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }
 
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }
 
    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
 
    public boolean isPresent() {
        return value != null;
    }
 
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }
 
    public Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }
 
    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
 
    public<U> Optional<U> flatMap(Function<? super T, Optional
    <U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }
 
    public T orElse(T other) {
        return value != null ? value : other;
    }
 
    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }
 
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> 
    exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
 
        if (!(obj instanceof Optional)) {
            return false;
        }
 
        Optional<?> other = (Optional<?>) obj;
        return Objects.equals(value, other.value);
    }
 
    @Override
    public int hashCode() {
        return Objects.hashCode(value);
    }
 
    @Override
    public String toString() {
        return value != null
            ? String.format("Optional[%s]", value)
            : "Optional.empty";
    }
}
Listagem 12. Código fonte da classe Optional(API do Java 8)

O método unit na classe Optional está representado em dois métodos: método of e método ofNullable.

E flatMap está lá e para provar vamos refatorar o código da Listagem 9, aplicando essa mônada.


import java.util.Optional;
import java.util.Random;
 
public class Empresa {
 
    static class Endereco {
 
        private Optional<Cidade> cidade;
 
        public void setCidade(Optional<Cidade> cidade) {
            this.cidade = cidade;
        }
 
        public Optional<Cidade> getCidade() {
            return this.cidade;
        }
    }
 
    static class Cidade {
 
        private String nome;
 
        public void setNome(String nome) {
            this.nome = nome;
        }
 
        public String getNome() {
            return this.nome;
        }
    }
 
    private Optional<Endereco> endereco;
 
    public void setEndereco(Optional<Endereco> endereco) {
        this.endereco = endereco;
    }
 
    public Optional<Endereco> getEndereco() {
        return this.endereco;
    }
 
    private static Object createRandom(Class clazz) throws InstantiationException, 
    IllegalAccessException {
        if (new Random().nextInt(2) == 1) {
            return clazz.newInstance();
        } else {
            return null;
        }
    }
 
    public static void main(String... args) throws InstantiationException, 
    IllegalAccessException {
        Optional<Empresa> empresa = Optional.ofNullable((Empresa) 
        createRandom(Empresa.class));
        Optional<Endereco> endereco = Optional.ofNullable((Endereco) 
        createRandom(Endereco.class));
        Optional<Cidade> cidade = Optional.ofNullable((Cidade) 
        createRandom(Cidade.class));
 
        // Atribuicoes        
        if (empresa.isPresent()) {
            empresa.get().setEndereco(endereco);
        }
        if (endereco.isPresent()) {
            endereco.get().setCidade(cidade);
        }
        if (cidade.isPresent()) {
            cidade.get().setNome("Sao Paulo");
        }
 
        // Leitura
        empresa.flatMap(empr -> empr.getEndereco())
               .flatMap(end -> end.getCidade())
               .ifPresent(cid -> System.out.println(cid.getNome()));
      }
}
Listagem 13. Usando a Mônada Optional

O código da Listagem 13 é o mesmo da Listagem 11, mas aplicando a classe Optional.

Vamos comparar a primeira parte das atribuições: perceba que não mudou muito em relação a quantidade de código fonte escrito, certo? Observe a Listagem 14.


Empresa empresa = (Empresa)createRandom(Empresa.class);
Endereco endereco =  (Endereco)createRandom(Endereco.class);
Cidade cidade =  (Cidade)createRandom(Cidade.class);

// Atribuicoes        
if(empresa != null) {
    empresa.setEndereco(endereco);
}
if(endereco != null) {
    endereco.setCidade(cidade);
}        
if(cidade != null) {
    cidade.setNome("Sao Paulo");
}

.....................

Optional<Empresa> empresa = Optional.ofNullable((Empresa) createRandom(Empresa.class));
Optional<Endereco> endereco = Optional.ofNullable((Endereco) createRandom(Endereco.class));
Optional<Cidade> cidade = Optional.ofNullable((Cidade) createRandom(Cidade.class));

// Atribuicoes        
if (empresa.isPresent()) {
    empresa.get().setEndereco(endereco);
}
if (endereco.isPresent()) {
    endereco.get().setCidade(cidade);
}
if (cidade.isPresent()) {
    cidade.get().setNome("Sao Paulo");
}
Listagem 14. Comparando a seção atribuição

Veja que estamos comparando as seções de atribuição dos códigos da Listagens 11 e 13. E sim, a priori, nada mudou. Porém, há algo muito importante: no código que usa Optional, para acessar a entidade (empresa, cidade, endereço) de verdade, nós precisamos "tirá-la" do contêiner, e para isso usamos o método get.

Porém, o get só funciona se houver um valor não-nulo dentro do contêiner. Caso contrário, ele lança uma exceção. No artigo "Programação Restritiva"explorou-se o fato de se fazer o compilador trabalhar a favor do programador. E é isso que acontece aqui. Se o programador quer extrair o dado real do contêiner, ele precisa testar antes (usando isPresent()).

No código sem Optional, o programador pode lembrar ou não fazer o teste, dependendo da vontade dele (e sem a ajuda do compilador). Se ele esquecer, NullPointerException...


// Leituras
if(empresa != null) {
Endereco endereco1 = empresa.getEndereco();
if(endereco1 != null) {
    Cidade cidade1 = endereco1.getCidade();
    if(cidade1 != null) {
        System.out.println(cidade1.getNome());
    }
}
}        

.......

// Leituras
empresa.flatMap(empr -> empr.getEndereco())
   .flatMap(end -> end.getCidade())
   .ifPresent(cid -> System.out.println(cid.getNome()));
Listagem 15. Comparando a seção leitura (imperativo x declarativo)

Na Listagem 15 temos um dos motivos de se usar a mônada Optional: encadeamento seguro de métodos (sem NullPointerException).

O código com ifs é imperativo:

"Se empresa for diferente de null, faça isso:

Se endereco1 for diferente de null faça aquilo..."

Na programação imperativa definimos, passo a passo, tudo o que o programa deve fazer. Na programação funcional adotamos um estilo mais declarativo, baseado em transformações (aplicadas por funções) e composição de métodos (principalmente com map, flatMap, filter, reduce, etc).

Mônada é um padrão de projeto em linguagens funcionais e seus maiores benefícios são:

  • Ocultar complexidade;
  • Reduzir duplicação de lógica e código;
  • Encapsular conceitos e computação;
  • Permitir encadeamento/composição.

Lembre-se que Optional não estende Serializable. Por isso, ao invés de sair convertendo os atributos da sua entidade para Optional, uma solução interessante seria fornecer um método utilitário, como veremos na Listagem 16, retirado do livro Java 8 in Action.


public class Empresa {
    private Endereco endereco;
    // Método utilitário
    public Optional<Endereco> getEnderecoOptional() {
      return Optional.ofNullable(endereco);
    }
}
Listagem 16. Método utilitário

Em Java 8, temos outras mônadas: Stream e CompletableFuture.

Por esse conceito ter sido introduzido no Java 8 (graças ao suporte aos Lambdas), é claro que a API do Java, por exemplo, não usa Optional em larga escala para minimizar o risco de NullPointerException. E a linguagem Java ainda não têm suporte para algo parecido com o "for expression" ou o poderoso mecanismo de pattern matching do Scala, que favorece uma escrita ainda mais compacta e declarativa do código ao se trabalhar com mônadas.

Porém, é o primeiro passo do Java, com a versão 8, para apresentar aos programadores OO alguns dos conceitos e práticas das linguagens funcionais. Os novos recursos do Java 8 são uma novidade interessante para os programadores acostumados apenas ao paradigma corrente, e que agora podem adotar um estilo mais declarativo e/ou usando lambdas para reduzir a verbosidade da linguagem (comparada com linguagens funcionais como Scala e Haskell).

Mônada é um conceito poderoso de abstração em linguagens funcionais, e muito ainda poderia ser falado sobre isso. O intuito desse artigo foi apenas dar uma luz inicial sobre o tema.