En Java hay tipos primitivos para byte
, short
, int
y long
y lo mismo para float
y double
. ¿Por qué es necesario que una persona establezca cuántos bytes se deben usar para un valor primitivo? ¿No podría determinarse el tamaño dinámicamente dependiendo de qué tan grande fue el número pasado?
Hay dos razones por las que puedo pensar:
- Establecer dinámicamente el tamaño de los datos significaría que también debería ser capaz de cambiar dinámicamente. ¿Esto podría causar problemas de rendimiento?
- Quizás el programador no quiera que alguien pueda usar un número mayor que cierto tamaño y esto les permite limitarlo.
Todavía creo que podría haber mucho que ganar simplemente usando un solo int
y float
tipo, ¿hubo alguna razón específica por la que Java decidió no seguir esta ruta?
java
language-design
data-types
numbers
yitzih
fuente
fuente
Respuestas:
Al igual que muchos aspectos del diseño del lenguaje, se trata de un equilibrio entre la elegancia y el rendimiento (sin mencionar alguna influencia histórica de los idiomas anteriores).
Alternativas
Ciertamente es posible (y bastante simple) crear un lenguaje de programación que tenga un solo tipo de números naturales
nat
. Casi todos los lenguajes de programación utilizados para el estudio académico (por ejemplo, PCF, Sistema F) tienen este tipo de número único, que es la solución más elegante, como suponía. Pero el diseño del lenguaje en la práctica no se trata solo de elegancia; También debemos considerar el rendimiento (la medida en que se considera el rendimiento depende de la aplicación prevista del lenguaje). El rendimiento comprende limitaciones de tiempo y espacio.Limitaciones de espacio
Permitir que el programador elija el número de bytes por adelantado puede ahorrar espacio en programas con limitaciones de memoria. Si todos sus números van a ser menores que 256, entonces puede usar 8 veces más
byte
s quelong
s, o usar el almacenamiento guardado para objetos más complejos. El desarrollador estándar de aplicaciones Java no tiene que preocuparse por estas restricciones, pero sí aparecen.Eficiencia
Incluso si ignoramos el espacio, todavía estamos limitados por la CPU, que solo tiene instrucciones que operan en un número fijo de bytes (8 bytes en una arquitectura de 64 bits). Eso significa que incluso proporcionar un solo tipo de 8 bytes
long
haría que la implementación del lenguaje sea significativamente más simple que tener un tipo de número natural ilimitado, al poder asignar operaciones aritméticas directamente a una sola instrucción subyacente de la CPU. Si permite que el programador use números arbitrariamente grandes, entonces una sola operación aritmética debe asignarse a una secuencia de instrucciones complejas de la máquina, lo que ralentizaría el programa. Este es el punto (1) que mencionaste.Tipos de punto flotante
La discusión hasta ahora solo se ha referido a los enteros. Los tipos de punto flotante son una bestia compleja, con una semántica y casos extremos extremadamente sutiles. Por lo tanto, a pesar de que podría fácilmente reemplazar
int
,long
,short
, ybyte
con un solonat
tipo, no está claro cuál es el tipo de números de punto flotante, incluso es . No son números reales, obviamente, ya que los números reales no pueden existir en un lenguaje de programación. Tampoco son números bastante racionales (aunque es sencillo crear un tipo racional si se desea). Básicamente, IEEE decidió una forma de aproximar un poco los números reales, y todos los lenguajes (y programadores) se han quedado con ellos desde entonces.Finalmente:
Esta no es una razón válida. En primer lugar, no puedo pensar en ninguna situación en la que los tipos puedan codificar naturalmente los límites numéricos, sin mencionar que las posibilidades son astronómicamente bajas de que los límites que el programador quiere imponer corresponderían exactamente a los tamaños de cualquiera de los tipos primitivos.
fuente
type my_type = int (7, 2343)
?La razón es muy simple: eficiencia . De múltiples maneras.
Tipos de datos nativos: cuanto más se acerquen los tipos de datos de un idioma a los tipos de datos subyacentes del hardware, más eficiente se considera que es el idioma. (No en el sentido de que sus programas serán necesariamente eficientes, pero en el sentido de que, si realmente sabe lo que está haciendo, puede escribir un código que se ejecutará tan eficientemente como el hardware puede ejecutarlo). Los tipos de datos ofrecidos por Java corresponden a bytes, palabras, palabras dobles y palabras cuádruples del hardware más popular que existe. Ese es el camino más eficiente.
Gastos indirectos injustificados en sistemas de 32 bits: si se hubiera tomado la decisión de asignar todo a un tamaño fijo de 64 bits, esto habría impuesto una gran penalización a las arquitecturas de 32 bits que necesitan considerablemente más ciclos de reloj para realizar un 64- operación de bits que una operación de 32 bits.
Desperdicio de memoria: hay mucho hardware por ahí que no es demasiado exigente con la alineación de la memoria (las arquitecturas Intel x86 y x64 son ejemplos de eso), por lo que una matriz de 100 bytes en ese hardware puede ocupar solo 100 bytes de memoria. Sin embargo, si ya no tiene un byte y tiene que usar un largo, la misma matriz ocupará un orden de magnitud más memoria. Y los conjuntos de bytes son muy comunes.
Cálculo de tamaños de números: su noción de determinar el tamaño de un número entero dinámicamente dependiendo de qué tan grande fue el número pasado es demasiado simplista; no hay un solo punto de "pasar" un número; el cálculo de qué tan grande debe ser un número debe realizarse en tiempo de ejecución, en cada operación que pueda requerir un resultado de un tamaño mayor: cada vez que incrementa un número, cada vez que agrega dos números, cada vez que multiplica dos números, etc.
Operaciones en números de diferentes tamaños: posteriormente, tener números de tamaños potencialmente diferentes flotando en la memoria complicaría todas las operaciones: incluso para comparar simplemente dos números, el tiempo de ejecución primero tendría que verificar si ambos números a comparar son iguales tamaño, y si no, cambie el tamaño del más pequeño para que coincida con el tamaño del más grande.
Operaciones que requieren tamaños de operando específicos: Ciertas operaciones de bits confían en que el entero tenga un tamaño específico. Al no tener un tamaño específico predeterminado, estas operaciones tendrían que ser emuladas.
Sobrecarga del polimorfismo: cambiar el tamaño de un número en tiempo de ejecución esencialmente significa que tiene que ser polimórfico. Esto a su vez significa que no puede ser una primitiva de tamaño fijo asignada en la pila, tiene que ser un objeto, asignado en el montón. Eso es terriblemente ineficiente. (Vuelva a leer # 1 arriba).
fuente
Para evitar repetir los puntos que se han discutido en otras respuestas, intentaré esbozar múltiples perspectivas.
Desde la perspectiva del diseño del lenguaje
Razones históricas
Esto ya se discute en el artículo de Wikipedia sobre la historia de Java, y también se discute brevemente en la respuesta de Marco13 .
Yo señalaría que:
Razones de eficiencia
¿Cuándo importa la eficiencia?
Eficiencia de almacenamiento (en memoria o en disco)
Eficiencia de ejecución (dentro de la CPU, o entre la CPU y la memoria)
La necesidad de lenguajes de programación para proporcionar una abstracción para enteros pequeños, incluso si se limita a contextos específicos
Interoperabilidad
char
matriz de tamaño 256. (Ejemplo).BitConverter
) para ayudar al empaquetado y desempaquetado de enteros estrechos en flujos de bits y flujos de bytes.Manejo de cuerdas
Manejo de formato de archivo
Conveniencia, calidad del software y responsabilidad del programador.
Considere el siguiente escenario.
A menudo, el software que puede escalar de forma segura muchos órdenes de magnitud debe diseñarse para ese propósito, con una complejidad creciente. No llega automáticamente incluso si se elimina el problema del desbordamiento de enteros. Esto llega a un círculo completo que responde a la perspectiva del diseño del lenguaje: a menudo, el software que se niega a realizar un trabajo cuando se produce un desbordamiento entero involuntario (arrojando un error o excepción) es mejor que el software que cumple automáticamente con operaciones astronómicamente grandes.
Esto significa la perspectiva del OP,
no es correcto. Al programador se le debe permitir, y a veces se le requiere, especificar la magnitud máxima que puede tomar un valor entero, en partes críticas del software. Como señala la respuesta de gardenhead , los límites naturales impuestos por los tipos primitivos no son útiles para este propósito; el lenguaje debe proporcionar formas para que los programadores declaren magnitudes y apliquen tales límites.
fuente
Todo proviene del hardware.
Un byte es la unidad de memoria direccionable más pequeña en la mayoría del hardware.
Cada tipo que acaba de mencionar está construido a partir de un múltiplo de bytes.
Un byte es de 8 bits. Con eso puedes expresar 8 booleanos pero no puedes buscar solo uno a la vez. Se dirige a 1, se dirige a los 8.
Y solía ser así de simple, pero luego pasamos de un bus de 8 bits a un bus de 16, 32 y ahora de 64 bits.
Lo que significa que si bien aún podemos direccionar en el nivel de bytes, ya no podemos recuperar un solo byte de la memoria sin obtener sus bytes vecinos.
Frente a este hardware, los diseñadores de idiomas eligieron permitirnos elegir tipos que nos permitieran elegir tipos que se ajustaran al hardware.
Puede afirmar que ese detalle puede y debe abstraerse, especialmente en un lenguaje que apunta a ejecutarse en cualquier hardware. Esto tendría problemas de rendimiento ocultos, pero puede que tenga razón. Simplemente no sucedió de esa manera.
Java realmente intenta hacer esto. Los bytes se promueven automáticamente a Ints. Un hecho que te volverá loco la primera vez que intentes hacer un trabajo de cambio de bits serio en él.
Entonces, ¿por qué no funcionó bien?
El gran argumento de venta de Java en el pasado es que podrías sentarte con un algoritmo C bien conocido, escribirlo en Java y con pequeños ajustes funcionaría. Y C está muy cerca del hardware.
Mantener ese tamaño y abstraer el tamaño de los tipos integrales simplemente no funcionó en conjunto.
Entonces podrían haberlo hecho. Simplemente no lo hicieron.
Este es un pensamiento válido. Hay métodos para hacer esto. La función de sujeción para uno. Un lenguaje podría llegar a romper límites arbitrarios en sus tipos. Y cuando esos límites se conocen en tiempo de compilación, eso permitiría optimizaciones en cómo se almacenan esos números.
Java simplemente no es ese lenguaje.
fuente
Probablemente, una razón importante de por qué existen estos tipos en Java es simple y angustiosamente no técnica:
¡C y C ++ también tenían estos tipos!
Aunque es difícil proporcionar una prueba de que esta es la razón, hay al menos algunas pruebas sólidas: la especificación del lenguaje Oak (versión 0.2) contiene el siguiente pasaje:
Entonces la pregunta podría reducirse a:
¿Por qué se inventaron short, int y long en C?
No estoy seguro de si la respuesta a la pregunta de la carta es satisfactoria en el contexto de la pregunta que se hizo aquí. Pero en combinación con las otras respuestas aquí, podría quedar claro que puede ser beneficioso tener estos tipos (independientemente de si su existencia en Java es solo un legado de C / C ++).
Las razones más importantes en las que puedo pensar son
Un byte es la unidad de memoria direccionable más pequeña (como ya mencionó CandiedOrange). A
byte
es el bloque de construcción elemental de datos, que se puede leer desde un archivo o a través de la red. Debe existir alguna representación explícita de esto (y existe en la mayoría de los idiomas, incluso cuando a veces viene disfrazado).Es cierto que, en la práctica, tendría sentido representar todos los campos y variables locales usando un solo tipo, y llamar a este tipo
int
. Hay una pregunta relacionada al respecto en stackoverflow: ¿Por qué la API de Java usa int en lugar de short o byte? . Como mencioné en mi respuesta allí, una justificación para tener los tipos más pequeños (byte
yshort
) es que puede crear matrices de estos tipos: Java tiene una representación de matrices que todavía está bastante "cerca del hardware". A diferencia de otros lenguajes (y en contraste con las matrices de objetos, como unaInteger[n]
matriz), unaint[n]
matriz no es una colección de referencias donde los valores están dispersos por todo el montón. En cambio, lo haráen la práctica, sea un bloque consecutivo den*4
bytes: un trozo de memoria con un tamaño y un diseño de datos conocidos. Cuando tiene la opción de almacenar 1000 bytes en una colección de objetos de valor entero de tamaño arbitrario, o en unbyte[1000]
(que toma 1000 bytes), este último puede ahorrar algo de memoria. (Algunas otras ventajas de esto pueden ser más sutiles y solo resultar obvias al interactuar Java con bibliotecas nativas)Con respecto a los puntos sobre los que ha preguntado específicamente:
Es probable que sea posible establecer dinámicamente el tamaño de las variables, si se considera diseñar un lenguaje de programación completamente nuevo desde cero. No soy un experto en la construcción de compiladores, pero creo que sería difícil manipular sensiblemente colecciones de tipos que cambian dinámicamente, especialmente cuando se tiene un lenguaje fuertemente tipado. Por lo tanto, probablemente se reduciría a todos los números que se almacenan en un "tipo de datos de números de precisión genéricos y arbitrarios", lo que ciertamente tendría un impacto en el rendimiento. Por supuesto, no son lenguajes de programación que están fuertemente tipado y / u ofrecen tipos de números de tamaño arbitrario, pero no creo que hay un verdadero lenguaje de programación de propósito general que fue de esta manera.
Notas al margen:
Es posible que se haya preguntado sobre el
unsigned
modificador que se mencionó en la especificación Oak. De hecho, también contiene una observación: "unsigned
aún no está implementado; puede que nunca lo esté". . Y tenían razón.Además de preguntarse por qué C / C ++ tenía estos diferentes tipos de enteros, es posible que se pregunte por qué los confundieron tan horriblemente que nunca se sabe cuántos bits
int
tiene. Las justificaciones para esto generalmente están relacionadas con el rendimiento y se pueden buscar en otros lugares.fuente
Ciertamente muestra que aún no se le ha enseñado sobre rendimiento y arquitecturas.
Ignorando la importancia del tamaño de los datos siempre afecta el rendimiento, debe utilizar tantos recursos como sea necesario, pero no más, siempre.
Esa es la diferencia entre un programa o sistema que hace cosas realmente simples y es increíblemente ineficiente que requiere muchos recursos y hace que el uso de ese sistema sea realmente costoso; o un sistema que hace mucho, pero funciona más rápido que otros y es realmente barato de ejecutar.
fuente
Hay un par de buenas razones.
(1) mientras que el almacenamiento de un byte variable frente a un largo es insignificante, el almacenamiento de millones en una matriz es muy significativo.
(2) la aritmética "nativa de hardware" basada en tamaños enteros particulares puede ser mucho más eficiente, y para algunos algoritmos en algunas plataformas, eso puede ser importante.
fuente