O que fazer quando o número que queremos representar é maior que um long
? O que fazer quando queremos realizar operações sem perda de exatidão?
A resposta é utilizar um padrão de projeto - o ValueObject - e criar um objeto que represente o valor que queremos com as propriedades que queremos.
Para representar um número maior que um long
o Java disponibiliza a classe BigInteger
. Estes números muito grandes (128 bits ou mais) são muito importantes em criptografia moderna para formar chaves fortes. Sem a existência desde objeto seria impossível implementar criptografia em Java. Além disso BigInteger
oferece um conjunto de métodos que permitem realizar operações com objetos deste tipo de forma semelhante ao que fazemos com int
ou long
e algumas extra como isProbablePrime
que determina se um dado número é um número primo. Números primos são muito importantes em criptografia dai a necessidade deste tipo de método.
Para representar números decimais o Java oferece a classe BigDecimal
. Esta classe permite realizar todas as operações aritméticas - exceto divisão - sem perda de exatidão. BigDecimal
é o substituto natural de double
e float
e deve ser usado sempre que a perda de precisão não for aceitável tal como em sistema que lidam com dinheiro. Contudo, neste caso específico de lidar com dinheiro, ainda existe o problema do arredondamento e o de somar dinheiro em moedas diferentes. Para resolver o problema da representação de dinheiro a melhor solução é usar o padrão Money (que inteligentemente nem precisa de BigDecimal).
BigDecimal
conta com vários modos de arredondamento que podem ser estipulados no momento da operação ou via um objeto de contexto MathContext
que pode ser usado ao longo de várias operações.
Ambas classes BigDecimal
e BigInteger
herdam de Number
. Number
é a raiz para todos os objetos numéricos padrão na plataforma Java e permite que todos eles sejam convertidos para os tipos numéricos primitivos por métodos como doubleValue
e integerValue
, por exemplo.
Objetos para primitivos
Classes que herdam de Number
são conhecidos como objetos Wrapper (Encapsuladores). Estes objetos são equivalentes às suas contrapartes primitivas, mas não contém métodos para realizar operações aritméticas. Contudo, contêm muitos métodos uteis para converter primitivos para objetos e primitivos de, e para, String
. Assim para cada primitivo existe um objeto que herda de Number
, a saber: Byte
, Short
, Character
, Integer
, Long
, Float
e Double
. Integer
é a versão objeto de int
e Character
a versão objeto de char
. Os outros são a versão do tipo primitivo com o mesmo nome.
Poder utilizar números primitivos como se fossem objetos têm muitas vantagens. Por exemplo, podemos guarda um conjunto de número num objeto List
ou em um Map
.Sendo que esta é uma funcionalidade muito útil, a partir da versão Java 5, o Java inclui as funcionalidades de Auto-Boxing e Auto-Unboxing.
Auto-boxing significa que o compilador deteta quando um número primitivo é sendo utilizado onde se espera um objeto e automaticamente encapsula (coloca na caixa – boxes) esse primitivo no seu wrapper correspondente. A operação de auto-unboxing é o inverso e acontece quando o compilador deteta que um primitivo é esperado onde um objeto é utilizado.
Embora as operações de auto-boxing sejam uteis elas podem ser perigosas se não forem usadas corretamente. Os dois motivos principais para isso são:
-
Um objeto pode ser null
. Ao realizar o unboxing o compilador escolhe um dos métodos de Number
para o primitivo certo. Por exemplo, se for esperado um double
o compilador invoca
objecto.doubleValue()
. O problema está em que se o objeto for null
esta operação irá causar uma exceção NullPointerException
quando o código for executado. A operação de unboxing mascara este problema dos olhos desatentos e pode causar muitos problemas. Prefira sempre fazer as operações de boxing explicitamente.
-
Como Number
não contém métodos para realizar operações aritméticas sempre que um wrapper de um primitivo for utilizado numa operação o compilador irá incluir duas operações de unboxing para converter os objetos para primitivos, fazer o cálculo, e converter de volta para um objeto com uma operação de auto-boxing. São três, ou mais, operações de boxing desnecessárias que causam a criação de objeto à toa. Embora as JVM modernas não se engasguem com isso é considerado uma má prática de programação e um abuso da funcionalidade de auto-boxing totalmente desproposital. Use sempre primitivos enquanto estiver realizando cálculos. Sobretudo se para os cálculos estiver usando algum tipo de laço for
ou while
.
Operações avançadas sobre inteiros: operações binárias
Além das operações aritméticas é possível realizar operações bit-a-bit sobre todos os tipos primitivos inteiros. Estas operações são muito uteis ao trabalha com criptografia, manipulação de imagem ou de arquivos. Os operadores binários são o &
( operador E) , |
(operador OU), ^
( operador XOR) e !
(operador NOT). Não confundir com os operadores lógicos &&
e ||
que apenas atuam sobre o tipo primitivo boolean
que, em Java, não é um número.
Mas o que fazer quando queremos manipular mais de 64 bits? Para resolver isso você pode usar BigInteger
que também conta com operações bit-a-bit ou utilizar BitSet
. BitSet
é uma classe especializada em trabalhar com conjuntos de bits e fornece operações para operar sobre eles de forma mais eficiente e além do limite de 64 bits.
Operações avançadas sobre decimais e a classe java.util.Math
Nem só de somas e multiplicações vivem as operações matemáticas. Funções mais complexas como seno e co-seno, potencia, raiz quadrada e logaritmo são operações comuns e necessárias em vários ramos.
Java dá suporte a estas operações através da classe java.util.Math
. Esta classe apenas contém métodos estáticos úteis para estes tipos de operações e outras funções genérica como valor absoluto (abs) , sinal (sgn) e arredondamento (round).
Estas funções trabalham com double
e como vimos isso pode significar problemas. Contudo, normalmente, para este tipo avançado de operações isso não é um problema muito grave, mas como sempre acontece ao trabalhar com double, surpresas desagradáveis podem acontecer. Portanto, utilize com cautela.