¿Por qué la API de Java usa int en lugar de short o byte?

¿Por qué la API de Java usa int , cuando el byte short o incluso sería suficiente?

Ejemplo: el campo DAY_OF_WEEK en la clase Calendar usa int .

Si la diferencia es muy mínima, ¿por qué existen esos tipos de datos ( short , int ) en absoluto?

Algunas de las razones ya han sido señaladas. Por ejemplo, el hecho de que “… (Casi) Todas las operaciones en byte, short promocionarán estas primitivas a int” . Sin embargo, la siguiente pregunta obvia sería: ¿POR QUÉ se promueven estos tipos a int ?

Entonces, para ir un nivel más profundo: la respuesta puede estar simplemente relacionada con el conjunto de instrucciones de la máquina virtual de Java. Como se resume en la Tabla en la Especificación de máquina virtual de Java , todas las operaciones aritméticas integrales, como agregar, dividir y otras, solo están disponibles para el tipo int y el tipo long , y no para los tipos más pequeños.

(Un lado: los tipos más pequeños ( byte y short ) son básicamente solo para matrices . Una matriz como new byte[1000] tendrá 1000 bytes, y una matriz como new int[1000] tomará 4000 bytes)

Ahora, por supuesto, uno podría decir que “… la próxima pregunta obvia sería: ¿POR QUÉ estas instrucciones solo se ofrecen para int (y long )?” .

Una razón se menciona en la especificación de JVM mencionada anteriormente:

Si cada instrucción tipada admitiera todos los tipos de datos en tiempo de ejecución de la Máquina virtual de Java, habría más instrucciones de las que podrían representarse en un byte.

Además, Java Virtual Machine se puede considerar como una abstracción de un procesador real. Y la introducción de la unidad de lógica aritmética dedicada para los tipos más pequeños no valdría la pena: necesitaría transistores adicionales, pero aún así solo podría ejecutar una adición en un ciclo de reloj. La architecture dominante cuando se diseñó la JVM fue de 32bits, justo para una int 32 bits. (Las operaciones que implican un valor de 64 bits de long se implementan como un caso especial).

(Nota: El último párrafo es un poco simplificado, teniendo en cuenta la posible vectorización, etc., pero debería dar la idea básica sin sumergirse demasiado en los temas de diseño del procesador)


EDITAR: Un apéndice corto, que se centra en el ejemplo de la pregunta, pero en un sentido más general: también se podría preguntar si no sería beneficioso almacenar campos usando los tipos más pequeños. Por ejemplo, uno podría pensar que la memoria se podría guardar almacenando Calendar.DAY_OF_WEEK como un byte . Pero aquí, entra en juego el Java Class File Format: todos los Fields en un Class File ocupan al menos un “slot”, que tiene el tamaño de un int (32 bits). (Los campos “anchos”, double y long , ocupan dos ranuras). Entonces, declarar explícitamente un campo como short o byte tampoco salvaría ninguna memoria.

(Casi) Todas las operaciones en byte , short promocionarán a int , por ejemplo, no puedes escribir:

 short x = 1; short y = 2; short z = x + y; //error 

Los aritméticos son más fáciles y sencillos cuando se usa int , sin necesidad de lanzar.

En términos de espacio, hace una pequeña diferencia. byte y el short complicarían las cosas, no creo que valga la pena esta optimización de micro, ya que estamos hablando de una cantidad fija de variables.

byte es relevante y útil cuando progtwig dispositivos integrados o trata con archivos / redes. Además, estas primitivas son limitadas, ¿y si los cálculos pudieran exceder sus límites en el futuro? Intenta pensar en una extensión para la clase Calendar que pueda desarrollar números más grandes.

También tenga en cuenta que en un procesador de 64 bits, los locales se guardarán en registros y no usarán ningún recurso, por lo que usar int , short y otras primitivas no hará ninguna diferencia. Además, muchas implementaciones Java alinean variables * (y objetos).


* byte y short ocupan el mismo espacio que int si son variables locales , variables de clase o incluso variables de instancia . ¿Por qué? Porque en (la mayoría) de los sistemas informáticos, las direcciones de las variables están alineadas , por lo que, por ejemplo, si utiliza un solo byte, en realidad terminará con dos bytes, uno para la variable en sí y otro para el relleno.

Por otro lado, en matrices, byte toma 1 byte, toma short 2 bytes e int toma 4 bytes, porque en las matrices solo debe alinearse el inicio y tal vez el final. Esto marcará la diferencia en caso de que desee utilizar, por ejemplo, System.arraycopy() , entonces realmente System.arraycopy() una diferencia de rendimiento.

Porque las operaciones aritméticas son más fáciles cuando se usan enteros en comparación con los cortos. Supongamos que las constantes efectivamente fueron modeladas por valores short . Entonces deberías usar la API de esta manera:

 short month = Calendar.JUNE; month = month + (short) 1; // is july 

Observe el lanzamiento explícito. Los valores cortos se promueven implícitamente a valores int cuando se usan en operaciones aritméticas. (En la stack de operandos, los cortos incluso se expresan como enteros). Esto sería bastante engorroso, razón por la cual a menudo se prefieren los valores int para las constantes.

Comparado con eso, la ganancia en eficiencia de almacenamiento es mínima porque solo existe un número fijo de dichas constantes. Estamos hablando de 40 constantes. Cambiar su almacenamiento de int a short le garantizaría 40 * 16 bit = 80 byte . Vea esta respuesta para mayor referencia.

Si utilizó la filosofía de que las constantes integrales se almacenan en el tipo más pequeño en el que quepan, entonces Java tendría un problema grave: siempre que los progtwigdores escriban código con constantes integrales, deben prestar especial atención a su código para verificar si el tipo de las constantes son importantes, y si es así, busque el tipo en la documentación o haga el tipo de conversión que sea necesario.

Entonces, ahora que hemos delineado un problema serio, ¿qué beneficios podría esperar lograr con esa filosofía? No me sorprendería si el único efecto observable en el tiempo de ejecución de ese cambio fuera el tipo que obtienes cuando miras la constante a través del reflection. (y, por supuesto, cualesquiera errores que sean introducidos por progtwigdores perezosos / involuntarios que no respondan correctamente de los tipos de las constantes)

Sopesar los pros y los contras es muy fácil: es una mala filosofía.

La complejidad del diseño de una máquina virtual es una función de cuántos tipos de operaciones puede realizar. Es más fácil tener cuatro implementaciones de una instrucción como “multiplicar” – una para cada entero de 32 bits, de 64 bits, de coma flotante de 32 bits y de coma flotante de 64 bits – que tener, además de a lo anterior, versiones para los tipos numéricos más pequeños también. Una pregunta de diseño más interesante es por qué debería haber cuatro tipos, en lugar de menos (realizar todos los cálculos enteros con enteros de 64 bits y / o hacer todos los cálculos de coma flotante con valores de coma flotante de 64 bits). La razón para usar enteros de 32 bits es que se esperaba que Java se ejecutara en muchas plataformas donde los tipos de 32 bits podrían actuar con la misma rapidez que los tipos de 16 bits u 8 bits, pero las operaciones en tipos de 64 bits serían notablemente más lento. Incluso en plataformas donde sería más rápido trabajar con los tipos de 16 bits, el costo adicional de trabajar con cantidades de 32 bits se vería compensado por la simplicidad que ofrece tener solo tipos de 32 bits.

En cuanto a la realización de cálculos de coma flotante en valores de 32 bits, las ventajas son un poco menos claras. Hay algunas plataformas donde un cálculo como float a=b+c+d; podría realizarse más rápidamente convirtiendo todos los operandos a un tipo de mayor precisión, agregándolos, y luego convirtiendo el resultado a un número de coma flotante de 32 bits para su almacenamiento. Existen otras plataformas en las que sería más eficiente realizar todos los cálculos utilizando valores de coma flotante de 32 bits. Los creadores de Java decidieron que todas las plataformas deberían hacer las cosas de la misma manera, y que deberían favorecer a las plataformas de hardware para las cuales los cálculos de coma flotante de 32 bits son más rápidos que los más largos, aunque esta PC severamente degradada tanto la velocidad y precisión de matemática de coma flotante en una PC típica, así como en muchas máquinas sin unidades de coma flotante. Nótese, por cierto, que dependiendo de los valores de b, c y d, se utilizan cálculos intermedios de mayor precisión cuando se calculan expresiones como el float a=b+c+d; antes mencionado float a=b+c+d; a veces dará resultados que son significativamente más precisos de lo que se lograría de todos los operandos intermedios se computaron en precisión de float , pero a veces arrojarán un valor que es un poco menos preciso. En cualquier caso, Sun decidió que todo debería hacerse de la misma manera y optó por usar valores de float precisión mínima.

Tenga en cuenta que las principales ventajas de los tipos de datos más pequeños se hacen aparentes cuando se almacenan grandes cantidades de ellos en una matriz; incluso si no existiera la ventaja de tener variables individuales de tipos menores de 64 bits, vale la pena tener matrices que puedan almacenar valores más pequeños de forma más compacta; tener una variable local ser un byte lugar de un long guarda siete bytes; tener una matriz de 1,000,000 de números contiene cada número como un byte lugar de una onda long 7,000,000 de bytes. Dado que cada tipo de matriz solo necesita admitir algunas operaciones (más notablemente, leer un elemento, almacenar un elemento, copiar un rango de elementos dentro de una matriz o copiar un rango de elementos de una matriz a otra), la complejidad adicional de tener más los tipos de matriz no son tan severos como la complejidad de tener más tipos de valores numéricos discretos directamente utilizables.

En realidad, habría una pequeña ventaja. Si tienes un

 class MyTimeAndDayOfWeek { byte dayOfWeek; byte hour; byte minute; byte second; } 

luego, en una JVM típica necesita tanto espacio como una clase que contiene una única int . El consumo de memoria se redondea a un siguiente múltiplo de 8 o 16 bytes (IIRC, eso es configurable), por lo que los casos en los que hay un ahorro real son bastante raros.

Esta clase sería un poco más fácil de usar si los métodos de Calendar correspondientes devolvieran un byte . Pero no existen tales métodos de Calendar , solo get(int) que debe devolver un int debido a otros campos. Cada operación en tipos más pequeños promueve a int , por lo que necesita mucha conversión.

Lo más probable es que te rindas y cambies a un int o escriba setters como

 void setDayOfWeek(int dayOfWeek) { this.dayOfWeek = checkedCastToByte(dayOfWeek); } 

Entonces, el tipo de DAY_OF_WEEK no importa, de todos modos.