Saltar a contenido

UD2 · Sección 8 — Wrappers y la clase Objects

Dato Valor
Unidad Didáctica UD2 — Programación Orientada a Objetos con la API de Java
Resultado de Aprendizaje RA2 — Escribe y prueba programas sencillos, reconociendo y aplicando los fundamentos de la programación orientada a objetos.
Sección 8 de 10 — Wrappers y la clase Objects
Evaluación 1.ª Evaluación
Duración estimada 2 horas lectivas
Criterios de evaluación cubiertos CE 2.2 (escribe programas simples), CE 2.3 (instancia objetos a partir de clases predefinidas), CE 2.4 (utiliza métodos y propiedades), CE 2.5 (llamadas a métodos estáticos), CE 2.6 (utiliza parámetros), CE 2.7 (incorpora y utiliza librerías de objetos)
Prerrequisitos UD1 completa, UD2 Secciones 1 a 7
Paquete del código de ejemplo dam.programacion.ud2
Herramientas JDK 25, IntelliJ IDEA Community, Maven 3.9.x

1. Punto de partida

Hasta ahora has trabajado con dos categorías de tipos en Java: los primitivos (int, double, boolean, char y sus variantes), que son eficientes pero no son objetos, y las clases (String, Random, LocalDate), que son objetos pero no son primitivos. En esta sección vas a conocer el puente entre ambos mundos: las clases wrapper (envoltorio), que permiten tratar los tipos primitivos como si fueran objetos cuando hace falta.

También vas a conocer la clase Objects, una caja de herramientas con métodos estáticos para trabajar con objetos de forma segura: comprobar si son null, compararlos, calcular su hash, etc. Es una clase que te acompañará durante toda tu carrera, especialmente cuando empieces a crear tus propias clases en UD4.

Es una sección corta pero conceptualmente importante: cierra el bloque de "clases predefinidas del API de Java" y prepara el terreno para los temas de Maven (Sección 9) y los ejercicios (Sección 10).


2. Wrappers: tipos primitivos como objetos

2.1. Qué son y por qué existen

Cada tipo primitivo de Java tiene una clase wrapper asociada que lo "envuelve" en un objeto:

Primitivo Wrapper Paquete
byte Byte java.lang
short Short java.lang
int Integer java.lang
long Long java.lang
float Float java.lang
double Double java.lang
boolean Boolean java.lang
char Character java.lang

Observa que Integer no se llama Int y Character no se llama Char: son las dos excepciones al patrón "capitalizar el nombre del primitivo".

¿Por qué existen? Porque Java es un lenguaje que distingue claramente primitivos de objetos. Hay situaciones donde solo se pueden usar objetos:

  • Colecciones (las verás en UD6): ArrayList<int> no compila, hay que usar ArrayList<Integer>.
  • Genéricos (UD6/UD7): los tipos genéricos solo admiten objetos.
  • Reflection y APIs avanzadas que trabajan con Object.
  • Atributos nullable: un int no puede ser null; un Integer sí. Útil para representar "valor ausente" (por ejemplo, un campo opcional de un formulario).
  • Constantes y utilidades: Integer.MAX_VALUE, Integer.parseInt(String), Double.isNaN(double)...

Analogía del sobre. Imagina que un primitivo es una moneda suelta y un wrapper es la misma moneda dentro de un sobre. La moneda es más fácil de manejar y contar sin sobre, pero hay sitios (por ejemplo, un buzón de correo) donde solo aceptan sobres, no monedas sueltas. El wrapper es el sobre que te permite meter la moneda en sitios que solo aceptan objetos.

2.2. Crear wrappers

Hay tres formas de crear un wrapper (las veremos con Integer, pero aplica a todos):

1
2
3
4
5
6
7
8
// 1. Constructor (DESPROBADO desde Java 9, pero compila)
Integer i1 = new Integer(42);   // evitar: siempre crea objeto nuevo

// 2. Metodo estatico valueOf (RECOMENDADO)
Integer i2 = Integer.valueOf(42);   // aprovecha cache para valores frecuentes

// 3. Autoboxing (Java 5+)
Integer i3 = 42;   // el compilador lo traduce a Integer.valueOf(42)

Recomendación: usa valueOf o autoboxing; nunca new Integer(...). Desde Java 9 el constructor está marcado como deprecated (obsoleto) y puede eliminarse en versiones futuras.

2.3. Autoboxing y unboxing

Java convierte automáticamente entre primitivos y wrappers cuando hace falta:

  • Autoboxing: primitivo → wrapper (lo hace el compilador).
  • Unboxing: wrapper → primitivo.
// Autoboxing
Integer a = 5;          // int 5 -> Integer.valueOf(5)
Integer b = 10;         // int 10 -> Integer.valueOf(10)

// Unboxing
int suma = a + b;       // Integer a -> int 5; Integer b -> int 10; luego suma

// En colecciones (lo veras en UD6)
// List<Integer> numeros = new ArrayList<>();
// numeros.add(7);      // autoboxing: int 7 -> Integer
// int primero = numeros.get(0);   // unboxing: Integer -> int

El compilador inserta las llamadas a valueOf y a xxxValue() automáticamente. Es muy cómodo, pero conviene saber que está pasando por debajo.

2.4. Riesgos del autoboxing

Riesgo 1: NullPointerException al desempaquetar null

Integer n = null;
int valor = n;   // NPE en tiempo de ejecucion

El compilador traduce int valor = n; como int valor = n.intValue();, y como n es null, intentar llamar a un método sobre null lanza NullPointerException. Es una trampa clásica.

Riesgo 2: confusión con ==

1
2
3
4
5
6
7
8
Integer a = 127;
Integer b = 127;
System.out.println(a == b);   // true (cache de Integer)

Integer c = 200;
Integer d = 200;
System.out.println(c == d);   // false (fuera de cache)
System.out.println(c.equals(d));   // true

¿Qué pasa aquí? Java cachea los Integer entre -128 y 127 (lo que se llama Integer Cache). Cuando usas autoboxing con valores en ese rango, te devuelve la misma instancia; fuera del rango, crea objetos nuevos. Por eso a == b es true (misma referencia cacheada) pero c == d es false (referencias distintas).

Regla de oro: para comparar wrappers por contenido, usa siempre .equals(), nunca ==.

Riesgo 3: sobrecoste de rendimiento

Cada autoboxing crea un objeto (salvo en el rango cacheado). En bucles críticos, esto puede afectar al rendimiento:

// LENTO: cada iteracion crea un Integer
Integer suma = 0;
for (int i = 0; i < 1_000_000; i++) {
    suma += i;   // unboxing + suma + autoboxing en cada iteracion
}

// RAPIDO: usa primitivo
long sumaRapida = 0;
for (int i = 0; i < 1_000_000; i++) {
    sumaRapida += i;
}

La primera versión crea un millón de objetos Integer que el GC tiene que limpiar. La segunda no crea ninguno. Para bucles críticos, usa primitivos.


3. Métodos útiles de los wrappers

3.1. Constantes

Cada wrapper expone constantes con los límites del tipo:

System.out.println(Integer.MAX_VALUE);   // 2147483647
System.out.println(Integer.MIN_VALUE);   // -2147483648
System.out.println(Long.MAX_VALUE);      // 9223372036854775807
System.out.println(Double.MAX_VALUE);    // 1.7976931348623157E308
System.out.println(Double.MIN_VALUE);    // 4.9E-324 (menor positivo, no negativo)
System.out.println(Double.NEGATIVE_INFINITY);
System.out.println(Double.POSITIVE_INFINITY);
System.out.println(Double.NaN);
System.out.println(Boolean.TRUE);
System.out.println(Boolean.FALSE);

Estas constantes son public static final (lo verás en UD4): están asociadas a la clase, no a instancias, y su valor no cambia.

3.2. Conversión desde String: parseXxx

Ya vimos Integer.parseInt en la Sección 2. Aquí tienes la tabla completa:

Método Qué devuelve Ejemplo
Integer.parseInt(String) int Integer.parseInt("42") → 42
Long.parseLong(String) long Long.parseLong("9999999999")
Double.parseDouble(String) double Double.parseDouble("3.14")
Float.parseFloat(String) float Float.parseFloat("3.14f")
Boolean.parseBoolean(String) boolean Boolean.parseBoolean("true")
Byte.parseByte(String) byte
Short.parseShort(String) short
Character.digit(char, int) int Conversión de un dígito en una base

Si el String no se puede parsear (por ejemplo Integer.parseInt("abc")), se lanza NumberFormatException. Más adelante, en UD3, aprenderás a gestionarla con try-catch.

3.3. Conversión a String: toString

1
2
3
4
5
6
String s1 = Integer.toString(42);       // "42"
String s2 = Double.toString(3.14);      // "3.14"
String s3 = Boolean.toString(true);     // "true"
String s4 = Integer.toHexString(255);   // "ff"
String s5 = Integer.toBinaryString(10); // "1010"
String s6 = Integer.toOctalString(64);  // "100"

Integer.toHexString, toBinaryString y toOctalString son muy útiles para depurar y para trabajar a bajo nivel.

3.4. Conversión entre tipos numéricos

Los wrappers ofrecen métodos xxxValue() para convertir entre tipos:

1
2
3
4
5
6
Integer i = 42;
byte b = i.byteValue();      // 42
short s = i.shortValue();    // 42
long l = i.longValue();      // 42L
float f = i.floatValue();    // 42.0f
double d = i.doubleValue();  // 42.0

Estos métodos son de instancia (necesitas un objeto) y devuelven el primitivo correspondiente.

3.5. Double y Float: valores especiales

1
2
3
4
5
6
7
8
double x = Math.sqrt(-1);
System.out.println(Double.isNaN(x));              // true
System.out.println(Double.isInfinite(1.0 / 0.0)); // true
System.out.println(Double.isFinite(3.14));        // true

double nan = Double.NaN;
System.out.println(nan == Double.NaN);   // false (NaN no es igual a si mismo)
System.out.println(Double.isNaN(nan));   // true (esta es la forma correcta)

Recuerda: NaN no es igual a sí mismo, así que nunca compares con == para detectarlo. Usa siempre Double.isNaN o Float.isNaN.

3.6. compareTo

Para comparar wrappers por orden:

1
2
3
4
5
Integer a = 42;
Integer b = 50;
System.out.println(a.compareTo(b));   // negativo, porque 42 < 50
System.out.println(b.compareTo(a));   // positivo, porque 50 > 42
System.out.println(a.compareTo(a));   // 0, son iguales

Útil para ordenar colecciones de wrappers (UD6).


4. La clase Objects

Objects (en java.util) es una clase de utilidades con métodos estáticos para trabajar con objetos. No tiene relación con Object (la clase raíz de Java); el nombre es confuso pero es lo que hay.

4.1. Por qué existe

Trabajar con referencias que pueden ser null es una fuente constante de bugs en Java. Objects ofrece métodos para hacerlo de forma segura y expresiva.

4.2. Métodos principales

Método Qué hace
Objects.isNull(Object) Devuelve true si el argumento es null. Equivalente a obj == null
Objects.nonNull(Object) Devuelve true si el argumento no es null. Equivalente a obj != null
Objects.requireNonNull(Object) Lanza NullPointerException si el argumento es null; si no, lo devuelve
Objects.requireNonNull(Object, String mensaje) Igual, con mensaje personalizado
Objects.requireNonNullElse(T, T defaultValue) Devuelve el argumento si no es null; si no, el valor por defecto
Objects.equals(Object a, Object b) Compara dos objetos de forma segura ante null
Objects.toString(Object) Devuelve "null" si el argumento es null; si no, llama a toString()
Objects.toString(Object, String defaultValue) Igual, pero con valor por defecto personalizado
Objects.hash(Object...) Calcula el hash de varios campos (lo verás en UD4)
Objects.compare(Object a, Object b, Comparator) Compara dos objetos con un comparador

4.3. requireNonNull: validar argumentos

Es probablemente el método más útil de la clase. Permite validar que un argumento no sea null al inicio de un método, fallando rápido si lo es:

import java.util.Objects;

public class Cliente {
    private String nombre;

    public Cliente(String nombre) {
        this.nombre = Objects.requireNonNull(nombre, "El nombre no puede ser null");
    }
}

// Si alguien hace new Cliente(null), se lanza NullPointerException
// con el mensaje "El nombre no puede ser null"

Aquí he adelantado la sintaxis de una clase propia (constructor, atributo, this), que verás en UD4. Lo importante ahora es la idea: requireNonNull valida al inicio y falla pronto, en vez de esperar a que el null cause problemas más adelante.

Analogía del portero de discoteca. requireNonNull es como el portero de una discoteca: comprueba el DNI en la entrada y, si no es válido, no te deja pasar. Sin portero, entrarías, te sentarás, pedirías una bebida, y en algún momento alguien se daría cuenta del problema. Mejor fallar en la entrada.

4.4. equals seguro ante null

1
2
3
4
5
6
7
8
String a = null;
String b = "hola";

// Sin Objects, hay que comprobar null explicitamente:
boolean iguales = (a == null) ? (b == null) : a.equals(b);

// Con Objects.equals, mucho mas conciso:
boolean iguales2 = Objects.equals(a, b);   // false

Objects.equals devuelve true si ambos son null, false si solo uno lo es, y a.equals(b) en caso contrario. Es la forma recomendada de comparar objetos cuando cualquiera de los dos puede ser null.

4.5. toString seguro ante null

1
2
3
4
String s = null;
System.out.println(s.toString());         // NullPointerException
System.out.println(Objects.toString(s));   // "null" (sin explotar)
System.out.println(Objects.toString(s, "(desconocido)"));  // "(desconocido)"

Útil para imprimir objetos que pueden ser null sin que el programa reviente.

4.6. isNull y nonNull

Son métodos puramente cosméticos: hacen lo mismo que obj == null y obj != null, pero se ven más "expresivos" cuando se usan con streams o method references (UD6/UD7):

1
2
3
String s = null;
System.out.println(Objects.isNull(s));    // true
System.out.println(Objects.nonNull(s));   // false

En código normal, s == null sigue siendo perfectamente válido.


5. Cuándo usar wrappers y cuándo primitivos

Situación Recomendación
Variables locales, parámetros, atributos normales Primitivos (más rápidos y sin riesgo de NPE)
Colecciones (List<Integer>, Map<String, Double>) Wrappers (las colecciones solo admiten objetos)
Campos opcionales que pueden ser "ausentes" Wrappers (un int no puede ser null)
Constantes como MAX_VALUE, parseInt Wrappers (son estáticos de la clase)
Bucles críticos de rendimiento Primitivos (sin autoboxing)
APIs genéricas que requieren Object Wrappers

Regla práctica: usa primitivos por defecto. Solo usa wrappers cuando sea estrictamente necesario (colecciones, nulabilidad, genéricos).

Use mouse to pan and zoom
flowchart TD
    A["Necesito un numero"] --> B{"Es para una coleccion<br/>o generico?"}
    B -->|Si| C["Wrapper (Integer, Double, ...)"]
    B -->|No| D{"Necesito que pueda ser null<br/>(valor ausente)?"}
    D -->|Si| C
    D -->|No| E["Primitivo (int, double, ...)"]
    C --> F{"Lo uso en un bucle critico?"}
    F -->|Si| G["Cuidado con autoboxing<br/>mide el rendimiento"]
    F -->|No| H["OK, usa wrapper"]

    style C fill:#ef6c00,color:#fff,stroke:#e65100
    style E fill:#2e7d32,color:#fff,stroke:#1b5e20

6. Ejemplo integrador

Programa que demuestra los conceptos de la sección: creación de wrappers, autoboxing/unboxing, constantes, parseo, conversión y métodos de Objects.

package dam.programacion.ud2;

import java.util.Objects;

/**
 * Ejemplo integrador de la Seccion 8 de UD2.
 * Demuestra:
 * - Wrappers: Integer, Double, Boolean, Character
 * - Autoboxing y unboxing
 * - Constantes de los wrappers
 * - parseXxx, toString, xxxValue
 * - Metodos de la clase Objects
 */
public class WrappersYObjects {

    public static void main(String[] args) {

        // 1. Creacion de wrappers
        Integer entero = Integer.valueOf(42);   // recomendado
        Integer entero2 = 42;                    // autoboxing
        Double decimal = 3.14;
        Boolean activo = Boolean.TRUE;
        Character letra = 'A';

        System.out.println("Entero: " + entero);    // 42
        System.out.println("Decimal: " + decimal);   // 3.14
        System.out.println("Activo: " + activo);     // true
        System.out.println("Letra: " + letra);       // A

        // 2. Constantes de los wrappers
        System.out.println("\nConstantes:");
        System.out.println("Integer.MAX_VALUE = " + Integer.MAX_VALUE);
        System.out.println("Integer.MIN_VALUE = " + Integer.MIN_VALUE);
        System.out.println("Long.MAX_VALUE = " + Long.MAX_VALUE);
        System.out.println("Double.MAX_VALUE = " + Double.MAX_VALUE);

        // 3. Conversion desde String (parseo)
        System.out.println("\nParseo:");
        int n = Integer.parseInt("42");
        double d = Double.parseDouble("3.14");
        boolean b = Boolean.parseBoolean("true");
        long l = Long.parseLong("9999999999");
        System.out.println("n=" + n + ", d=" + d + ", b=" + b + ", l=" + l);

        // 4. Conversion a String y a otras bases
        System.out.println("\nConversion a String:");
        System.out.println("42 en hex: " + Integer.toHexString(255));   // ff
        System.out.println("10 en binario: " + Integer.toBinaryString(10));   // 1010
        System.out.println("64 en octal: " + Integer.toOctalString(64));   // 100

        // 5. Conversion entre tipos (xxxValue)
        Integer i = 42;
        System.out.println("\nConversion entre tipos:");
        System.out.println("Como long: " + i.longValue());
        System.out.println("Como double: " + i.doubleValue());
        System.out.println("Como byte: " + i.byteValue());

        // 6. Double con valores especiales
        System.out.println("\nValores especiales:");
        double nan = Math.sqrt(-1);
        double infinito = 1.0 / 0.0;
        System.out.println("NaN: " + nan + " (isNaN=" + Double.isNaN(nan) + ")");
        System.out.println("Infinito: " + infinito + " (isInfinite=" + Double.isInfinite(infinito) + ")");
        System.out.println("NaN == NaN: " + (nan == nan));   // false, trampa clasica

        // 7. Trampa del == con Integer (cache -128..127)
        System.out.println("\nTrampa del == con Integer:");
        Integer a = 127;
        Integer b2 = 127;
        Integer c = 200;
        Integer d2 = 200;
        System.out.println("127 == 127: " + (a == b2));      // true (cache)
        System.out.println("200 == 200: " + (c == d2));      // false (fuera de cache)
        System.out.println("200.equals(200): " + c.equals(d2));   // true

        // 8. Clase Objects
        System.out.println("\nClase Objects:");
        String s1 = "hola";
        String s2 = null;

        System.out.println("Objects.isNull(null): " + Objects.isNull(s2));      // true
        System.out.println("Objects.nonNull(s1): " + Objects.nonNull(s1));     // true
        System.out.println("Objects.equals(s1, s2): " + Objects.equals(s1, s2));   // false
        System.out.println("Objects.equals(null, null): " + Objects.equals(null, null));   // true
        System.out.println("Objects.toString(null): " + Objects.toString(s2));   // "null"
        System.out.println("Objects.toString(null, default): " + Objects.toString(s2, "(vacio)"));   // (vacio)

        // 9. requireNonNull
        System.out.println("\nrequireNonNull:");
        String validado = Objects.requireNonNull(s1, "s1 no puede ser null");
        System.out.println("s1 validado: " + validado);

        // Si descomentas la siguiente linea, se lanza NullPointerException
        // Objects.requireNonNull(s2, "s2 no puede ser null");

        // 10. requireNonNullElse
        String nombre = Objects.requireNonNullElse(s2, "anonimo");
        System.out.println("Nombre final: " + nombre);   // anonimo
    }
}

Salida esperada:

Entero: 42
Decimal: 3.14
Activo: true
Letra: A

Constantes:
Integer.MAX_VALUE = 2147483647
Integer.MIN_VALUE = -2147483648
Long.MAX_VALUE = 9223372036854775807
Double.MAX_VALUE = 1.7976931348623157E308

Parseo:
n=42, d=3.14, b=true, l=9999999999

Conversion a String:
42 en hex: ff
10 en binario: 1010
64 en octal: 100

Conversion entre tipos:
Como long: 42
Como double: 42.0
Como byte: 42

Valores especiales:
NaN: NaN (isNaN=true)
Infinito: Infinity (isInfinite=true)
NaN == NaN: false

Trampa del == con Integer:
127 == 127: true
200 == 200: false
200.equals(200): true

Clase Objects:
Objects.isNull(null): true
Objects.nonNull(s1): true
Objects.equals(s1, s2): false
Objects.equals(null, null): true
Objects.toString(null): null
Objects.toString(null, default): (vacio)

requireNonNull:
s1 validado: hola
Nombre final: anonimo

7. Errores comunes

Error Qué pasa Cómo evitarlo
Usar new Integer(42) Funciona pero está deprecated Usa Integer.valueOf(42) o autoboxing
Comparar wrappers con == Funciona a veces (cache) y falla otras Usa siempre .equals()
Hacer unboxing de null NullPointerException Comprueba null antes de asignar a un primitivo
Usar wrappers en bucles críticos Sobrecoste por autoboxing Usa primitivos en bucles críticos
Comparar NaN con == Siempre devuelve false, incluso NaN == NaN Usa Double.isNaN(x)
Confundir Objects con Object Object es la raíz; Objects es utilidades Recuerda: Objects siempre en plural
Llamar a toString() sobre null NullPointerException Usa Objects.toString(obj) o Objects.toString(obj, default)
Olvidar que Integer.parseInt("abc") lanza excepción El programa revienta Usa try-catch (lo verás en UD3)
Usar wrappers "por defecto" Memoria y rendimiento innecesarios Usa primitivos salvo que necesites objetos

8. Resumen

Concepto Idea clave
Wrapper Clase que envuelve un primitivo: Integer, Double, Boolean, Character...
Autoboxing Primitivo → wrapper automático
Unboxing Wrapper → primitivo automático
valueOf(x) Forma recomendada de crear wrappers (aprovecha cache)
new Integer(x) Deprecated desde Java 9; evitar
Integer.MAX_VALUE / MIN_VALUE Constantes de los límites del tipo
parseXxx(String) Conversión desde String a primitivo
Integer.toHexString / toBinaryString / toOctalString Conversión a otras bases
xxxValue() Conversión entre tipos numéricos
Double.isNaN(x) Detectar NaN (no usar == NaN)
Integer Cache Integer entre -128 y 127 se cachea; afecta a ==
Objects.requireNonNull(obj, msg) Validar que un argumento no es null
Objects.equals(a, b) Comparación segura ante null
Objects.toString(obj, default) toString seguro ante null
Objects.isNull / Objects.nonNull Versiones expresivas de == null / != null
Regla práctica Primitivos por defecto; wrappers solo cuando sea necesario

9. Cuestionario de autoevaluación

Preguntas

  1. ¿Qué es una clase wrapper y por qué existen en Java?

  2. Nombra los wrappers de los 8 tipos primitivos. ¿Cuáles NO siguen el patrón de "capitalizar el nombre del primitivo"?

  3. ¿Cuál es la diferencia entre Integer.valueOf(42), new Integer(42) y la asignación directa Integer i = 42? ¿Cuál es la forma recomendada?

  4. ¿Qué es el autoboxing y el unboxing? Pon un ejemplo de cada uno.

  5. ¿Por qué el siguiente código imprime false?

    1
    2
    3
    Integer c = 200;
    Integer d = 200;
    System.out.println(c == d);
    

  6. ¿Por qué c.equals(d) en el código anterior sí devuelve true?

  7. ¿Qué valor devuelve Integer.parseInt("abc")? ¿Qué ocurre?

  8. Escribe una línea que convierta el String "3.14" a un double.

  9. ¿Por qué Double.NaN == Double.NaN devuelve false? ¿Cómo se comprueba entonces si un valor es NaN?

  10. ¿Qué hace Objects.requireNonNull(obj, "mensaje") y cuándo se suele usar?

  11. ¿Qué devuelven Objects.toString(null) y Objects.toString(null, "por defecto")?

  12. ¿Por qué el siguiente código lanza NullPointerException?

    Integer n = null;
    int valor = n;
    

  13. ¿Qué clase usarías para representar la edad de un cliente en un campo opcional de un formulario que puede estar vacío: int o Integer? ¿Por qué?

  14. ¿Qué método de Integer devuelve la representación binaria de un número? ¿Y la hexadecimal?

  15. Di si cada uno de estos métodos es estático o de instancia: Integer.parseInt, Integer.valueOf, Integer.MAX_VALUE, i.intValue(), Objects.requireNonNull.

Soluciones

  1. Un wrapper es una clase que envuelve un valor primitivo en un objeto. Existen porque Java distingue claramente primitivos de objetos, y hay situaciones donde solo se pueden usar objetos: colecciones, genéricos, APIs que requieren Object, campos que pueden ser null (valor ausente) y para exponer constantes y utilidades estáticas.

  2. Byte, Short, Integer, Long, Float, Double, Boolean, Character. Los que no siguen el patrón son Integer (de int) y Character (de char).

  3. Las tres formas crean un Integer con valor 42, pero:

  4. Integer.valueOf(42): recomendado. Aprovecha la cache de Integer para valores entre -128 y 127.
  5. new Integer(42): crea siempre un objeto nuevo. Deprecated desde Java 9.
  6. Integer i = 42: autoboxing; el compilador lo traduce a Integer.valueOf(42). Recomendado por concisión.

La forma recomendada es valueOf o autoboxing.

  1. Autoboxing: conversión automática de primitivo a wrapper, hecha por el compilador. Ejemplo: Integer i = 5; (el compilador inserta Integer.valueOf(5)). Unboxing: conversión automática de wrapper a primitivo. Ejemplo: int n = i; (el compilador inserta i.intValue()).

  2. Porque Integer cachea los valores entre -128 y 127. Para valores fuera de ese rango (como 200), el autoboxing crea objetos nuevos, con referencias distintas. == compara referencias, no contenidos, así que devuelve false.

  3. Porque equals compara por contenido, no por referencia. Para valores dentro del rango cacheado, == también funcionaría por accidente, pero equals es siempre fiable.

  4. No devuelve nada: lanza NumberFormatException en tiempo de ejecución, porque "abc" no es un número entero válido. Más adelante, en UD3, verás cómo gestionarla con try-catch.

  5. double d = Double.parseDouble("3.14");

  6. Porque el estándar IEEE 754 define que NaN no es igual a sí mismo (es una propiedad matemática del "no es un número"). Para comprobarlo, usa Double.isNaN(x) o Float.isNaN(x).

  7. Lanza NullPointerException si obj es null; si no, devuelve obj. Se usa al inicio de métodos y constructores para validar argumentos y fallar pronto con un mensaje claro, en vez de esperar a que el null cause un error misterioso más adelante.

  8. Objects.toString(null) devuelve la cadena "null". Objects.toString(null, "por defecto") devuelve "por defecto". Ninguno de los dos lanza excepción.

  9. Porque el compilador traduce int valor = n; como int valor = n.intValue();, y como n es null, intentar llamar a un método sobre null lanza NullPointerException. Es una trampa clásica del unboxing.

  10. Integer, porque un primitivo int no puede ser null. Si el campo es opcional, necesitas distinguir "edad no introducida" (null) de "edad 0" (que sería un valor válido). Con Integer, puedes representar ambos casos.

  11. Binaria: Integer.toBinaryString(int). Hexadecimal: Integer.toHexString(int). (También existe Integer.toOctalString para octal.)

  12. Todos estáticos excepto i.intValue(), que es de instancia (necesita un objeto Integer concreto sobre el que operar). Integer.MAX_VALUE es una constante static final, también asociada a la clase.

Evaluación del cuestionario

Aciertos Nivel
14-15 Dominas la sección. Adelante con la siguiente.
10-13 Bien, repasa los puntos fallados.
6-9 Repasa la sección, especialmente autoboxing y la trampa de == con wrappers.
Menos de 6 Vuelve a leer con calma y ejecuta el ejemplo integrador paso a paso en IntelliJ.

10. Información ampliada y próximos pasos

Próximos pasos:

  1. Practica en IntelliJ. Copia el ejemplo integrador y modifícalo: prueba a descomentar la línea que lanza NullPointerException con requireNonNull, observa el mensaje, prueba a comparar distintos valores de Integer con == y con .equals().
  2. Experimenta con NaN y Infinity. Calcula 0.0 / 0.0, 1.0 / 0.0, -1.0 / 0.0, Math.sqrt(-1). Comprueba con Double.isNaN e Double.isInfinite.
  3. Avanza a la Sección 9 de UD2. Maven: gestión de dependencias externas. El repositorio Central de Maven, cómo añadir una dependencia al pom.xml, primer contacto con dependencias externas usando JUnit 5, y el ciclo de vida de Maven (compile, test, package, install). Es la sección que abre la puerta al ecosistema de librerías Java más allá del JDK.