Saltar al contenido
Portada » TechNexus – Blog » Cookbook de trampas comunes en Java

Cookbook de trampas comunes en Java

trampas comunes en Java

Cookbook de trampas comunes en Java

Este documento recopila errores comunes y comportamientos sutiles en Java que pueden generar bugs difíciles de detectar. Cada entrada sigue el esquema problema → solución → discusión para que puedas identificar, entender y evitar estos fallos en tu código.


1. Comparación de Integer fuera del rango cacheado

Problema: El operador == devuelve false para enteros mayores a 127 (o menores a -128) aunque los valores numéricos sean iguales.

Solución: Usa .equals() para comparar valores de Integer. Para rendimiento o simplicidad, usa int si no necesitas nulos.

Discusión: Java cachea los Integer en el rango [-128, 127]. Fuera de ese rango, Integer.valueOf() devuelve instancias distintas; == compara referencias, no contenido.


2. Comparación de String con ==

Problema: == entre cadenas a veces da true y otras false para el mismo texto, lo que confunde a principiantes.

Solución: Compara cadenas con .equals() o .equalsIgnoreCase().

Discusión: El string pool puede hacer que dos literales apunten a la misma referencia, pero objetos creados con new String() no. == compara referencias; .equals() compara contenido.


3. Modificar una colección durante la iteración

Problema: Eliminar o añadir elementos a una colección mientras se recorre con for-each lanza ConcurrentModificationException.

Solución: Usa un Iterator y su remove(), o recopila primero y modifica después; alternativamente, usa colecciones concurrentes.

Discusión: Muchas colecciones detectan cambios estructurales durante la iteración. Las colecciones concurrentes (p. ej., CopyOnWriteArrayList) tienen semánticas diferentes y coste adicional.


4. NullPointerException al hacer unboxing

Problema: Convertir un Integer null a int produce NullPointerException.

Solución: Valida null antes del unboxing o usa OptionalInt / valores por defecto.

Discusión: El unboxing invoca internamente intValue() sobre la referencia; si es null, falla. Evítalo donde sea posible usando tipos primitivos.


5. Arrays no hacen copia profunda

Problema: Asignar un array a otra variable copia la referencia; cambios en una vista afectan a la otra.

Solución: Usa Arrays.copyOf() o clone() para copiar el array; para matrices/objetos, implementa una copia profunda explícita.

Discusión: La copia por referencia es rápida pero sorprende a quienes esperan independencia de datos. Para estructuras anidadas, copia cada nivel.


6. Falta de break en switch (fall-through)

Problema: Olvidar break ejecuta casos adicionales inesperadamente.

Solución: Añade break o usa switch con expresiones (Java 14+) y ->, que no hace fall-through por defecto.

Discusión: El fall-through es útil en casos agrupados, pero suele ser fuente de bugs. Las expresiones switch modernas reducen este riesgo.


7. Llamadas virtuales en constructores (inicialización incompleta)

Problema: Llamar a métodos sobreescritos desde un constructor puede usar campos aún no inicializados de la subclase.

Solución: No invoques métodos sobreescritos en constructores; usa métodos final o inicialización explícita.

Discusión: El constructor de la superclase se ejecuta antes de inicializar los campos de la subclase. La llamada virtual accede a estado incompleto.


8. Arrays de tipos genéricos

Problema: No se pueden crear arrays de tipos genéricos concretos, p. ej., new ArrayList<String>[10].

Solución: Usa List<?>[] o preferentemente colecciones, evitando arrays de genéricos.

Discusión: Los arrays son covariantes y reificados; los genéricos usan borrado de tipos. La mezcla rompe seguridad de tipos en tiempo de ejecución.


9. Claves mutables en HashMap/HashSet

Problema: Modificar una clave después de insertarla impide encontrarla de nuevo.

Solución: Usa claves inmutables (por ejemplo, record o clases inmutables) o no modifiques las claves tras usarlas.

Discusión: hashCode() y equals() determinan la ubicación. Si cambian, el mapa pierde la referencia a la entrada.


10. Optional.get() sin verificar presencia

Problema: Llamar a get() sobre un Optional vacío lanza NoSuchElementException.

Solución: Usa orElse(), orElseGet(), orElseThrow() o ifPresent().

Discusión: Optional pretende evitar null; usar get() rompe su intención. Prefiere API expresivas.


11. Shadowing de variables (ocultamiento de campos)

Problema: El parámetro del método oculta el campo de instancia y asignaciones como nombre = nombre; no hacen nada útil.

Solución: Usa this.nombre = nombre; o cambia el nombre del parámetro.

Discusión: El sombreado dificulta el mantenimiento y causa bugs silenciosos. Activar inspecciones del IDE ayuda a detectarlo.


12. Concurrencia: falta de volatile (visibilidad)

Problema: Un hilo no observa cambios de otro hilo sobre una variable sin sincronización.

Solución: Declara la variable como volatile o sincroniza accesos (synchronized, Lock).

Discusión: El modelo de memoria de Java permite reordenamientos y cachés por hilo. volatile garantiza visibilidad y orden de publicación/lectura.


13. Concurrencia: incrementos no atómicos

Problema: Operaciones como valor++ pierden actualizaciones bajo concurrencia.

Solución: Usa AtomicInteger.incrementAndGet(), bloques synchronized o acumuladores (LongAdder).

Discusión: valor++ implica leer-modificar-escribir. Sin exclusión mutua, dos hilos pueden sobrescribir resultados.


14. Calendar: mes base cero

Problema: Calendar#set(año, mes, día) interpreta enero como 0; 10 es noviembre, no octubre.

Solución: Usa constantes (Calendar.OCTOBER) o migra a java.time.

Discusión: La API antigua de fechas es propensa a errores. java.time (JSR-310) es más clara e inmutable.


15. LocalDate.parse() con formatos no ISO

Problema: LocalDate.parse("05/10/2025") lanza DateTimeParseException.

Solución: Define un formateador: DateTimeFormatter.ofPattern("dd/MM/yyyy") y pásalo a parse().

Discusión: Por defecto, parse() espera yyyy-MM-dd. Para otros formatos debes indicar el patrón explícitamente.

es_ESSpanish