Em Java há tipos primitivos para byte
, short
, int
e long
e a mesma coisa para float
e double
. Por que é necessário que uma pessoa defina quantos bytes devem ser usados para um valor primitivo? O tamanho não podia ser determinado dinamicamente, dependendo do tamanho do número passado?
Existem 2 razões pelas quais posso pensar:
- Definir dinamicamente o tamanho dos dados significaria que também seria necessário alterar dinamicamente. Isso pode causar problemas de desempenho?
- Talvez o programador não queira que alguém possa usar um número maior que um determinado tamanho e isso permita que ele o limite.
Eu ainda acho que poderia ter havido muito a ganhar com o simples uso de um único int
e float
tipo. Houve algum motivo específico para o Java ter decidido não seguir esse caminho?
java
language-design
data-types
numbers
yitzih
fonte
fonte
Respostas:
Como muitos aspectos do design de linguagem, trata-se de uma troca de elegância e desempenho (para não mencionar alguma influência histórica de idiomas anteriores).
Alternativas
Certamente é possível (e bastante simples) criar uma linguagem de programação que possua apenas um único tipo de números naturais
nat
. Quase todas as linguagens de programação usadas para estudos acadêmicos (por exemplo, PCF, Sistema F) possuem esse tipo de número único, que é a solução mais elegante, como você supôs. Mas o design de linguagem na prática não se trata apenas de elegância; também devemos considerar o desempenho (a extensão em que o desempenho é considerado depende da aplicação pretendida do idioma). O desempenho compreende restrições de tempo e espaço.Restrições de espaço
Deixar o programador escolher o número de bytes antecipadamente pode economizar espaço em programas com restrição de memória. Se todos os seus números forem inferiores a 256, você poderá usar 8 vezes mais
byte
s quelong
s ou usar o armazenamento salvo para objetos mais complexos. O desenvolvedor de aplicativos Java padrão não precisa se preocupar com essas restrições, mas elas surgem.Eficiência
Mesmo se ignorarmos o espaço, ainda estamos limitados pela CPU, que possui apenas instruções que operam em um número fixo de bytes (8 bytes em uma arquitetura de 64 bits). Isso significa que mesmo o fornecimento de um único
long
tipo de 8 bytes tornaria a implementação da linguagem significativamente mais simples do que ter um tipo de número natural ilimitado, podendo mapear operações aritméticas diretamente para uma única instrução subjacente da CPU. Se você permitir que o programador use números arbitrariamente grandes, uma única operação aritmética deverá ser mapeada para uma sequência de instruções complexas da máquina, o que atrasaria o programa. Este é o ponto (1) que você mencionou.Tipos de ponto flutuante
Até agora, a discussão envolveu apenas números inteiros. Os tipos de ponto flutuante são uma fera complexa, com semântica extremamente sutil e casos extremos. Assim, mesmo que poderia facilmente substituir
int
,long
,short
, ebyte
com um úniconat
tipo, não está claro o que o tipo de números de ponto flutuante ainda é . Eles não são números reais, obviamente, pois números reais não podem existir em uma linguagem de programação. Também não são números bastante racionais (embora seja fácil criar um tipo racional, se desejado). Basicamente, o IEEE decidiu um meio de aproximar números reais, e todos os idiomas (e programadores) estão presos a eles desde então.Finalmente:
Este não é um motivo válido. Em primeiro lugar, não consigo pensar em nenhuma situação em que os tipos possam codificar naturalmente os limites numéricos, para não mencionar que as chances são astronomicamente baixas de que os limites que o programador deseja impor corresponderiam exatamente aos tamanhos de qualquer um dos tipos primitivos.
fonte
type my_type = int (7, 2343)
?O motivo é muito simples: eficiência . De várias maneiras.
Tipos de dados nativos: quanto mais próximos os tipos de dados de um idioma corresponderem aos tipos de dados subjacentes do hardware, mais eficiente o idioma será considerado. (Não no sentido de que seus programas serão necessariamente eficientes, mas no sentido de que você pode, se realmente souber o que está fazendo, escrever um código que seja tão eficiente quanto o hardware.) Os tipos de dados oferecidos por Java corresponde a bytes, palavras, palavras duplas e quadrúpedes do hardware mais popular disponível no mercado. Esse é o caminho mais eficiente a seguir.
Sobrecarga injustificada em sistemas de 32 bits: se tivesse sido tomada a decisão de mapear tudo para um tamanho fixo de 64 bits, isso teria imposto uma penalidade enorme às arquiteturas de 32 bits que precisam de consideravelmente mais ciclos de clock para executar um processo de 64 bits. operação de bits que uma operação de 32 bits.
Desperdício de memória: há muito hardware por aí que não é muito exigente quanto ao alinhamento de memória (as arquiteturas Intel x86 e x64 são exemplos disso), portanto uma matriz de 100 bytes nesse hardware pode ocupar apenas 100 bytes de memória. No entanto, se você não tiver mais um byte e precisar usar um longo, a mesma matriz ocupará uma ordem de magnitude a mais de memória. E matrizes de bytes são muito comuns.
Calculando tamanhos de número: Sua noção de determinar dinamicamente o tamanho de um número inteiro, dependendo do tamanho do número passado, é muito simplista; não existe um ponto único para "passar" um número; o cálculo do tamanho de um número precisa ser executado em tempo de execução, em cada operação que exija um resultado de tamanho maior: toda vez que você incrementa um número, toda vez que você adiciona dois números, toda vez que você multiplica dois números etc.
Operações em números de tamanhos diferentes: Posteriormente, ter números de tamanhos potencialmente diferentes flutuando na memória complicaria todas as operações: Mesmo para simplesmente comparar dois números, o tempo de execução precisaria primeiro verificar se os dois números a serem comparados são iguais. tamanho e, se não, redimensione o menor para corresponder ao tamanho do maior.
Operações que requerem tamanhos específicos de operando: Certas operações bit a bit dependem do número inteiro que possui um tamanho específico. Não tendo tamanho específico pré-determinado, essas operações teriam que ser emuladas.
Sobrecarga do polimorfismo: alterar o tamanho de um número em tempo de execução significa essencialmente que ele deve ser polimórfico. Isso, por sua vez, significa que não pode ser uma primitiva de tamanho fixo alocada na pilha, deve ser um objeto alocado na pilha. Isso é terrivelmente ineficiente. (Releia o item 1 acima.)
fonte
Para evitar repetir os pontos discutidos em outras respostas, tentarei esboçar várias perspectivas.
Do ponto de vista do design da linguagem
Razões históricas
Isso já é discutido no artigo da Wikipedia sobre a história do Java e também é discutido brevemente na resposta de Marco13 .
Eu apontaria que:
Razões de eficiência
Quando a eficiência importa?
Eficiência de armazenamento (na memória ou no disco)
Eficiência de execução (na CPU ou entre CPU e memória)
A necessidade de linguagens de programação para fornecer uma abstração para números inteiros pequenos, mesmo que limitados a contextos específicos
Interoperabilidade
char
matriz de tamanho 256. (Exemplo.)BitConverter
) para ajudar a empacotar e descompactar números inteiros estreitos em fluxos de bits e fluxos de bytes.Manipulação de String
Manipulação de formato de arquivo
Desejabilidade, qualidade do software e responsabilidade do programador
Considere o seguinte cenário.
Freqüentemente, o software que pode escalar com segurança muitas ordens de magnitude deve ser projetado para esse fim, com crescente complexidade. Ele não vem automaticamente, mesmo se o problema de excesso de número inteiro for eliminado. Isso chega a um círculo completo, respondendo à perspectiva do design da linguagem: geralmente, o software que se recusa a executar um trabalho quando ocorre um estouro indesejado de número inteiro (lançando um erro ou exceção) é melhor do que o software que cumpre automaticamente as operações astronomicamente grandes.
Isso significa a perspectiva do OP,
não está correto. O programador deve ter permissão, e algumas vezes necessário, para especificar a magnitude máxima que um valor inteiro pode ter, em partes críticas do software. Como aponta a resposta de Gardenhead , os limites naturais impostos pelos tipos primitivos não são úteis para esse fim; a linguagem deve fornecer maneiras para os programadores declararem magnitudes e aplicarem esses limites.
fonte
Tudo vem do hardware.
Um byte é a menor unidade de memória endereçável na maioria dos hardwares.
Todo tipo que você mencionou é criado a partir de vários bytes.
Um byte é de 8 bits. Com isso, você pode expressar 8 booleanos, mas não pode procurar apenas um de cada vez. Você endereça 1, está endereçando todos os 8.
E costumava ser tão simples, mas depois passamos de um barramento de 8 bits para um de 16, 32 e agora de 64 bits.
O que significa que, embora ainda possamos endereçar no nível de bytes, não podemos mais recuperar um único byte da memória sem obter os bytes vizinhos.
Diante desse hardware, os designers de idiomas escolheram para nos permitir escolher tipos que nos permitissem escolher tipos que se encaixassem no hardware.
Você pode afirmar que esse detalhe pode e deve ser abstraído, especialmente em um idioma que visa executar em qualquer hardware. Isso ocultaria problemas de desempenho, mas você pode estar certo. Simplesmente não aconteceu dessa maneira.
Java realmente tenta fazer isso. Os bytes são promovidos automaticamente para Ints. Um fato que o deixará maluco na primeira vez em que tentar fazer algum trabalho sério de mudança de bits.
Então, por que não funcionou bem?
O grande ponto de venda de Java na época em que você podia se sentar com um bom algoritmo C conhecido, digitá-lo em Java e, com pequenos ajustes, ele funcionaria. E C está muito próximo do hardware.
Manter esse tamanho ativo e abstrato fora dos tipos integrais simplesmente não funcionava juntos.
Então eles poderiam ter. Eles simplesmente não.
Este é um pensamento válido. Existem métodos para fazer isso. A função de grampo para um. Um idioma pode chegar ao ponto de estabelecer limites arbitrários em seus tipos. E quando esses limites são conhecidos em tempo de compilação, isso permitiria otimizações na maneira como esses números são armazenados.
Java simplesmente não é essa linguagem.
fonte
Provavelmente, uma importante razão pela qual esses tipos existem em Java é simples e, infelizmente, não é técnica:
C e C ++ também tinham esses tipos!
Embora seja difícil fornecer uma prova de que esse é o motivo, há pelo menos algumas evidências fortes: A Oak Language Specification (Versão 0.2) contém a seguinte passagem:
Portanto, a pergunta pode se resumir a:
Por que curto, int e muito inventado em C?
Não tenho certeza se a resposta à pergunta da carta é satisfatória no contexto da pergunta que foi feita aqui. Mas, em combinação com as outras respostas aqui, pode ficar claro que pode ser benéfico ter esses tipos (independentemente de sua existência em Java ser apenas um legado do C / C ++).
As razões mais importantes em que consigo pensar são
Um byte é a menor unidade de memória endereçável (como CandiedOrange já mencionado). A
byte
é o elemento básico dos dados, que pode ser lido de um arquivo ou pela rede. Alguma representação explícita disso deve existir (e existe na maioria dos idiomas, mesmo quando às vezes aparece disfarçada).É verdade que, na prática, faria sentido representar todos os campos e variáveis locais usando um único tipo e chamar esse tipo
int
. Há uma pergunta relacionada sobre isso no stackoverflow: Por que a API Java usa int em vez de curto ou byte? . Como mencionei na minha resposta, uma justificativa para ter os tipos menores (byte
eshort
) é que você pode criar matrizes desses tipos: Java possui uma representação de matrizes que ainda estão "próximas do hardware". Em contraste com outras linguagens (e em contraste com matrizes de objetos, como umaInteger[n]
matriz), umaint[n]
matriz não é uma coleção de referências onde os valores estão espalhados pelo heap. Em vez disso, ele vaina prática, seja um bloco consecutivo den*4
bytes - um pedaço de memória com tamanho e layout de dados conhecidos. Quando você tem a opção de armazenar 1000 bytes em uma coleção de objetos de valor inteiro de tamanho arbitrário ou em umbyte[1000]
(que ocupa 1000 bytes), o último pode realmente economizar memória. (Algumas outras vantagens disso podem ser mais sutis e só se tornam óbvias ao fazer a interface do Java com bibliotecas nativas)Em relação aos pontos que você perguntou especificamente sobre:
Provavelmente seria possível definir dinamicamente o tamanho das variáveis, se alguém considerasse projetar uma linguagem de programação completamente nova do zero. Não sou especialista na construção de compiladores, mas acho que seria difícil gerenciar coleções de tipos que mudam dinamicamente de maneira sensata - principalmente quando você tem uma linguagem fortemente tipada. Portanto, provavelmente se resumiria a todos os números armazenados em um "tipo de dados de número de precisão arbitrário genérico", o que certamente teria impactos no desempenho. É claro que existem linguagens de programação fortemente tipadas e / ou que oferecem tipos de números de tamanho arbitrário, mas não acho que exista uma linguagem de programação real de propósito geral que tenha sido assim.
Notas laterais:
Você pode ter se perguntado sobre o
unsigned
modificador mencionado nas especificações do Oak. De fato, ele também contém uma observação: "unsigned
ainda não foi implementado; talvez nunca o seja". . E eles estavam certos.Além de se perguntar por que o C / C ++ tinha esses tipos inteiros diferentes, você pode se perguntar por que eles os atrapalharam tão horrivelmente que você nunca sabe quantos bits um
int
tem. As justificativas para isso geralmente estão relacionadas ao desempenho e podem ser consultadas em outros lugares.fonte
Certamente mostra que você ainda não foi ensinado sobre desempenho e arquiteturas.
Ignorando a importância do tamanho dos dados sempre atinge o desempenho, você deve usar quantos recursos forem necessários, mas não mais, sempre!
Essa é a diferença entre um programa ou sistema que faz coisas realmente simples e é incrivelmente ineficiente, exigindo muitos recursos e tornando o uso desse sistema muito caro; ou um sistema que faz muito, mas roda mais rápido que os outros e é muito barato de executar.
fonte
Existem algumas boas razões
(1) enquanto o armazenamento de uma variável de um byte com um comprimento é insignificante, o armazenamento de milhões em uma matriz é muito significativo.
(2) a aritmética "hardware nativo" baseada em tamanhos inteiros específicos pode ser muito mais eficiente e, para alguns algoritmos em algumas plataformas, isso pode ser importante.
fonte