Saltar al contenido
math-y-wrappers.java · devschool

Math y clases envoltorio en Java

Lección 8 de 26 · 10 min de lectura · Actualizado el

En esta lección
  1. La clase Math
  2. La clase Random
  3. Clases envoltorio
  4. Convertir entre texto y números
  5. Cuidado al comparar Integer con ==
  6. Dinero: double no es exacto
  7. Errores frecuentes
  8. Resumen

Java trae de serie muchas herramientas para trabajar con números: la clase Math para potencias, raíces y redondeos; Random para generar números aleatorios; y las clases envoltorio (Integer, Double…), que convierten textos en números y permiten guardar números donde solo caben objetos. En esta lección las verás todas, junto con una trampa clásica al comparar Integer y la forma correcta de manejar dinero.

La clase Math

Math es una clase con métodos estáticos: se usan escribiendo Math. delante, sin crear ningún objeto. No hace falta ningún import, porque está en java.lang.

System.out.println(Math.abs(-7));      // 7
System.out.println(Math.pow(2, 10));   // 1024.0
System.out.println(Math.sqrt(81));     // 9.0
System.out.println(Math.max(4, 9));    // 9
System.out.println(Math.min(4, 9));    // 4
System.out.println(Math.PI);           // 3.141592653589793
MétodoQué haceDevuelve
Math.abs(x)Valor absoluto (quita el signo)Mismo tipo que x
Math.pow(base, exp)Potenciadouble
Math.sqrt(x)Raíz cuadradadouble
Math.cbrt(x)Raíz cúbicadouble
Math.max(a, b) / Math.min(a, b)Mayor / menor de dosMismo tipo
Math.round(x)Redondeo al entero más cercanolong (con double)
Math.floor(x)Redondeo hacia abajodouble
Math.ceil(x)Redondeo hacia arribadouble
Math.random()Aleatorio entre 0.0 (incluido) y 1.0 (excluido)double

Fíjate en que pow y sqrt siempre devuelven double, aunque el resultado sea exacto. Si lo necesitas entero, haz un casting: int kb = (int) Math.pow(2, 10);.

Un ejemplo práctico: la distancia entre dos puntos del plano (teorema de Pitágoras).

double x1 = 1, y1 = 2, x2 = 4, y2 = 6;
double distancia = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
System.out.println(distancia); // 5.0

Redondear: round, floor y ceil

System.out.println(Math.round(4.5));  // 5
System.out.println(Math.round(4.49)); // 4
System.out.println(Math.floor(4.9));  // 4.0
System.out.println(Math.ceil(4.1));   // 5.0
System.out.println(Math.round(-4.5)); // -4
System.out.println(Math.floor(-4.1)); // -5.0
  • round va al entero más cercano; en el empate (.5) sube hacia el positivo, por eso -4.5 da -4.
  • floor (“suelo”) siempre baja y ceil (“techo”) siempre sube, también con negativos.

ceil es muy útil para calcular cuántos “recipientes” necesitas. Si tienes 23 alumnos y en cada mesa caben 4:

int alumnos = 23;
int porMesa = 4;
int mesas = (int) Math.ceil((double) alumnos / porMesa);
System.out.println(mesas); // 6

Para redondear a dos decimales hay un truco con round: multiplicas por 100, redondeas y divides entre 100.0.

double precio = 19.98765;
double redondeado = Math.round(precio * 100) / 100.0;
System.out.println(redondeado); // 19.99

Si solo quieres mostrarlo con dos decimales, no cambies el número: usa printf("%.2f", precio) (lo tienes en String y StringBuilder).

Números aleatorios con Math.random()

Math.random() devuelve un double entre 0.0 y 1.0 (sin llegar a 1). Para convertirlo en un entero de un rango hay una fórmula que conviene entender, no memorizar:

(int) (Math.random() * cantidadDeValores) + minimo

Para simular un dado (del 1 al 6, que son 6 valores posibles):

int dado = (int) (Math.random() * 6) + 1;
  • Math.random() * 6 da un número entre 0.0 y 5.999…
  • (int) corta los decimales: 0, 1, 2, 3, 4 o 5.
  • + 1 lo desplaza: de 1 a 6.

Para un número entre 10 y 20, ambos incluidos, hay 11 valores: (int) (Math.random() * 11) + 10.

La clase Random

java.util.Random es más cómoda y más completa. Su método nextInt(limite) devuelve un entero entre 0 y limite - 1:

import java.util.Random;

public class Loteria {
    public static void main(String[] args) {
        Random azar = new Random();
        int dado = azar.nextInt(6) + 1;        // 1 a 6
        int porcentaje = azar.nextInt(101);    // 0 a 100
        int entre = azar.nextInt(10, 21);      // 10 a 20 (Java 17+)
        boolean moneda = azar.nextBoolean();
        double decimal = azar.nextDouble();    // 0.0 a 1.0

        System.out.println(dado >= 1 && dado <= 6); // true
    }
}

Desde Java 17, nextInt(origen, limite) acepta directamente el rango (el límite sigue siendo excluido).

Si creas el Random con una semilla, new Random(42), genera siempre la misma secuencia. Parece raro, pero es muy útil para hacer pruebas repetibles.

Clases envoltorio

Cada tipo primitivo tiene una clase envoltorio (wrapper) que “envuelve” el valor dentro de un objeto:

PrimitivoClase envoltorio
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

¿Para qué sirven? Por tres motivos:

  1. Colecciones: un ArrayList solo guarda objetos, así que no puedes escribir ArrayList<int>; se escribe ArrayList<Integer>. Lo verás en ArrayList y listas.
  2. Pueden ser null: un Integer puede representar “sin valor”, algo imposible con un int.
  3. Utilidades: métodos y constantes para convertir, comparar y conocer límites.

Autoboxing y unboxing

Java convierte automáticamente entre el primitivo y su envoltorio:

Integer edad = 25;          // autoboxing: int -> Integer
int copia = edad;           // unboxing: Integer -> int
Integer suma = edad + 5;    // unboxing, suma y autoboxing
System.out.println(suma);   // 30

Es cómodo, pero tiene un peligro: si el Integer es null, el unboxing lanza NullPointerException.

Integer stock = null;
int unidades = stock; // NullPointerException

Convertir entre texto y números

Es una de las operaciones más habituales: todo lo que llega del teclado, de un fichero o de un formulario es texto.

int edad = Integer.parseInt("25");
double precio = Double.parseDouble("19.99");
long grande = Long.parseLong("8100000000");
boolean activo = Boolean.parseBoolean("true");

System.out.println(edad + 1);     // 26
System.out.println(precio * 2);   // 39.98

Y al revés, de número a texto:

String a = String.valueOf(42);
String b = Integer.toString(42);
String c = "" + 42;  // funciona, pero es menos claro
System.out.println(a + b + c); // 424242

Integer.parseInt también puede leer otras bases: Integer.parseInt("FF", 16) devuelve 255.

NumberFormatException

Si el texto no es un número válido, la conversión lanza NumberFormatException:

int n = Integer.parseInt("12a"); // NumberFormatException: For input string: "12a"

Ojo con casos que parecen inofensivos: Integer.parseInt(" 12") (con espacio) y Integer.parseInt("12.5") (con decimales) también fallan. Limpia antes con strip() y usa el método adecuado para cada tipo.

La forma correcta de convertir datos del usuario es capturar la excepción:

String entrada = "doce";
try {
    int unidades = Integer.parseInt(entrada);
    System.out.println("Unidades: " + unidades);
} catch (NumberFormatException e) {
    System.out.println("'" + entrada + "' no es un número"); // 'doce' no es un número
}

try y catch se explican en detalle en excepciones.

Límites: MAX_VALUE y MIN_VALUE

Cada envoltorio numérico tiene constantes con sus límites. Son útiles, por ejemplo, como valor inicial al buscar un mínimo:

int[] temperaturas = {18, 12, 25, 9, 21};
int minima = Integer.MAX_VALUE; // cualquier valor real será menor
for (int t : temperaturas) {
    if (t < minima) {
        minima = t;
    }
}
System.out.println(minima); // 9
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Double.MAX_VALUE);  // 1.7976931348623157E308

Otros métodos útiles: Integer.compare(a, b), Integer.sum(a, b), Integer.toBinaryString(10) ("1010") y Character.getNumericValue('7') (7).

Cuidado al comparar Integer con ==

Esta trampa aparece en entrevistas y en exámenes. Mira este código:

Integer a = 100;
Integer b = 100;
System.out.println(a == b);      // true

Integer c = 1000;
Integer d = 1000;
System.out.println(c == d);      // false
System.out.println(c.equals(d)); // true

¿Por qué el mismo código da true con 100 y false con 1000? Integer es un objeto, y == compara si son el mismo objeto, no si valen lo mismo. Para ahorrar memoria, Java guarda en una caché los Integer entre -128 y 127, y reutiliza el mismo objeto cada vez que haces autoboxing de esos valores. Por eso a y b apuntan al mismo objeto. Con 1000, fuera de la caché, se crean dos objetos distintos.

Regla: compara los envoltorios con equals, igual que los String. O pásalos a primitivo antes: c.intValue() == d.intValue().

Dinero: double no es exacto

Los double se guardan en binario, y muchos decimales no tienen representación exacta, igual que 1/3 no la tiene en decimal (0,333…). El resultado:

System.out.println(0.1 + 0.2);        // 0.30000000000000004
System.out.println(1.10 - 1.00);      // 0.10000000000000009

Para una nota media no importa, pero con dinero esos céntimos fantasma se acumulan. Para importes se usa BigDecimal, del paquete java.math, que trabaja con decimales exactos:

import java.math.BigDecimal;
import java.math.RoundingMode;

public class Factura {
    public static void main(String[] args) {
        BigDecimal precio = new BigDecimal("19.99");
        BigDecimal unidades = new BigDecimal("3");
        BigDecimal iva = new BigDecimal("0.21");

        BigDecimal base = precio.multiply(unidades);
        BigDecimal total = base.add(base.multiply(iva)).setScale(2, RoundingMode.HALF_UP);

        System.out.println(base);  // 59.97
        System.out.println(total); // 72.56
        System.out.println(new BigDecimal("0.1").add(new BigDecimal("0.2"))); // 0.3
    }
}
  • Créalo siempre a partir de un texto (new BigDecimal("19.99")). Si usas new BigDecimal(19.99), arrastras la imprecisión del double.
  • No admite + ni *: se usan los métodos add, subtract, multiply y divide.
  • setScale(2, RoundingMode.HALF_UP) redondea a dos decimales como se hace en las facturas.

Consejo: otra opción sencilla es guardar el dinero en céntimos con un long: 19,99 euros son 1999. Las sumas son exactas y solo divides entre 100 al mostrar.

Errores frecuentes

  • Olvidar que pow y sqrt devuelven double: int r = Math.pow(2, 3); no compila.
  • Rango de aleatorios mal calculado: (int) (Math.random() * 6) da de 0 a 5, no de 1 a 6.
  • Convertir textos sin validar: parseInt lanza NumberFormatException con espacios, decimales o letras.
  • Comparar Integer con ==: funciona entre -128 y 127 y falla fuera. Usa equals.
  • Unboxing de null: int x = integerNulo; lanza NullPointerException.
  • Dinero con double: usa BigDecimal o céntimos en un long.

Resumen

  • Math tiene métodos estáticos: abs, pow, sqrt, max, min, round, floor, ceil y random.
  • Para aleatorios, Random.nextInt(n) da de 0 a n-1; con Math.random() usa (int) (Math.random() * cantidad) + minimo.
  • Cada primitivo tiene su clase envoltorio (Integer, Double…); Java convierte solo con autoboxing y unboxing.
  • Integer.parseInt, Double.parseDouble y String.valueOf convierten entre texto y números; si el texto no es válido, salta NumberFormatException.
  • Compara envoltorios con equals, no con ==.
  • Para dinero, BigDecimal creado desde texto.

Con los datos, los operadores y las utilidades numéricas controlados, el siguiente paso es organizar el código en bloques reutilizables: métodos.

Pon a prueba lo que has aprendido

[Java] ¿Qué imprime este código?
System.out.println(Math.round(2.5) + " " + Math.floor(-2.5) + " " + Math.ceil(2.1));

[Java] ¿Qué expresión genera un número entero aleatorio entre 1 y 10, ambos incluidos?

[Java] ¿Qué imprime este código?
Integer a = 127, b = 127;
Integer c = 128, d = 128;
System.out.println((a == b) + " " + (c == d) + " " + c.equals(d));

[Java] ¿Qué ocurre al ejecutar esta línea?
int precio = Integer.parseInt("12.50");

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.