Operaciones con primitivos (III): casts y conversiones

Esta semana seguimos con las variables primitivas. Si no lo has hecho ya, te recomiendo que, antes de continuar, le eches un vistazo a los dos artículos que publiqué anteriormente: el primero, sobre los tipos primitivos enteros; el segundo, sobre primitivos decimales, char y boolean. En este artículo te voy a explicar cómo realizar operaciones con primitivos, y cómo funciona el cast explícito.

Operaciones con primitivos: conversión sin cast

A la conversión sin cast se la llama conversión primitiva estrecha. He visto que en otros lugares lo llaman así, pero, si te digo la verdad, a mí me chirría.

En cualquier caso, lo que quiere decir es que se toma una expresión constante de tipo int y se guarda en un tipo menor (byte, short o char) sin hacer un cast, es decir, directamente. (Más abajo te explicaré qué es un cast). Recuerda que la expresión constante ha de entrar dentro del rango del tipo al que queremos convertirlo; es decir, para una variable de tipo char, no utilices una expresión constante de tipo int mayor de 65.536. (Para recordar los rangos de los tipos primitivos, échale un vistazo al artículo que publiqué al respecto).

¿Qué significa "expresión constante"?

En Java, una expresión de tipo int es constante cuando cumple al menos una de las dos siguientes condiciones:

  • ¿Recuerdas cuando te dije que todos los números enteros son de tipo int de manera predeterminada? En este sentido, un número entero es también una expresión constante, ya que la cifra 14 no puede tener otro valor más que 14. Es por esta razón por la que la mera asignación de un número entero (que es un int) a una variable de tipo byte, short o char se considera una conversión estrecha.
  • Si una variable es final, es también constante, ya que no se le puede asignar otro valor.

Nota sobre el compilador

Algo que debes tener muy en cuenta durante el examen es el modo en el que el compilador ve tu código.

En primer lugar, deberás asegurarte de que el código que aparezca en la pregunta compile. Es posible que aparezca una opción similar a "La línea X no compila" (en cuyo caso debes prestar atención a dicha línea) pero, si no la hay, no des por sentado que el código va a compilar. El examen, como ya te he dicho en otras ocasiones, va a intentar ponerte trampas.

En este caso que nos ocupa, quiero te quede bien claro que el compilador no sabe qué valor tiene una variable, a no ser que sea una constante. Es por ello que a una variable de tipo char se le puede asignar la cifra 3, pero no se le puede asignar una variable de tipo int ya que el compilador, si esta no es final, no sabe qué valor contiene (incluso aunque se le acabe de asignar el valor. El compilador no lo recuerda).

Ejemplo con dos latas de conservas y Canelo, el perrete

Imagínate que tienes una barra de pan. La barra de pan simboliza la variable de tipo char. Si en char puedes almacenar un valor de 0 a 65.536, en la barra de pan solo puedes introducir comida apta para seres humanos.

Tienes hambre y quieres hacerte un bocata. Abres la estantería y encuentras dos latas de conservas.

Ambas latas simbolizan sendas variables de tipo int. Solo necesitas una para hacerte un bocadillo, pero mientras que una tiene una etiqueta (atún), la otra lata no tiene etiqueta. Puede ser atún, porque ambas latas son iguales, pero también sabes que las latas de comida de tu perrete, Canelo, son también exactamente iguales. Pregunta: ¿qué harías?

Operaciones con primitivos: ejemplo

No te comas la comida de Canelo

La respuesta es que sabes que puedes utilizar la lata etiquetada, pero no la no etiquetada sin saber qué contiene. Y este es el modo en el que funciona el compilador. Si tiene un char y un int sin etiqueta final, por si acaso, detiene la compilación. Nadie quiere un bocata de comida para perros. Excepto Canelo, claro.

El perrete Canelo, las latas sin etiquetas y las operaciones con primitivos en Java: una breve historia 🙂

¡Comparte!

Con todo lo anterior, comprenderás por qué el siguiente ejemplo sí compila:

Operaciones con primitivos: conversiones con cast

Conversión de un valor primitivo (no constante) a un tipo menor

Supongamos que quieres asignar una variable int a una variable char pero no puedes etiquetar la variable int como final. Ya sabes que a una variable final no se le puede asignar un valor distinto, y eso no te viene bien. ¿Qué hacer?

Sigo con el ejemplo anterior: tienes la barra de pan y las dos latas. No te fias de la lata sin etiquetar, pero en ese momento entra tu mujer a la cocina, coge una cerveza del frigorífico y, señalando a las latas, te dice:

—Si te vas a hacer un bocata, compártelo conmigo. Coge la lata sin etiquetar. Es pulpo en aceite de oliva.

Y esto es un cast: todavía no sabes qué contiene la lata (porque no lo has visto tú), pero te aseguran que es comible (supongamos que el pulpo en aceite de oliva, de lata, es comestible).

Operaciones con primitivos: cast

Pulpo fresco con mirada desaprobadora, no como el de tu lata

Un cast se realiza anteponiendo entre paréntesis el tipo al que quieres convertir el valor asignado. Si quieres guardar un int (no constante) en una variable de tipo char, deberás utilizar (char):

Recuerda que, al imprimir un char, se imprime el caracter Unicode que representa.

Conversión de un valor decimal a uno entero

Pero, ¿y si tienes que convertir un número decimal (double) a uno entero (int)?

En ese caso, olvídate de todas las cifras decimales. La conversión no redondea, sino que trunca: un 3,7 queda en 3.

El cast en las operaciones con primitivos enteros

Una característica curiosa de los tipos enteros es que el resultado de ejecutar cualquier operación con ellos es siempre un valor int. Es decir, si multiplicas dos variables de tipo byte, has de realizar un cast para almacenar el resultado en otra variable del mismo tipo.

Aquí tienes el resultado de no realizar un cast.

 No olvides que esto ocurre tanto con byte como con char y short. Realizar operaciones con primitivos enteros da como resultado siempre un int.

La única excepción a este caso son las operaciones realizadas con los operadores compuestos (+=, -=, *=, /=), cuyo resultado es del mismo tipo que el operando original. Si a un byte le sumas una unidad utilizando +=, el resultado sigue siendo un byte. Ten cuidado si le sumas un decimal, porque va a truncar el resultado, y no el valor inicial decimal que utilizas.

¡Espera! ¿Y el tipo long?

En las operaciones con primitivos de tipo long e int tienes que tener en cuenta que, si uno de los operandos es de tipo long, el resultado va a ser también long. Pero, ¿y si tenemos una multiplicación de dos int, y el valor resultante supera el rango de int (unos dos mil millones)?

¿Esperabas un error de compilación? En lugar de ello, el código compila, se ejecuta e imprime un número negativo, en lugar del valor esperado (34.000.000.000).

Ambos operandos son variables de tipo int, de modo que el resultado también lo es. Al ser el resultado mucho mayor de lo que cabe en el rango de un int, ocurre lo que se conoce como desbordamiento.

Imagínate que tienes una variable primitiva a la que le asignas el valor más alto que puede contener. Súmale una unidad. ¿Qué crees que ocurre?

El código imprime, en primer lugar, el valor máximo que puede tomar un int. A continuación, el resultado de sumarle una unidad a dicho valor.

Al desbordarse, el int toma el valor menor posible, y continúa la cuenta a partir de ahí. Así, al ser el resultado de la operación anterior 34 mil millones, el valor de int se ha desbordado varias veces, hasta que el valor que quedaba cabía en un int.

Operaciones con primitivos decimales con cast

¿Y qué pasa con los decimales?

¿Recuerdas que, al igual que los números enteros son siempre int, los decimales son siempre double?

Ocurre lo mismo que con las variables de tipo long. Si todos los operandos son double, el resultado será double y tendrás que realizar un cast para almacenarlo en un float. Recuerda que, en este caso, el rango de double es mayor al rango de float:

Ejercicios recomendados

  • ¿Cuál es el valor predeterminado de una variable primitiva en un objeto? Crea un objeto que contenga variables primitivas de instancia, pero no les asignes ningún valor.
  • Convierte las variables del objeto que acabas de crear a otros tipos primitivos. ¿Qué valor toma el valor predeterminado de la variable con el nuevo tipo?
  • Dadas las siguientes líneas de código, ¿cuál es el resultado? ¿por qué?

¿Tienes alguna duda? Si es así, ¡no dudes en escribir un comentario, o plantéame tu pregunta en las redes sociales!

Deja una respuesta 0 comentarios