8. Conversión de tipos
UD1 — Fundamentos de Programación
IES Thiar · Curso 2026 - 2027
| Aspecto | Valor |
|---|---|
| Resultado de aprendizaje | RA1 — Reconoce la estructura de un programa informático, identificando y relacionando los elementos propios del lenguaje de programación utilizado. |
| Criterios de evaluación cubiertos | CE 1.8 — Se ha comprobado el funcionamiento de las conversiones de tipos explícitas e implícitas. |
| Marco normativo | RD 405/2023 · RD 450/2010 · RD 659/2023 |
| Tecnología base | Java 25 LTS — widening, casting, wrappers, autoboxing (Java 5+), pattern matching (Java 16+). |
Introducción de la sección
En la Sección 5 viste cómo los operadores combinan valores para producir resultados. Pero en cuanto mezclas tipos distintos en una expresión (int con double, int con String, String con int), aparece una pregunta inevitable: ¿qué tipo tiene el resultado, y cómo se transforma un valor de un tipo a otro? Ese es el tema de esta sección: la conversión de tipos (type conversion o type casting). Java es un lenguaje fuertemente tipado, así que cada transformación entre tipos sigue reglas estrictas, y es crucial conocerlas para evitar los dos errores opuestos que más sufren los principiantes: perder precisión sin darte cuenta (al hacer un narrowing implícito) y escribir código que no compila porque el compilador se niega a hacer la conversión automáticamente.
El objetivo de esta sección es que entiendas la ampliación implícita (widening, cuando el tipo destino es más grande que el origen y no hay pérdida), el casting explícito (narrowing, cuando el tipo destino es más pequeño y puede haber pérdida), el parseo de cadenas para convertir texto en número (Integer.parseInt), la conversión a texto con String.valueOf, y por qué el parseo puede fallar y cómo protegerlo con try-catch (un adelanto de UD3). Cuando acabes, sabrás mezclar tipos con seguridad y reconocer las situaciones en que el compilador te avisa de una posible pérdida de datos.
8.1 Conversión implícita (widening): cuando el destino es más grande
Qué es y cuándo ocurre
La conversión implícita o widening (ampliación) ocurre cuando asignas un valor de un tipo más pequeño a una variable de un tipo más grande. Como el tipo destino puede representar todos los valores del tipo origen sin pérdida, Java la hace automáticamente, sin que tú escribas nada especial.
Analogía
Imagina que el tipo int es una jarra de 1 litro y double es un cubo de 10 litros. Verter agua de la jarra al cubo es trivial: cabe entera, no se derrama nada, no necesitas ningún utensilio especial. Eso es el widening: el tipo destino es lo suficientemente grande para alojar cualquier valor del tipo origen sin pérdida.
Cadena de ampliaciones
Java define una cadena de ampliaciones entre tipos primitivos numéricos. La conversión es automática siempre que el tipo destino esté a la derecha del origen en esta cadena:
flowchart LR
BYTE["byte<br/>8 bits"] --> SHORT["short<br/>16 bits"]
SHORT --> INT["int<br/>32 bits"]
CHAR["char<br/>16 bits"] --> INT
INT --> LONG["long<br/>64 bits"]
LONG --> FLOAT["float<br/>32 bits"]
FLOAT --> DOUBLE["double<br/>64 bits"]
style BYTE fill:#e8f5e9,stroke:#2e7d32,color:#000
style SHORT fill:#e8f5e9,stroke:#2e7d32,color:#000
style INT fill:#e3f2fd,stroke:#1565c0,color:#000
style CHAR fill:#e0f7fa,stroke:#00838f,color:#000
style LONG fill:#fff3e0,stroke:#e65100,color:#000
style FLOAT fill:#fff3e0,stroke:#e65100,color:#000
style DOUBLE fill:#ffebee,stroke:#c62828,color:#000
Diagrama 8.1 — Cadena de ampliación (widening) entre tipos primitivos en Java. La conversión es automática en el sentido de las flechas.
Tabla de ampliaciones permitidas
| Desde | Hacia (automático) |
|---|---|
byte |
short, int, long, float, double |
short |
int, long, float, double |
char |
int, long, float, double |
int |
long, float, double |
long |
float, double |
float |
double |
Una trampa sutil: long → float
Fíjate en que long (64 bits) se amplía a float (32 bits). Aparentemente es un "estrechamiento" (de 64 a 32 bits), pero Java lo permite como widening porque el rango de float es mucho mayor que el de long. La trampa es que float tiene menos precisión que long (7 dígitos significativos frente a 19), así que para valores grandes de long puede haber pérdida de precisión al convertirlos a float:
Cuidado
El widening long → float es una de las trampas más sutiles de Java. Para valores grandes, pierdes precisión sin que el compilador te avise. Si trabajas con identificadores grandes (IDs, timestamps), usa double o mantén el valor como long.
Widening en operaciones aritméticas
Cuando mezclas tipos en una operación, Java aplica widening al tipo más pequeño antes de calcular. El resultado es del tipo más amplio:
Esto explica por qué 5 / 2.0 da 2.5: el 5 se amplía a 5.0 antes de la división, así que la operación es entre double y el resultado es double.
8.2 Casting explícito (narrowing): cuando el destino es más pequeño
Qué es y cuándo se necesita
El casting explícito o narrowing (estrechamiento) ocurre cuando asignas un valor de un tipo más grande a una variable de un tipo más pequeño. Como el tipo destino no puede representar todos los valores del tipo origen, Java no lo hace automáticamente: tienes que pedirlo tú explícitamente con el operador de cast (tipo).
Analogía
Si widening era verter agua de una jarra pequeña a un cubo grande, narrowing es verter agua de un cubo grande a una jarra pequeña. A veces cabe todo (si el cubo estaba medio vacío), pero a veces se desborda y pierdes agua. Por eso Java te obliga a confirmar: "sé lo que haces, pero asegúrate de que el cubo no está demasiado lleno". El operador de cast (int) es esa confirmación escrita.
Sintaxis del cast
El cast es un operador unario con alta precedencia (nivel 2 en la tabla de la Sección 5), así que conviene usar paréntesis para evitar sorpresas:
Truncamiento, no redondeo
El cast de double o float a entero trunca los decimales (los descarta hacia cero), no redondea:
Para redondear, usa Math.round, Math.floor o Math.ceil:
Pista sobre
Math. La claseMath(del paquetejava.lang, no necesitaimport) agrupa funciones matemáticas comunes: constantes (Math.PI,Math.E), funciones trigonométricas (Math.sin,Math.cos), redondeo (Math.round,Math.floor,Math.ceil), valor absoluto (Math.abs), potencias y raíces (Math.pow,Math.sqrt), y detección de desbordamiento (Math.addExact,Math.toIntExact). La verás en detalle en UD2; por ahora basta con saber que existe y que sus métodos son estáticos, por lo que se llaman comoMath.nombreMetodo(...).
Conversión entre enteros: cuidado con el desbordamiento
Cuando haces cast entre tipos enteros y el valor no cabe, Java trunca los bits que sobran, lo que produce un valor aparentemente absurdo:
Cuidado
El cast entre enteros no lanza excepción aunque el valor no quepa: simplemente trunca. Es tu responsabilidad como programador comprobar que el valor está en rango antes de hacer el cast. Para enteros, puedes usar Math.toIntExact(largo) (Java 8+) que lanza ArithmeticException si el long no cabe en int.
Cast de char a entero
Como char es internamente un entero sin signo de 16 bits, puedes hacer cast entre char y tipos enteros:
Esto es útil para manipular caracteres a bajo nivel (cifrado César, generación de secuencias, etc.).
8.3 Parseo de cadenas: de texto a número
Qué es el parseo
El parseo es la conversión de un String a un tipo primitivo (o wrapper). Se hace con métodos estáticos parseXxx definidos en cada clase wrapper:
| Método | Devuelve | Ejemplo |
|---|---|---|
Integer.parseInt("42") |
int |
42 |
Long.parseLong("1000000") |
long |
1000000 |
Double.parseDouble("3.14") |
double |
3.14 |
Float.parseFloat("3.14f") |
float |
3.14 |
Boolean.parseBoolean("true") |
boolean |
true |
Byte.parseByte("100") |
byte |
100 |
Short.parseShort("1000") |
short |
1000 |
La gran diferencia con el cast
El cast (int) x convierte entre tipos numéricos (de double a int, por ejemplo). El parseo Integer.parseInt(s) convierte de texto a número. Son cosas distintas:
Cuidado
Integer.parseInt("3.14") lanza NumberFormatException porque "3.14" no es un entero válido. Para parsear un decimal, usa Double.parseDouble("3.14") y luego, si lo necesitas como entero, haz cast: (int) Double.parseDouble("3.14").
Boolean.parseBoolean es permisivo
A diferencia de los numéricos, Boolean.parseBoolean no lanza excepción para entradas no válidas: devuelve false para cualquier cosa que no sea "true" (ignorando mayúsculas):
Cuidado
Esta permisividad es una trampa: si esperas que "1" se interprete como true (como en C o JavaScript), te equivocas. En Java, solo la cadena "true" (case-insensitive) da true; todo lo demás da false.
8.4 Conversión a texto: String.valueOf y compañía
La operación inversa al parseo es convertir cualquier valor a String. Java ofrece varias formas, todas válidas pero con matices.
String.valueOf(...)
Método estático sobrecargado para todos los tipos primitivos y Object:
String.valueOf es null-safe: si le pasas null, devuelve la cadena "null" en lugar de lanzar excepción. Esto lo hace seguro cuando el valor puede ser nulo.
Concatenación con +
La forma más común de convertir algo a String es concatenarlo con una cadena vacía o con otra cadena:
Internamente, el compilador traduce "" + n a String.valueOf(n), así que el rendimiento es equivalente. Es la forma más concisa y se usa mucho en código real.
8.5 Parseo seguro con try-catch (adelanto de UD3)
El problema del NumberFormatException
Integer.parseInt("hola") no devuelve 0 ni null: lanza una excepción llamada NumberFormatException que, si no la capturas, detiene el programa. Como casi siempre lees datos de fuentes externas (entrada de usuario, fichero, base de datos, red), el texto puede no ser un número válido y tu programa debe reaccionar con elegancia, no caerse.
La solución: try-catch
Java ofrece la construcción try-catch para capturar excepciones y reaccionar. La verás en detalle en UD3 (Control de Flujo y Depuración); aquí te presento el patrón defensivo más usado para parseo:
flowchart TD
A["Leer línea del usuario"] --> B["try: Integer.parseInt(linea)"]
B --> C{"¿Es un número<br/>válido?"}
C -->|"Sí"| D["Asignar a edad<br/>y continuar"]
C -->|"No"| E["Se lanza<br/>NumberFormatException"]
E --> F["catch NumberFormatException"]
F --> G["Mostrar mensaje<br/>de error amigable"]
G --> H["El programa sigue<br/>ejecutándose"]
style A fill:#e3f2fd,stroke:#1565c0,color:#000
style B fill:#e8f5e9,stroke:#2e7d32,color:#000
style C fill:#fff8e1,stroke:#f57f17,color:#000
style D fill:#e8f5e9,stroke:#2e7d32,color:#000
style E fill:#ffebee,stroke:#c62828,color:#000
style F fill:#fff3e0,stroke:#e65100,color:#000
style G fill:#fff3e0,stroke:#e65100,color:#000
style H fill:#e8f5e9,stroke:#2e7d32,color:#000
Diagrama 8.2 — Patrón defensivo con try-catch para parseo seguro de entrada de usuario: si el texto no es un número válido, el programa no cae, muestra un mensaje y continúa.
Patrón de método parseador seguro
Una buena práctica es encapsular el parseo en un método que devuelva un valor por defecto o un Optional:
Anticipación de UD3
En UD3 verás try-catch con detalle: bloques try, múltiples catch, finally, excepciones comprobadas vs. no comprobadas, lanzar tus propias excepciones con throw, y la jerarquía Throwable → Exception → RuntimeException. Por ahora basta con que sepas el patrón: envuelve el parseo en try-catch cuando el texto viene de una fuente que no controlas.
8.6 Errores comunes y buenas prácticas
Los siete errores más frecuentes
| Error | Síntoma | Causa | Solución |
|---|---|---|---|
| Cast implícito que no compila | error: incompatible types: possible lossy conversion |
Asignar double a int sin cast |
Añade cast: (int) x |
| Pérdida de precisión al cast | Valor truncado sin aviso | (int) 3.7 da 3, no 4 |
Usa Math.round si quieres redondear |
| Desbordamiento al cast entre enteros | Valor absurdo tras cast | (int) 5_000_000_000L |
Comprueba rango o usa Math.toIntExact |
parseInt con decimales |
NumberFormatException |
Integer.parseInt("3.14") |
Usa Double.parseDouble o limpia la cadena |
| Cast sin paréntesis | Resultado inesperado | (int) a + b ≠ (int) (a + b) |
Pon paréntesis explícitos |
Buenas prácticas resumidas
- Deja que Java haga el widening automáticamente cuando sea seguro:
long l = entero;es correcto y limpio. - Marca el narrowing con cast explícito: si asignas
doubleaint, escribe(int) x. Es tu confirmación de que aceptas la posible pérdida. - Comprueba el rango antes de cast entre enteros: usa
Math.toIntExact(largo)si quieres que lance excepción al desbordar. - Usa
Math.round,floor,ceilsi necesitas redondear, no truncar. - Para parseo de entrada externa, usa
try-catch: nunca confíes en que el texto sea un número válido. - Para conversión a texto,
String.valueOfo concatenación con+: son null-safe y legibles. - Cuidado con
Boolean.parseBoolean: solo"true"(case-insensitive) datrue; todo lo demás dafalse, sin excepción. - Agrupa conversiones en métodos de utilidad si se repiten:
parseEnteroSeguro,aMoneda, etc. Mejora la legibilidad y el mantenimiento.
Ejercicios propuestos
Ejercicio 8.1 (widening). Predice si cada asignación compila y por qué:
Ejercicio 8.2 (casting). Predice el resultado de cada println sin ejecutarlo y comprueba:
Ejercicio 8.3 (desbordamiento). Predice el resultado sin ejecutarlo y comprueba:
Ejercicio 8.4 (Boolean.parseBoolean). Predice el resultado de cada parseBoolean y comprueba:
Ejercicio 8.5 (conversión a texto). Escribe dos formas distintas de convertir el entero 42 a String y explica cuál es null-safe.
Resumen de la sección 8
Antes de pasar a la siguiente sección, asegúrate de que controlas estos puntos:
- La conversión implícita (widening) ocurre cuando el tipo destino es más grande que el origen (
byte→short→int→long→float→double). Es automática y sin pérdida, salvo la sutil pérdida de precisión enlong→float. - El casting explícito (narrowing) se necesita cuando el destino es más pequeño. Usa
(tipo) expresion. Trunca decimales y bits sin aviso, así que comprueba el rango. - Los wrappers (
Integer,Double,Boolean...) envuelven primitivos como objetos. El autoboxing/unboxing (Java 5+) convierte automáticamente, pero cuidado:==en wrappers puede dar sorpresas y el unboxing denulllanza NPE. - El parseo convierte
Stringa primitivo con métodosInteger.parseInt,Double.parseDouble, etc. Si la cadena no es válida, lanzaNumberFormatException. - La conversión a texto se hace con
String.valueOf(x)(null-safe), concatenación con+(null-safe), ox.toString()(¡no null-safe!). Boolean.parseBooleanes permisivo: solo"true"(case-insensitive) datrue; todo lo demás dafalse, sin excepción.- Para parseo de entrada externa, usa
try-catchy capturaNumberFormatExceptionpara evitar que el programa caiga. Lo verás en detalle en UD3.