Desde os primórdios dos tempos, o homem tem a necessidade de realizar trocas de bens, materiais ou serviços, começando com a simples troca de mercadorias. Naquela época já se tinha necessidade de iniciar o processo de padronização e o primeiro passo foi o uso de metais nobres (ouro, bronze, prata), chegando ao papel-moeda conhecido hoje. Atualmente com o grande número de e-commerce e a automatização do mercado financeiro, vários softwares manipulam de forma direta ou indireta essa unidade de medida. Eis que surge a pergunta: como se tratar dinheiro no sistema? O objetivo desse artigo é tratar exatamente desse ponto.

Qual variável utilizar para tratar o dinheiro: Integer, Long, Double, Float ou BigDecimal? No livro Java Efetivo se fala do problema de se usar dinheiro com representação em float e double devido a problemas na representação do ponto flutuante, recomendando o uso do BigDecimal ou long e int, como mostra o exemplo a seguir:


  public class Product { 
	  private String name; 
	  private BigDecimal price; 
	  //getter and setter
  }

Ao utilizarmos o BigDecimal, por exemplo, nos damos com alguns problemas, dentre eles, o fato de exibir essa informação com o símbolo monetário, arredondamento, etc. Assim, criamos uma classe utilitária para realizar tais operações, uma MoneyUtils. Nesse caso, nos damos com o problema do código ficar espalhado. Uma boa solução para isso seria a criação de uma variável do tipo dinheiro, assim centraliza o comportamento do dinheiro (visualizar com símbolo monetário, dentre outros comportamentos) em um objeto. Podemos observar como seria o código:


  public class Product { 
	  private String name; 
	  private Money price;  
	  //getter and setter 
  }

Mas com a globalização e o forte elo da internet, é natural que se tenha que se lidar com outras moedas além da moeda do país de origem. Assim, eis que surgem novos desafios/problemas como:

  • Buscar Cotação;
  • Armazenar a moeda da compra;
  • Realizar a conversão para a moeda desejada;
  • Buscar informações das padronizações de moedas (ISO 4217).

Com esse objetivo nasceu a nova API de dinheiro, JSR 354, com o intuito de padronizar e tornar mais fácil a manipulação dessa unidade de medida. Assim, será possível realizar arredondamento, conversão de moedas, dentre outros.

Começando a falar da Unidade de medida, o primeiro passo será demonstrar a criação da moeda, que é representada pela interface CurrencyUnit. Com isso, é possível criar de algumas maneiras a moeda, dentre elas, podemos passar um string na moeda ou passar o Locale da moeda, como mostra a Listagem 1.


  public class CreateCurrencyUnit 
  { 
      public static void main( String[] args ) 
      { 
          CurrencyUnit real = MonetaryCurrencies.getCurrency("BRL"); 
          CurrencyUnit dollar = MonetaryCurrencies.getCurrency(Locale.US); 
          System.out.println(real); //BRL – the Brazilian currency
          System.out.println(dollar); //USD – the north American currency
      } 
  }
Listagem 1. Criando uma moeda

O próximo passo é a criação do valor monetário: essa interface sempre estará atrelada em uma moeda. No exemplo da Listagem 2 será criado essa interface com duas implementações:

  • Money: representação que utiliza o BigDecimal;
  • FastMoney: representação em long em vez de BigDecimal. Com isso, essa implementação, além de ser mais rápida que a money, tende a ser até 15 vezes mais rápida, no entanto, possui a limitação do número de casas decimais, no caso apenas cinco.

  public class CreateMonetaryCurrency { 
   
	      public static void main(String[] args) { 
	          CurrencyUnit real = MonetaryCurrencies.getCurrency("BRL"); 
	          CurrencyUnit dollar = MonetaryCurrencies.getCurrency(Locale.US); 
	  MonetaryAmount money = Money.of(120, real); 
	  MonetaryAmount fastMoney = FastMoney.of(80, dollar); 
	  System.out.println(money); 
	  System.out.println(fastMoney); 
      } 
  }
Listagem 2. Criação do valor monetário

Com a classe criada é possível realizar algumas operações básicas, como as listadas na Listagem 3.


public class SimpleOperations { 

      public static void main(String[] args) { 
                  CurrencyUnit dollar = MonetaryCurrencies.getCurrency(Locale.US); 
  MonetaryAmount money = Money.of(120, dollar); 
  MonetaryAmount money2 = Money.of(50, dollar); 
  System.out.println(money.add(money2)); 
  System.out.println(money.subtract(money2)); 
  System.out.println(money.multiply(2)); 
  System.out.println(money.divide(2)); 
  System.out.println(money.isEqualTo(money2)); 
  System.out.println(money.isGreaterThan(money2)); 
  System.out.println(money.isGreaterThanOrEqualTo(money2)); 
  System.out.println(money.isGreaterThanOrEqualTo(money2)); 
  System.out.println(money.isLessThan(money2)); 
  System.out.println(money.isLessThanOrEqualTo(money2)); 
  System.out.println(money.isNegative()); 
  System.out.println(money.isNegativeOrZero()); 
          } 
}
Listagem 3. Operações básicas com a moeda

Outra possibilidade é a da formatação desses valores, como mostra a Listagem 4.


public class FormatExample { 

  public static void main(String[] args) { 
      CurrencyUnit dollar = MonetaryCurrencies.getCurrency(Locale.US); 
       
      MonetaryAmount monetaryAmount = Money.of(1202.12D, dollar); 
      MonetaryAmountFormat germanFormat = MonetaryFormats.getAmountFormat( 
                             Locale.GERMANY); 
      MonetaryAmountFormat usFormat = MonetaryFormats.getAmountFormat( 
                             Locale.US); 
       MonetaryAmountFormat customFormat = MonetaryFormats.getAmountFormat( 
          AmountFormatQueryBuilder.create(Locale.US).set(CurrencyStyle.SYMBOL).build()); 
       
      System.out.println(germanFormat.format(monetaryAmount));//1.202,12 USD 
      System.out.println(usFormat.format(monetaryAmount));//USD1,202.12 
      System.out.println(customFormat.format(monetaryAmount));//$1,202.12 
               
               
  } 
} 
Listagem 4. Formatação de valores

Essa API entrará juntamente no Java 9, assim, todos os recursos como o Stream, lambda, já estão incluídas. Além disso, para facilitar a manipulação dos dados com Stream, foi criado o MonetaryFunction, que possui alguns métodos utilitários para a manipulação do money. Podemos subdividir essa classe em quatro funcionalidades:

Manipulação de Redução

Operações de reduções como: maior valor, menor valor, somatório. Veja um exemplo na Listagem 5.


public class MonetaryReducerOperations { 

	  public static void main(String[] args) { 
	          List<MonetaryAmount> moneys = StreamFactory.getDollars(); 
	          MonetaryAmount min = moneys.stream().reduce(MonetaryFunctions.min()).get(); 
	          MonetaryAmount max = moneys.stream().reduce(MonetaryFunctions.max()).get(); 
	          MonetaryAmount sum = moneys.stream().reduce(MonetaryFunctions.sum()).get(); 
	          System.out.println(min);//USD 50 
	          System.out.println(max);//USD 120 
	          System.out.println(sum);//USD 460 
	  } 
   
}
Listagem 5. Operações de redução

Filtros

É possível fazer seleção apenas de uma moeda ou mais moedas ou a partir do valor (maior, menor ou entre um determinado valor). Veja um exemplo na Listagem 6.


public class MonetaryFilterOperations { 
	  private static CurrencyUnit DOLLAR = MonetaryCurrencies.getCurrency(Locale.US); 
	  private static CurrencyUnit EURO = MonetaryCurrencies.getCurrency("EUR"); 

	  public static void main(String[] args) { 
	   
          MonetaryAmount money = Money.of(BigDecimal.valueOf(100D), DOLLAR); 
          MonetaryAmount min = Money.of(BigDecimal.valueOf(6D), DOLLAR); 
          MonetaryAmount max = Money.of(BigDecimal.valueOf(100D), DOLLAR); 
           
          List<MonetaryAmount> moneys = getMoneys(); 
           
          List<MonetaryAmount> justDollar = moneys.stream() 
                                 .filter((MonetaryFunctions.isCurrency(DOLLAR))) 
                                 .collect(Collectors.toList()); 
          List<MonetaryAmount> notEuro = moneys.stream()
          .filter((MonetaryFunctions.isNotCurrency(EURO))).collect(Collectors
          .toList()); 
          List<MonetaryAmount> euroOrDollar = moneys.stream()
          .filter((MonetaryFunctions.containsCurrencies(EURO, DOLLAR)))
          .collect(Collectors.toList()); 
          List<MonetaryAmount> dollarGreaterOneHundred = moneys.stream()
          .filter((MonetaryFunctions.isCurrency(DOLLAR).and(MonetaryFunctions
          .isGreaterThan(money)))).collect(Collectors.toList()); 
          List<MonetaryAmount> dollarGreaterOneHundredDistinct = 
          moneys.stream().distinct().filter((MonetaryFunctions.isCurrency(DOLLAR)
          .and(MonetaryFunctions.isGreaterThan(money)))).collect(Collectors.toList()); 
          List<MonetaryAmount> between = moneys.stream()
          .filter((MonetaryFunctions.isCurrency(DOLLAR).and(MonetaryFunctions
          .isBetween(min, max)))).collect(Collectors.toList()); 
           
          System.out.println(justDollar); 
          System.out.println(notEuro); 
          System.out.println(euroOrDollar); 
          System.out.println(dollarGreaterOneHundred); 
          System.out.println(dollarGreaterOneHundredDistinct); 
          System.out.println(between); 
                       
  	} 
           
           
  	public static List<MonetaryAmount> getMoneys() { 
          List<MonetaryAmount> moneys = new ArrayList<>(); 
          moneys.add(Money.of(120, DOLLAR)); 
          moneys.add(Money.of(50, DOLLAR)); 
          moneys.add(Money.of(80, DOLLAR)); 
          moneys.add(Money.of(90, DOLLAR)); 
          moneys.add(Money.of(120, DOLLAR)); 
           
           
          moneys.add(Money.of(120, EURO)); 
          moneys.add(Money.of(50, EURO)); 
          moneys.add(Money.of(80, EURO)); 
          moneys.add(Money.of(90, EURO)); 
          moneys.add(Money.of(120, EURO)); 
          return moneys; 
      }
Listagem 6. Utilizando filtros

Manipulação para a Ordenação

As operações para realizar a ordenação da lista, como ordenar pelo pela moeda ou pelo valor nela contido, de forma crescente ou decrescente. Lembrando que é possível definir várias ordenações, ou seja, se pode ordenar de forma crescente a moeda e de forma decrescente o valor, como mostra a Listagem 7.


public class MonetarySorterOperations { 

       
	  public static void main(String[] args) { 
          List<MonetaryAmount> orderCurrency = getDollars().stream().sorted(
          MonetaryFunctions.sortCurrencyUnit()).collect(Collectors.toList()); 
          List<MonetaryAmount> orderSort = getDollars() 
             .stream() 
             .sorted(MonetaryFunctions.sortCurrencyUnit().thenComparing( 
                 MonetaryFunctions.sortNumber())) 
             .collect(Collectors.toList()); 
          List<MonetaryAmount> orderCurrencyNumber = getDollars() 
             .stream() 
             .sorted(MonetaryFunctions.sortCurrencyUnit().thenComparing( 
                 MonetaryFunctions.sortCurrencyUnitDesc())) 
             .collect(Collectors.toList()); 
           
          System.out.println(orderCurrency); 
          System.out.println(orderSort); 
          System.out.println(orderCurrencyNumber); 
	               
	  } 
	   
	  public static List<MonetaryAmount> getDollars() { 
          CurrencyUnit dollar = MonetaryCurrencies.getCurrency(Locale.US); 
          List<MonetaryAmount> moneys = new ArrayList<>(); 
          moneys.add(Money.of(120, dollar)); 
          moneys.add(Money.of(50, dollar)); 
          moneys.add(Money.of(80, dollar)); 
          moneys.add(Money.of(90, dollar)); 
          moneys.add(Money.of(120, dollar)); 
          return moneys; 
	  } 
}
Listagem 7. Ordenação de valor e moeda

Operações de agrupar

A partir dessas funcionalidades é possível agrupar os valores pela moeda, realizar um sumário (o objeto que possui informações de quantos elementos na lista, somatório, valor mínimo, máximo além da média), sem falar de agrupar esse sumário a partir da moeda. Veja um exemplo das operações na Listagem 8.


public class MonetaryGroupOperations { 
       
      private static CurrencyUnit DOLLAR = MonetaryCurrencies.getCurrency(Locale.US); 
      private static CurrencyUnit EURO = MonetaryCurrencies.getCurrency("EUR"); 

       
      public static void main(String[] args) { 
          Map<CurrencyUnit, List<MonetaryAmount>> groupBy = getCurrencies() 
             .stream().collect(MonetaryFunctions.groupByCurrencyUnit()); 
          MonetarySummaryStatistics summary = getCurrencies().stream() 
             .filter(MonetaryFunctions.isCurrency(DOLLAR)) 
             .collect(MonetaryFunctions.summarizingMonetary(DOLLAR)); 
          GroupMonetarySummaryStatistics groupSummary = getCurrencies().stream() 
             .filter(MonetaryFunctions.isCurrency(DOLLAR)) 
             .collect(MonetaryFunctions.groupBySummarizingMonetary()); 

          System.out.println(groupBy); 
          System.out.println(summary.getMin()); 
          System.out.println(summary.getMax()); 
          System.out.println(summary.getAvarage()); 
          System.out.println(summary.getCount()); 
          System.out.println(groupSummary); 
      } 
       
      public static List<MonetaryAmount> getCurrencies() { 
                   
                   
	      List<MonetaryAmount> moneys = new ArrayList<>(); 
	       
	      moneys.add(Money.of(120, DOLLAR)); 
	      moneys.add(Money.of(50, DOLLAR)); 
	      moneys.add(Money.of(80, DOLLAR)); 
	      moneys.add(Money.of(90, DOLLAR)); 
	      moneys.add(Money.of(120, DOLLAR)); 
	       
	      moneys.add(Money.of(120, EURO)); 
	      moneys.add(Money.of(50, EURO)); 
	      moneys.add(Money.of(80, EURO)); 
	      moneys.add(Money.of(90, EURO)); 
	      moneys.add(Money.of(120, EURO)); 
	      return moneys; 
      } 
  }
Listagem 8. Operações de agrupamento

Outro recurso interessante é realizar a conversão da moeda, permitido graças a interface ExchangeRateProvider. Atualmente existem duas implementações: uma para o ECB e a outra para o IMF. Veja um exemplo na Listagem 9.


public class ExchangeExample { 

  public static void main(String[] args) { 
     ExchangeRateProvider imfRateProvider = MonetaryConversions 
         .getExchangeRateProvider("IMF"); 
     ExchangeRateProvider ecbRateProvider = MonetaryConversions 
         .getExchangeRateProvider("ECB"); 

     CurrencyUnit real = MonetaryCurrencies.getCurrency("BRL"); 
     CurrencyUnit dollar = MonetaryCurrencies.getCurrency(Locale.US); 

     CurrencyConversion ecbDollarConvertion = ecbRateProvider 
         .getCurrencyConversion(dollar); 

     CurrencyConversion imfDollarConvertion = imfRateProvider 
         .getCurrencyConversion(dollar); 

     MonetaryAmount money = Money.of(10, real); 
     System.out.println(money.with(ecbDollarConvertion)); 
     System.out.println(money.with(imfDollarConvertion)); 
  } 

}

A expectativa é que essa nova API seja nativa no Java 9, além de outras novidades. Com isso, a ideia será facilitar e muito a vida do desenvolvedor Java, fazendo com que ele se preocupe cada vez mais com o seu negócio e menos com os processos de padronização do dinheiro.