Analisando na Prática o uso de Primitivos e Wrappers

O uso de tipos primitivos e Wrappers é um tema que traz diversas discussões, inclusive existem vários tutorias que mostram a aplicabilidade de cada uma das estratégias em diferentes cenários.

As Classes Wrappers


As classes Wrappers são aquelas que representam os tipos primitivos da linguagem Java, há algum tempo existiam autores que falavam que Java não era uma linguagem 100% OO por tratar os tipos básicos como primitivos, mas isso acabou quando adicionaram os tipos Wrappers a linguagem.

O Wrappers são classes imutáveis, e possuem vários métodos utilitários que facilitam conversões, parses, formatação, entre outras funcionalidades bem utilizadas no dia a dia.

Vamos analisar cada tipo primitivo e sua representação em classe Wrapper:
  • byte
    • Byte;
  • short
    • Short;
  • int
    • Integer;
  • long
    • Long;
  • boolean
    • Boolean;
  • char
    • Character;
  • float
    • Float;
  • double
    • Double;

Comparação entre Wrappers


Vamos entender como esses objetos funcionam no quesito comparação, já analisei casos onde uma comparação de valores iguais não resultavam em true, vamos analisar alguns motivos para isso ocorrer:

 Integer oneWrapper = 1; //Autoboxing do valor <1> para a variavel oneWrapper
 Integer twoWrapper = 1; //Autoboxing do valor <1> para a variavel twoWrapper
  
 System.out.println(" Comparação de Wrappers \n");
 System.out.println(oneWrapper == twoWrapper);
 System.out.println(oneWrapper != twoWrapper);
 System.out.println(oneWrapper.equals(twoWrapper));

O Resultado do nosso código será o seguinte:
oneWrapper == twoWrapper : true
oneWrapper != twoWrapper : false
oneWrapper.equals(twoWrapper) : true

Para entender os resultado, vamos lembrar que quando criamos as variáveis do tipo Wrapper realizando a atribuição direta de um valor, o compilador realiza o autoboxing, que é a conversão de um tipo primitivo em tipo Wrapper.

Na primeira condição temos oneWrapper == twoWrapper, quando comparamos objetos com == a comparação acontece entre as referências dos objetos, e neste caso como as variáveis foram criadas através de atribuição direta, o valor 1 será reaproveitado do pool de objetos, assim as variáveis apontaram para o mesmo objeto em memória.

Na segunda condição temos oneWrapper != twoWrapper, aqui a explicação é a mesma da primeira.

Na terceira condição temos oneWrapper.equals(twoWrapper), quando aprendemos manipulação de objetos com Java, analisamos o método equals(), que é o método que utilizamos para analisar se um objeto é igual ao outrolembrando que os Wrappers são objetos, portanto o equals é a melhor forma de realizar a comparação.


Integer oneWrapper = 1; //Autoboxing do valor <1> para a variavel oneWrapper
Integer twoWrapper = new Integer(1); //Criação sem reaproveitar o objeto do pool
  
System.out.println(" Comparação de Wrappers com Instância direta \n");
System.out.println(oneWrapper == twoWrapper);
System.out.println(oneWrapper != twoWrapper);
System.out.println(oneWrapper.equals(twoWrapper));

O resultado será o seguinte:

oneWrapper == twoWrapper        : false
oneWrapper != twoWrapper         : true
oneWrapper.equals(twoWrapper) : true

Vamos entender o resultado, no primeiro exemplo é feita uma comparação entre 2 Wrappers utilizando o operador ==, mas os objetos foram criados de forma diferente, na variável oneWrapper foi realizado a atribuição do valor diretamente onde irá ser realizado o autoboxing, já a variável twoWrapper foi criada utilizando o construtor de Integer.

Ao criar utilizando o construtor, não reaproveitamos objetos do pool de objetos, ou seja, foi criado outro objeto em memória, e como já sabemos o operador == realiza a comparação entre referências, portanto resultado da comparação é false.

No segundo exemplo utilizando o operador != aqui o conceito é o mesmo do exemplo anterior, o valor de ambas as variáveis é 1, mas lembrando que o operador irá comparar referências que são diferentes portanto o resultado é true.

Já no terceiro exemplo utilizamos o método equals() para realizar a comparação, neste caso o que será avaliado é o valor das variáveis, portanto o resultado é true.

Analisando a Performance


Um ponto importante é analisarmos a performance das operações de boxing dentro de estruturas de repetição, estas operações são custosas pois exigem  muitas transformação em laços.

Vamos analisar alguns códigos para entendermos os casos.

long init = System.currentTimeMillis();
  
long result = 0;
  
for (long i = 0; i < 1000000000; i++) {
     result += i;
}
  
long end = System.currentTimeMillis();
  
System.out.println(" Tempo: "+(end - init)); 

O Resultado da execução é:  Tempo: 460

Esse é um código onde apenas executamos um soma entre as variáveis result e i utilizando o operador += , perceba que como estamos usando o tipo long a soma acontece sem maiores problemas.


long init = System.currentTimeMillis();
  
Long result = 0L;
  
for (long i = 0; i < 1000000000; i++) {
     result += i;
}
  
long end = System.currentTimeMillis();
  
System.out.println(" Tempo: "+(end - init));

O Resultado da execução é:  Tempo: 5043

Este exemplo é quase igual ao anterior, a unica diferença que a variável result agora é do tipo Wrapper Long e não long primitiva, após a execução notamos um tempo superior ao do primeiro exemplo.

Acontece que o operador += só pode ser usado em operações de tipos primitivos, e em nosso exemplo a variável result é uma Wrapper, e neste caso a JVM é obrigada a realizar a operação de Autoboxing, deixando o laço mais lento.

Conclusão


O uso de Wrapper e Primitivos deve ser analisado em cada cenário, levando em consideração as operações que serão executadas, performance, cálculos, entre outros fatores.

Os recursos de Autoboxing e Unboxing são muito bons e deixaram a criação e manipulação de objetos mais produtiva, mas sempre devemos nos atentar para não utilizar em lugares onde poderemos ter problemas de performance.

Até mais.

Código Fonte

Referências


Nenhum comentário:

Postar um comentário