visão geral
neste tutorial, mostramos os prós e contras do uso de tipos primitivos Java e suas contrapartidas envolvidas.
Java Type System
Java has a two-fold type system consisting of primitives such as int, boolean and reference types such as Integer, Boolean. Todo tipo primitivo corresponde a um tipo de referência.
cada objeto contém um único valor do tipo primitivo correspondente. As classes wrapper são imutáveis (de modo que seu estado não pode mudar uma vez que o objeto é construído) e são finais (de modo que não podemos herdar deles).
Sob o capô, o Java executa uma conversão entre os primitivos e tipos de referência se de um tipo real é diferente da declarada:
Integer j = 1; // autoboxingint i = new Integer(1); // unboxing
O processo de conversão de um tipo primitivo para uma referência de um é chamado de autoboxing, o processo inverso é chamado de unboxing.
prós e contras
a decisão que objeto deve ser usado é baseada no desempenho da aplicação que tentamos alcançar, quanta memória disponível temos, a quantidade de memória disponível e quais valores padrão que devemos lidar.se não enfrentarmos nenhum desses, podemos ignorar estas considerações, embora valha a pena conhecê-las.
3.1. Pegada de memória de Item único
apenas para a referência, as variáveis de tipo primitivo têm o seguinte impacto na memória:
- boolean – 1 bit
- byte 8 bits
- short, char – 16 bits
- int, float – 32 bits
- long double – 64 bits
Na prática, esses valores podem variar dependendo da Máquina Virtual em execução. No VM do Oracle, o tipo booleano, por exemplo, é mapeado para valores int 0 e 1, então ele leva 32 bits, como descrito aqui: tipos primitivos e valores.
variáveis destes tipos vivem na pilha e, portanto, são acessadas rapidamente. Para os detalhes, recomendamos o nosso tutorial sobre o modelo de memória Java.
os tipos de referência são objetos, eles vivem no heap e são relativamente lentos de acesso. Eles têm uma certa sobrecarga em relação aos seus homólogos primitivos.
os valores concretos das despesas gerais são, em geral, específicos da JVM. Aqui, apresentamos os resultados para uma máquina virtual de 64 bits com estes parâmetros:
java 10.0.1 2018-04-17Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10)Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)
Para obter um objeto interno da estrutura, podemos usar o Objeto Java ferramenta de Layout (veja nosso outro tutorial sobre como obter o tamanho de um objeto).
acontece que uma única instância de um tipo de referência sobre este JVM ocupa 128 bits, exceto para Long e Double que ocupam 192 bits:
- Boolean – 128 bits
- Byte – 128 bits
- Curto, Personagem – 128 bits
- Integer, Float – 128 bits
- Longo, Clique duas vezes 192 bits
podemos ver que uma única variável do tipo Boolean ocupa tanto espaço quanto de 128 primitivo, enquanto uma variável de número Inteiro ocupa tanto espaço como os quatro int queridos.
3, 2. Pegada de memória para Arrays
a situação torna-se mais interessante se compararmos quanto a memória ocupa arrays dos tipos em consideração.
Quando podemos criar matrizes com as diversas número de elementos de cada tipo, obtemos um enredo:
o que demonstra que os tipos são agrupados em quatro famílias com respeito a como a memória m(s) depende do número de elementos de s da matriz:
- long double: m(s) = 128 + 64 s
- short, char: m(s) = 128 + 64
- byte, boolean: m(s) = 128 + 64
- o resto: m ( s) = 128 + 64
onde os parêntesis rectos indicam a função de tecto padrão.surpreendentemente, as matrizes dos tipos primitivos longos e duplos consomem mais memória do que as suas classes espectrais longas e duplas.
Podemos ver que as matrizes de um único elemento de tipos primitivos são quase sempre mais caras (exceto para o longo e o dobro) do que o tipo de referência correspondente.
3, 3. Desempenho
o desempenho de um código Java é uma questão bastante sutil, depende muito do hardware em que o código funciona, do compilador que pode realizar certas otimizações, no estado da máquina virtual, na atividade de outros processos no sistema operacional.
Como já mencionamos, os tipos primitivos vivem na pilha enquanto os tipos de referência vivem na pilha. Este é um fator dominante que determina a rapidez com que os objetos são acessados.
Para demonstrar o quanto as operações para tipos primitivos são mais rápidas do que aquelas para as classes de wrapper, vamos criar uma de cinco milhões de elemento de matriz em que todos os elementos são iguais, exceto para o último; em seguida, realizamos uma pesquisa para esse elemento:
while (!pivot.equals(elements)) { index++;}
e comparar o desempenho desta operação para o caso em que a matriz contém variáveis de tipos primitivos e para o caso de quando ele contém objetos dos tipos de referência.
utilizamos o conhecido JMH ferramenta de avaliação de desempenho (veja o nosso tutorial sobre como usá-lo), e os resultados da operação de pesquisa pode ser resumido no quadro seguinte:
Até para uma operação simples, podemos ver que é necessário mais tempo para realizar a operação para classes de wrapper.
no caso de operações mais complicadas como soma, multiplicação ou divisão, a diferença de velocidade pode subir.
3, 4. Valores padrão
valores padrão dos tipos primitivos são 0 (na representação correspondente, ou seja, 0, 0.0d etc) para os tipos numéricos, false para o tipo booleano, \u0000 para o tipo char. Para as classes wrapper, o valor padrão é nulo.
significa que os tipos primitivos podem adquirir valores apenas de seus domínios, enquanto os tipos de referência podem adquirir um valor (nulo) que em algum sentido não pertence a seus domínios.
embora não seja considerado uma boa prática deixar variáveis não inicializadas, às vezes podemos atribuir um valor após a sua criação.
em tal situação, quando uma variável de tipo primitivo tem um valor que é igual ao seu tipo padrão, devemos descobrir se a variável foi realmente inicializada.
não existe tal problema com as variáveis da classe wrapper, uma vez que o valor nulo é uma indicação evidente de que a variável não foi inicializada.
uso
como vimos, os tipos primitivos são muito mais rápidos e requerem muito menos memória. Por isso, podemos preferir usá-los.
Por outro lado, a especificação atual da linguagem Java não permite o uso de tipos primitivos nos tipos parametrizados (genéricos), nas Coleções Java ou na API de reflexão.
Quando a nossa aplicação necessita de colecções com um grande número de elementos, devemos considerar a utilização de arrays com o tipo mais “económico” possível, como é ilustrado no gráfico acima.
Conclusion
It this tutorial, we illustrated that the objects in Java are slower and have a bigger memory impact than their primitive analogs.
Como sempre, excertos de código podem ser encontrados no nosso repositório no GitHub.
Começar com Mola 5 e Primavera de Inicialização 2, através do Aprender a Primavera curso:
>> confira O CURSO