Saltar a contenido

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.

1
2
3
4
int entero = 42;
long largo = entero;        // widening: int → long (automático)
double decimal = largo;     // widening: long → double (automático)
double decimal2 = entero;   // widening: int → double (automático)

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: longfloat

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:

1
2
3
4
long grande = 9_876_543_210_987_654L;
float f = grande;                       // widening legal, pero...
System.out.println(f);                  // 9.876543E15 (solo 7 dígitos)
System.out.println((long) f);           // 9_876_543_244_132_352  (¡no es el original!)

Cuidado

El widening longfloat 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:

1
2
3
4
5
6
7
int a = 5;
double b = 2.5;
double r = a + b;        // a se convierte a double (5.0), resultado 7.5 (double)

int x = 5;
long y = 10L;
long r2 = x + y;         // x se convierte a long, resultado 15L (long)

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).

double pi = 3.14159;
int entero = (int) pi;           // casting explícito: double → int
System.out.println(entero);      // 3  (los decimales se truncan)

long grande = 1_000_000_000L;
int pequeno = (int) grande;      // casting explícito: long → int
System.out.println(pequeno);     // 1_000_000_000 (cabe, sin pérdida)

long masGrande = 5_000_000_000L;
int pequeno2 = (int) masGrande;  // casting explícito
System.out.println(pequeno2);    // 705032704  (¡valor absurdo! se ha desbordado)

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

(tipoDestino) expresion

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:

1
2
3
4
5
6
double x = 5.7, y = 2.3;

int r1 = (int) (x + y);     // bien: primero suma (8.0), luego cast → 8
int r2 = (int) x + y;       // distinto: primero cast (5), luego suma (5 + 2.3 = 7.3)
                            // ERROR de compilación: double no se asigna a int
int r3 = (int) x + (int) y; // bien: 5 + 2 = 7

Truncamiento, no redondeo

El cast de double o float a entero trunca los decimales (los descarta hacia cero), no redondea:

1
2
3
System.out.println((int) 3.7);    // 3 (no 4)
System.out.println((int) -3.7);   // -3 (no -4: trunca hacia cero)
System.out.println((int) 3.5);    // 3

Para redondear, usa Math.round, Math.floor o Math.ceil:

Pista sobre Math. La clase Math (del paquete java.lang, no necesita import) 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 como Math.nombreMetodo(...).

1
2
3
4
System.out.println(Math.round(3.5));    // 4 (redondeo matemático)
System.out.println(Math.round(3.4));    // 3
System.out.println((int) Math.floor(3.7));  // 3 (hacia abajo)
System.out.println((int) Math.ceil(3.2));   // 4 (hacia arriba)

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:

1
2
3
4
5
6
7
int grande = 300;
byte b = (byte) grande;        // 300 no cabe en byte (rango -128..127)
System.out.println(b);         // 44  (300 - 256 = 44)

long enorme = 5_000_000_000L;
int i = (int) enorme;          // 5 mil millones no cabe en int
System.out.println(i);         // 705032704 (bits truncados)

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:

1
2
3
4
char letra = 'A';
int codigo = letra;            // widening automático: char → int, valor 65
char mayusA = (char) 65;       // casting explícito: int → char
char siguiente = (char) (letra + 1);   // 'B' (primero suma como int, luego cast)

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
String edadTexto = "23";
int edad = Integer.parseInt(edadTexto);
System.out.println("El año que viene tendrás " + (edad + 1));   // 24

String precioTexto = "19.99";
double precio = Double.parseDouble(precioTexto);
System.out.println("Con IVA: " + (precio * 1.21));   // 24.2879

String activoTexto = "true";
boolean activo = Boolean.parseBoolean(activoTexto);
System.out.println("Usuario activo: " + activo);   // true

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:

// Cast: double → int (funciona, trunca decimales)
double d = 3.14;
int i = (int) d;            // 3

// Parseo: String → int (falla si el texto no es un número válido)
String s = "3.14";
int j = Integer.parseInt(s);  // NumberFormatException

String s2 = "42";
int k = Integer.parseInt(s2); // 42

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):

1
2
3
4
5
6
Boolean.parseBoolean("true");    // true
Boolean.parseBoolean("TRUE");    // true
Boolean.parseBoolean("True");    // true
Boolean.parseBoolean("false");   // false
Boolean.parseBoolean("hola");    // false (no lanza excepción)
Boolean.parseBoolean("1");       // false (no es "true")

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:

1
2
3
4
5
6
7
String.valueOf(42);          // "42"
String.valueOf(3.14);        // "3.14"
String.valueOf(true);        // "true"
String.valueOf('A');         // "A"
String.valueOf(null);        // "null" (no lanza NPE)
Object o = null;
String.valueOf(o);           // "null"

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:

1
2
3
4
5
6
int n = 42;
String s = "" + n;           // "42" (concatenación con cadena vacía)
String s2 = "Número: " + n;  // "Número: 42"

Object o = null;
String s3 = "" + o;          // "null"

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.

1
2
3
String entrada = "abc";
int n = Integer.parseInt(entrada);   // NumberFormatException: el programa muere aquí
System.out.println("Esto no se imprime");

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:

import java.util.Scanner;

Scanner sc = new Scanner(System.in);
System.out.print("¿Cuántos años tienes? ");
String linea = sc.nextLine();

try {
    int edad = Integer.parseInt(linea);
    System.out.println("El año que viene tendrás " + (edad + 1));
} catch (NumberFormatException e) {
    System.out.println("Eso no es un número válido. Escribe solo dígitos.");
}
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:

public static int parseIntSeguro(String texto, int valorPorDefecto) {
    try {
        return Integer.parseInt(texto);
    } catch (NumberFormatException e) {
        return valorPorDefecto;
    }
}

// Uso
int edad = parseIntSeguro(linea, -1);
if (edad == -1) {
    System.out.println("No te he entendido la edad.");
}

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 ThrowableExceptionRuntimeException. 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

  1. Deja que Java haga el widening automáticamente cuando sea seguro: long l = entero; es correcto y limpio.
  2. Marca el narrowing con cast explícito: si asignas double a int, escribe (int) x. Es tu confirmación de que aceptas la posible pérdida.
  3. Comprueba el rango antes de cast entre enteros: usa Math.toIntExact(largo) si quieres que lance excepción al desbordar.
  4. Usa Math.round, floor, ceil si necesitas redondear, no truncar.
  5. Para parseo de entrada externa, usa try-catch: nunca confíes en que el texto sea un número válido.
  6. Para conversión a texto, String.valueOf o concatenación con +: son null-safe y legibles.
  7. Cuidado con Boolean.parseBoolean: solo "true" (case-insensitive) da true; todo lo demás da false, sin excepción.
  8. 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é:

1
2
3
4
5
int i = 5;
long l = i;          // ¿compila?
double d = l;          // ¿compila?
float f = l;           // ¿compila?
byte b = i;            // ¿compila?

Ejercicio 8.2 (casting). Predice el resultado de cada println sin ejecutarlo y comprueba:

1
2
3
4
5
System.out.println((int) 3.7);          // ?
System.out.println((int) -3.7);         // ?
System.out.println((int) 3.5);          // ?
System.out.println((int) Math.round(3.5));  // ?
System.out.println((int) (5 / 2.0));     // ?

Ejercicio 8.3 (desbordamiento). Predice el resultado sin ejecutarlo y comprueba:

int x = (int) 5_000_000_000L;
System.out.println(x);

Ejercicio 8.4 (Boolean.parseBoolean). Predice el resultado de cada parseBoolean y comprueba:

1
2
3
4
5
System.out.println(Boolean.parseBoolean("true"));    // ?
System.out.println(Boolean.parseBoolean("TRUE"));    // ?
System.out.println(Boolean.parseBoolean("1"));        // ?
System.out.println(Boolean.parseBoolean("false"));    // ?
System.out.println(Boolean.parseBoolean("hola"));      // ?

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:

  1. La conversión implícita (widening) ocurre cuando el tipo destino es más grande que el origen (byteshortintlongfloatdouble). Es automática y sin pérdida, salvo la sutil pérdida de precisión en longfloat.
  2. 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.
  3. 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 de null lanza NPE.
  4. El parseo convierte String a primitivo con métodos Integer.parseInt, Double.parseDouble, etc. Si la cadena no es válida, lanza NumberFormatException.
  5. La conversión a texto se hace con String.valueOf(x) (null-safe), concatenación con + (null-safe), o x.toString() (¡no null-safe!).
  6. Boolean.parseBoolean es permisivo: solo "true" (case-insensitive) da true; todo lo demás da false, sin excepción.
  7. Para parseo de entrada externa, usa try-catch y captura NumberFormatException para evitar que el programa caiga. Lo verás en detalle en UD3.