Por que a API Java usa int
, quando short
ou mesmo byte
seria suficiente?
Exemplo: O DAY_OF_WEEK
campo na classe Calendar
usa int
.
Se a diferença é mínima demais, por que esses tipos de dados ( short
, int
) existem?
Por que a API Java usa int
, quando short
ou mesmo byte
seria suficiente?
Exemplo: O DAY_OF_WEEK
campo na classe Calendar
usa int
.
Se a diferença é mínima demais, por que esses tipos de dados ( short
, int
) existem?
Respostas:
Algumas das razões já foram apontadas. Por exemplo, o fato de "... (Quase) Todas as operações no byte, curto promoverão essas primitivas para int" . No entanto, a próxima pergunta óbvia seria: POR QUE esses tipos são promovidos int
?
Então, para ir um nível mais além: a resposta pode estar simplesmente relacionada ao conjunto de instruções da Java Virtual Machine. Conforme resumido na Tabela em Java Virtual Machine Specification , todas as operações aritméticas integrais, como adição, divisão e outras, estão disponíveis apenas para o tipo int
e o tipo long
, e não para os tipos menores.
(Além disso: os tipos menores ( byte
e short
) destinam-se basicamente apenas a matrizes . Uma matriz como new byte[1000]
terá 1000 bytes e uma matriz como new int[1000]
4000 bytes)
Agora, é claro, alguém poderia dizer que "... a próxima pergunta óbvia seria: POR QUE essas instruções são oferecidas apenas para int
(e long
)?" .
Um motivo é mencionado nas especificações da JVM mencionadas acima:
Se cada instrução digitada suportasse todos os tipos de dados de tempo de execução da Java Virtual Machine, haveria mais instruções do que poderiam ser representadas em um byte
Além disso, a Java Virtual Machine pode ser considerada como uma abstração de um processador real. E a introdução da Unidade Lógica Aritmética dedicada para tipos menores não valeria o esforço: precisaria de transistores adicionais, mas ainda assim poderia executar apenas uma adição em um ciclo de clock. A arquitetura dominante quando a JVM foi projetada era de 32 bits, ideal para 32 bits int
. (As operações que envolvem um long
valor de 64 bits são implementadas como um caso especial).
(Nota: o último parágrafo é um pouco simplificado, considerando possíveis vetorizações etc., mas deve fornecer a idéia básica sem se aprofundar nos tópicos de design do processador)
EDIT: Um pequeno adendo, focado no exemplo da pergunta, mas em um sentido mais geral: também se poderia perguntar se não seria benéfico armazenar campos usando os tipos menores. Por exemplo, pode-se pensar que a memória pode ser salva armazenando Calendar.DAY_OF_WEEK
como a byte
. Mas aqui o Java Class File Format entra em jogo: Todos os campos em um arquivo de classe ocupam pelo menos um "slot", que tem o tamanho de um int
(32 bits). (Os campos "largos" double
e long
ocupam dois slots). Declarar de forma explícita um campo como short
ou byte
não guardar qualquer memória também.
int
. Se você tem uma referência a outra implementação, atualizo a resposta e insiro o link adequadamente.
(Quase) Todas as operações em byte
, short
vai promovê-los para int
, por exemplo, você não pode escrever:
short x = 1;
short y = 2;
short z = x + y; //error
A aritmética é mais fácil e direta ao usar int
, sem necessidade de fundição.
Em termos de espaço, faz uma diferença muito pequena. byte
e short
complicaria as coisas, não acho que essa micro otimização valha a pena, pois estamos falando de uma quantidade fixa de variáveis.
byte
é relevante e útil quando você programa para dispositivos incorporados ou ao lidar com arquivos / redes. Além disso, essas primitivas são limitadas, e se os cálculos puderem exceder seus limites no futuro? Tente pensar em uma extensão para a Calendar
classe que pode evoluir em números maiores.
Observe também que em processadores de 64 bits, os moradores serão guardados nos registos e não vai usar todos os recursos, portanto, usando int
, short
e outros primitivos não fará qualquer diferença. Além disso, muitas implementações Java alinham variáveis * (e objetos).
* byte
e short
ocupam o mesmo espaço como int
se fossem variáveis locais , variáveis de classe ou mesmo variáveis de instância . Por quê? Como na maioria dos sistemas de computadores, os endereços das variáveis são alinhados ; por exemplo, se você usar um único byte, você terá dois bytes - um para a variável em si e outro para o preenchimento.
Por outro lado, em matrizes, byte
use 1 byte, short
2 e int
4 bytes, porque nas matrizes apenas o início e talvez o final dela devem estar alinhados. Isso fará a diferença no caso de você querer usar, por exemplo System.arraycopy()
, e notará realmente uma diferença de desempenho.
Como as operações aritméticas são mais fáceis ao usar números inteiros em comparação com curtos. Suponha que as constantes foram realmente modeladas por short
valores. Então você teria que usar a API desta maneira:
short month = Calendar.JUNE;
month = month + (short) 1; // is july
Observe a transmissão explícita. Valores curtos são implicitamente promovidos para int
valores quando usados em operações aritméticas. (Na pilha de operandos, os shorts são expressos como ints.) Isso seria bastante complicado de usar, e é por isso que os int
valores são geralmente preferidos para constantes.
Comparado a isso, o ganho em eficiência de armazenamento é mínimo, porque existe apenas um número fixo dessas constantes. Estamos falando de 40 constantes. Alterar o armazenamento de int
para para short
protegeria você 40 * 16 bit = 80 byte
. Veja esta resposta para referência adicional.
Se você usasse a filosofia em que constantes integrais são armazenadas no menor tipo em que se encaixam, o Java teria um problema sério: sempre que os programadores escrevem código usando constantes integrais, eles devem prestar muita atenção ao código para verificar se o tipo de as constantes são importantes e, se assim for, procure o tipo na documentação e / ou faça as conversões de tipo necessárias.
Então, agora que descrevemos um problema sério, que benefícios você poderia esperar obter com essa filosofia? Eu não ficaria surpreso se o único efeito observável em tempo de execução dessa alteração fosse o tipo que você obtém quando procura a constante por meio de reflexão. (e, é claro, quaisquer que sejam os erros introduzidos por programadores preguiçosos / inconscientes que não respondem corretamente aos tipos de constantes)
Pesar os prós e os contras é muito fácil: é uma filosofia ruim.
A complexidade do design de uma máquina virtual é uma função de quantos tipos de operações ela pode executar. É mais fácil ter quatro implementações de uma instrução como "multiplicar" - uma para número inteiro de 32 bits, número inteiro de 64 bits, ponto flutuante de 32 bits e ponto flutuante de 64 bits - do que ter, além disso acima, versões para os tipos numéricos menores também. Uma questão de design mais interessante é por que deveria haver quatro tipos, em vez de menos (executando todos os cálculos inteiros com números inteiros de 64 bits e / ou fazendo todos os cálculos de ponto flutuante com valores de ponto flutuante de 64 bits). A razão para o uso de números inteiros de 32 bits é que o Java era executado em muitas plataformas nas quais os tipos de 32 bits poderiam ser utilizados tão rapidamente quanto os tipos de 16 ou 8 bits, mas as operações nos tipos de 64 bits seriam visivelmente Mais devagar.tendo apenas tipos de 32 bits.
Quanto à realização de cálculos de ponto flutuante em valores de 32 bits, as vantagens são um pouco menos claras. Existem algumas plataformas em que uma computação comofloat a=b+c+d;
pode ser executado mais rapidamente convertendo todos os operandos para um tipo de precisão mais alta, adicionando-os e depois convertendo o resultado em um número de ponto flutuante de 32 bits para armazenamento. Existem outras plataformas nas quais seria mais eficiente executar todos os cálculos usando valores de ponto flutuante de 32 bits. Os criadores de Java decidiram que todas as plataformas deveriam fazer as coisas da mesma maneira e que deveriam favorecer as plataformas de hardware para as quais os cálculos de ponto flutuante de 32 bits são mais rápidos que os mais longos, mesmo que esse PC severamente degradado tanto a velocidade e precisão da matemática de ponto flutuante em um PC típico, bem como em muitas máquinas sem unidades de ponto flutuante. Observe, btw, que dependendo dos valores de b, c e d, usando cálculos intermediários de maior precisão ao calcular expressões como a mencionada acimafloat a=b+c+d;
algumas vezes produz resultados significativamente mais precisos do que seria alcançado com todos os operandos intermediários calculados com float
precisão, mas algumas vezes produz um valor um pouco menos preciso. De qualquer forma, a Sun decidiu que tudo deveria ser feito da mesma maneira e optou por usar float
valores de precisão mínima .
Observe que as principais vantagens de tipos de dados menores se tornam aparentes quando um grande número deles é armazenado juntos em uma matriz; mesmo que não houvesse vantagem em ter variáveis individuais de tipos menores que 64 bits, vale a pena ter matrizes que possam armazenar valores menores de maneira mais compacta; ter uma variável local em byte
vez de long
salvar sete bytes; tendo uma matriz de 1.000.000 de números, mantenha cada número como um byte
e não comolong
acena 7.000.000 de bytes. Como cada tipo de matriz precisa apenas suportar algumas operações (principalmente ler um item, armazenar um item, copiar um intervalo de itens dentro de uma matriz ou copiar um intervalo de itens de uma matriz para outra), a complexidade adicional de ter mais tipos de matriz não é tão grave quanto a complexidade de ter mais tipos de valores numéricos discretos diretamente utilizáveis.
Na verdade, haveria uma pequena vantagem. Se você tem um
class MyTimeAndDayOfWeek {
byte dayOfWeek;
byte hour;
byte minute;
byte second;
}
em uma JVM típica, ele precisa de tanto espaço quanto uma classe que contém uma única int
. O consumo de memória é arredondado para um próximo múltiplo de 8 ou 16 bytes (IIRC, configurável); portanto, os casos em que há economia real são bastante raros.
Essa classe seria um pouco mais fácil de usar se os Calendar
métodos correspondentes retornassem a byte
. Mas não existem Calendar
métodos, apenas o get(int)
que deve retornar um int
por causa de outros campos. Cada operação em tipos menores promove int
, então você precisa de muita conversão.
Muito provavelmente, você desistirá e mudará para um int
ou gravará setters como
void setDayOfWeek(int dayOfWeek) {
this.dayOfWeek = checkedCastToByte(dayOfWeek);
}
Então o tipo de DAY_OF_WEEK
não importa, de qualquer maneira.