Saltar a contenido

6. Constantes y literales

**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.6 — Se han creado y utilizado constantes y literales.
Marco normativo RD 405/2023 · RD 450/2010 · RD 659/2023
Tecnología base Java 25 LTS — final, static final, enum (introducción; profundización en UD4).

Introducción de la sección

Hasta ahora todas las variables que has visto podían cambiar de valor durante la ejecución: por eso se llaman "variables". Pero en muchos casos no quieres que un valor cambie jamás: el número PI vale 3.14159 y no va a variar mañana, la mayoría de edad son 18 años mientras la ley no cambie, el IVA general es el 21% hasta que el Gobierno lo modifique. Para esos casos, Java ofrece el modificador final, que "congela" una variable y la convierte en constante: una vez asignado el valor, no puede volver a cambiar. Y para constantes compartidas por toda una clase, existe la combinación static final que es el estándar de facto en la industria Java.

El objetivo de esta sección es que entiendas por qué escribir if (edad >= 18) en medio del código es una mala práctica profesional (lo que se llama magic number), cómo declarar constantes locales y de clase con final y static final, qué convenciones de nombres las identifican (UPPER_SNAKE_CASE), cuándo es mejor una constante frente a un enum, y la trampa sutil de usar final con referencias a objetos (la referencia es constante, pero el contenido del objeto puede cambiar). Cuando acabes, sabrás eliminar los números mágicos de tu código y dejarlo mantenible, que es la diferencia entre un programa de estudiante y un programa profesional.


6.1 El problema de los magic numbers

Qué es un magic number

Un magic number (número mágico) es un literal numérico que aparece directamente en el código sin explicación de su significado. El nombre es despectivo: viene de que el número "hace magia" (funciona) pero nadie sabe muy bien por qué tiene ese valor y no otro.

// Ejemplo típico de magic numbers
if (edad >= 18) {
    precio = precio * 1.21;   // ¿18? ¿1.21? ¿Qué significan?
}

if (intentos > 3) {
    bloquearCuenta();
}

if (codigo == 404) {
    mostrarPaginaNoEncontrada();
}

Para ti, hoy, esos números pueden ser obvios. Pero dentro de seis meses, o para tu compañero que herede el código, serán un misterio: ¿18 son años? ¿meses? ¿un identificador? ¿1.21 es un factor de corrección? ¿el IVA? ¿un tipo de interés? Cada número requiere que el lector abra el debugger o el git blame para reconstruir el contexto. Eso es tiempo perdido y fuente de bugs.

Analogía

Un magic number es como recoger una receta de cocina de tu abuela y leer "añadir 2 cucharadas de polvo misterioso". ¿Es sal? ¿Azúcar? ¿Levadura? Si la receta dijera "añadir 2 cucharadas de canela molida", no habría duda. La canela tiene nombre propio; el "polvo misterioso" obliga a adivinar. En programación, ponerle nombre a un número es como etiquetar el bote de especias: el código se vuelve autoexplicativo.

La solución: constantes con nombre

La forma de eliminar magic numbers es declararlos como constantes con un nombre descriptivo:

// Versión sin magic numbers: se entiende sin comentarios
if (edad >= MAYORIA_EDAD) {
    precio = precio * (1 + IVA_GENERAL);
}

if (intentos > MAX_INTENTOS_LOGIN) {
    bloquearCuenta();
}

if (codigo == HTTP_NOT_FOUND) {
    mostrarPaginaNoEncontrada();
}
flowchart LR
    A["Edad 18<br/>aparece 7 veces<br/>en el código"] --> B{"¿Y si cambia<br/>la ley?"}
    B --> C["Buscar las 7 apariciones<br/>a mano con Ctrl+F"]
    C --> D["Cambiarlas una a una<br/>sin olvidar ninguna"]
    D --> E["Riesgo de bug:<br/>unas sí, otras no"]

    F["MAYORIA_EDAD = 18<br/>declarado 1 vez"] --> G{"¿Y si cambia<br/>la ley?"}
    G --> H["Cambiar 1 sola línea"]
    H --> I["Todas las referencias<br/>se actualizan solas"]
    I --> J["Sin riesgo de bug"]

    style A fill:#ffebee,stroke:#c62828,color:#000
    style B fill:#fff3e0,stroke:#e65100,color:#000
    style C fill:#ffebee,stroke:#c62828,color:#000
    style D fill:#ffebee,stroke:#c62828,color:#000
    style E fill:#ffebee,stroke:#c62828,color:#000
    style F fill:#e8f5e9,stroke:#2e7d32,color:#000
    style G fill:#fff3e0,stroke:#e65100,color:#000
    style H fill:#e8f5e9,stroke:#2e7d32,color:#000
    style I fill:#e8f5e9,stroke:#2e7d32,color:#000
    style J fill:#e8f5e9,stroke:#2e7d32,color:#000

Diagrama 6.1 — Un magic number esclaviza el mantenimiento; una constante lo libera: un solo punto de cambio para todas las referencias.

Por qué son malos

Los magic numbers tienen cuatro problemas concretos que empeoran con el tiempo:

  1. Incomprensión: el lector no sabe qué significa el número ni por qué tiene ese valor.
  2. Mantenimiento: si el valor cambia (la ley baja la mayoría de edad a 16, el IVA sube al 25%), tienes que buscar todas las apariciones manualmente. Forgetting una es un bug.
  3. Duplicación: el mismo número aparece varias veces; si cambias unas y olvidas otras, el programa se vuelve inconsistente.
  4. Búsqueda imposible: buscar 18 en un proyecto de 50.000 líneas te da cientos de resultados, la mayoría falsos positivos. Buscar MAYORIA_EDAD te lleva directamente al sitio.

Regla de oro. Si un número no es 0, 1, o evidente por el contexto (como i++ en un bucle), ponle nombre. Cualquier otro número que aparezca literal en el código es sospechoso de ser un magic number.

Ejemplo real: el bug del año 2000

El ejemplo histórico más caro de magic numbers fue el efecto 2000. En los años 60 y 70, los programadores escribían los años con dos dígitos (69 en vez de 1969) para ahorrar memoria. El número 19 estaba hardcodeado por todas partes: "19" + añoDosDigitos. Cuando llegó el año 2000, esos 19 tenían que cambiar a 20, y la búsqueda y sustitución afectó a millones de líneas de código en todo el mundo. Se estima que costó más de 300.000 millones de dólares. Si en su momento se hubiera definido SIGLO = 19 como constante, el cambio habría sido trivial. Lección: los magic numbers te los cobra la historia con intereses.


6.2 final: el modificador que congela el valor

Sintaxis básica

Para declarar una constante en Java, añades el modificador final antes del tipo. Una vez asignado el valor, no puede volver a cambiarse:

1
2
3
4
5
6
7
final int MAYORIA_EDAD = 18;
final double IVA_GENERAL = 0.21;
final double PI = 3.141592653589793;
final String NOMBRE_APP = "Sistema de Gestión Académica";
final boolean MODO_DEBUG = false;

MAYORIA_EDAD = 21;   // ERROR de compilación: cannot assign a value to final variable

Pista

El nombre del fichero y la línea del error te dirán dónde te has saltado la regla. IntelliJ lo subraya en rojo en cuanto escribes la reasignación. No esperes a compilar: el IDE te avisa al instante.

Declaración e inicialización

Una variable final puede declararse y inicializarse en el mismo momento, o declararse primero e inicializarse más tarde (pero solo una vez):

// Forma 1: declaración + inicialización inmediata (lo más común)
final double IVA_GENERAL = 0.21;

// Forma 2: declaración primero, inicialización después
final double descuento;
if (esClienteVIP) {
    descuento = 0.15;
} else {
    descuento = 0.05;
}
descuento = 0.10;   // ERROR: ya fue asignada en el if/else

La forma 2 es útil cuando el valor depende de una condición, pero debes asegurar que todos los caminos del código asignen la variable exactamente una vez. Si hay un if sin else que no la inicializa, el compilador te lo dirá: variable descuento might not have been initialized.

Constantes locales vs campos

final funciona tanto en variables locales como en campos de clase:

public class Calculadora {

    // Campo constante (de instancia): cada objeto tiene su copia
    final double FACTOR_CORRECCION = 1.05;

    void calcularPrecio(double precioBase) {
        // Constante local: solo existe dentro de este método
        final int DESCUENTO_PORCENTAJE = 10;

        double precio = precioBase * FACTOR_CORRECCION;
        precio = precio * (1 - DESCUENTO_PORCENTAJE / 100.0);
        return precio;
    }
}

En este ejemplo, FACTOR_CORRECCION es un campo final (una constante por objeto) y DESCUENTO_PORCENTAJE es una constante local al método calcularPrecio. Diferencia clave: el campo vive mientras viva el objeto; la local, solo mientras se ejecuta el método.

Cuidado

Un campo final sin static es una constante por objeto: cada instancia de la clase tiene su propia copia. Si el valor es el mismo para todos los objetos (como PI o IVA_GENERAL), estás desperdiciando memoria. Para esos casos, usa static final (lo veremos en 6.3).

final en parámetros de método

También puedes marcar los parámetros de un método como final, lo que impide que se reasignen dentro del método:

1
2
3
4
void procesar(final int cantidad) {
    cantidad = cantidad + 1;   // ERROR: no se puede reasignar un parámetro final
    int resultado = cantidad * 2;   // bien: leerlo sí se puede
}

Es poco común en código Java moderno (la convención es no reasignar parámetros aunque no sean final), pero aparece en código heredado y en programación funcional con lambdas (donde las variables referenciadas deben ser efectivamente final).

La convención UPPER_SNAKE_CASE

Las constantes static final siguen una convención de nombres muy estricta: UPPER_SNAKE_CASE (todo en mayúsculas, palabras separadas por guión bajo). Esta convención es universal en Java: la encontrarás en el API oficial, en todas las librerías y en todo el código empresarial.

Tipo de identificador Convención Ejemplos
Variables y métodos camelCase edad, calcularPrecio
Clases, interfaces, records PascalCase Configuracion, Alumno
Constantes static final UPPER_SNAKE_CASE IVA_GENERAL, MAX_INTENTOS_LOGIN
Paquetes todo.minusculas dam.programacion.ud1

La razón de esta convención es legibilidad: cuando ves IVA_GENERAL en medio del código, sabes al instante que es una constante, no una variable. Tu cerebro no tiene que hacer la pregunta "¿esto puede cambiar?". Es un convenio tan fuerte que violarlo se considera casi un error de compilación en la práctica profesional.


6.3 static final: constantes de clase

Qué añade static

Cuando una constante es la misma para todos los objetos de una clase (como PI, IVA_GENERAL, MAX_INTENTOS), no tiene sentido que cada objeto tenga su propia copia. Ahí entra static: declara el campo como perteneciente a la clase, no a las instancias. Solo existe una copia compartida por toda la clase, y se accede a ella con NombreClase.NOMBRE_CONSTANTE.

1
2
3
4
5
6
7
8
public class Configuracion {

    // Constante de clase: una sola copia para toda la clase
    public static final double IVA_GENERAL = 0.21;
    public static final int MAX_INTENTOS_LOGIN = 3;
    public static final String NOMBRE_APP = "Gestión Académica";
    public static final int HTTP_NOT_FOUND = 404;
}
1
2
3
4
5
6
// Acceso desde cualquier parte del código
double precioConIVA = precio * (1 + Configuracion.IVA_GENERAL);

if (intentos > Configuracion.MAX_INTENTOS_LOGIN) {
    bloquearCuenta();
}

Ejemplos del API de Java

El propio API de Java está lleno de constantes static final que ya has usado sin saberlo:

// De la clase Math
Math.PI                    // 3.141592653589793
Math.E                     // 2.718281828459045

// De la clase Integer
Integer.MAX_VALUE          // 2_147_483_647
Integer.MIN_VALUE          // -2_147_483_648

// De la clase Double
Double.POSITIVE_INFINITY   // +infinito
Double.NaN                 // Not-a-Number

// De la clase System
System.lineSeparator()     // "\n" en Linux/macOS, "\r\n" en Windows

// De java.time
TimeUnit.SECONDS           // enum, pero con filosofía similar

Pista

Cuando veas Clase.NOMBRE_EN_MAYUSCULAS en cualquier código Java, puedes asumir al 99% que es una constante public static final. Esta convención es más fiable que muchos comentarios.


6.4 Constantes vs variables: cuándo usar cada una

Criterio de decisión

La pregunta "¿esto debe ser constante o variable?" no siempre tiene respuesta obvia. La regla general es: si el valor no debería cambiar durante la ejecución, hazlo constante. Pero hay matices.

Criterio Constante (final) Variable
¿Cambia en ejecución? No
¿Es conocido en compilación? Sí (o se calcula una sola vez) No (depende de la entrada)
¿Es compartido por toda la clase? Suele ser static final Suele ser campo de instancia
Ejemplos PI, IVA_GENERAL, MAX_INTENTOS edad, nombre, precioActual
flowchart TD
    D["Tengo un valor en mi código"] --> P1{"¿Cambia durante<br/>la ejecución?"}
    P1 -->|"Sí"| V["VARIABLE<br/>(int edad, double precio...)"]
    P1 -->|"No"| P2{"¿Es el mismo valor<br/>para todos los objetos<br/>de la clase?"}
    P2 -->|"Sí"| SF["CONSTANTE DE CLASE<br/>static final NOMBRE = valor;"]
    P2 -->|"No, depende del objeto"| F["CONSTANTE DE INSTANCIA<br/>final NOMBRE = valor;"]
    P2 -->|"Solo dentro de un método"| LF["CONSTANTE LOCAL<br/>final NOMBRE = valor;"]

    style D fill:#1565c0,color:#fff,stroke:#0d47a1
    style P1 fill:#fff8e1,stroke:#f57f17,color:#000
    style P2 fill:#fff8e1,stroke:#f57f17,color:#000
    style V fill:#e3f2fd,stroke:#1565c0,color:#000
    style SF fill:#e8f5e9,stroke:#2e7d32,color:#000
    style F fill:#fff3e0,stroke:#e65100,color:#000
    style LF fill:#f3e5f5,stroke:#6a1b9a,color:#000

Diagrama 6.2 — Árbol de decisión para elegir entre variable y los tres tipos de constante.

Casos límite

Constante que se calcula al arrancar

Una constante static final no tiene por qué tener un literal como valor: puede ser el resultado de una expresión que se evalúa una sola vez al cargar la clase:

1
2
3
4
5
public class Configuracion {
    public static final double IVA_INCLUIDO = 1 + IVA_GENERAL;   // 1.21
    public static final int MAX_BYTES = 1024 * 1024;             // 1 megabyte
    public static final String RUTA_LOG = System.getProperty("user.home") + "/app.log";
}

La inicialización se hace en el orden en que aparecen las constantes, en el momento en que la JVM carga la clase. Esto se llama inicialización estática y es muy potente, pero requiere cuidado con el orden (no puedes referenciar IVA_INCLUIDO antes de declarar IVA_GENERAL).

Constante leída de un fichero

Si el valor se lee de un fichero de configuración al arrancar la aplicación, técnicamente no es una constante de compilación, pero sí quieres que sea inmutable durante la ejecución. La forma de hacerlo es static final inicializada en un bloque static:

1
2
3
4
5
6
7
8
9
public class Configuracion {
    public static final double IVA_GENERAL;

    static {   // bloque de inicialización estática: se ejecuta al cargar la clase
        IVA_GENERAL = leerIVADeFichero();   // se ejecuta una vez
    }

    private static double leerIVADeFichero() { ... }
}

Estos matices los verás con más detalle en UD4 (Clases y Objetos Propios). Por ahora basta con que sepas que static final no se limita a literales: cualquier expresión que se evalúe una sola vez al cargar la clase puede inicializarlo.


6.5 Introducción a los enum

El problema que resuelven

A veces una variable solo puede tomar un conjunto cerrado de valores: los días de la semana (LUNES a DOMINGO), los meses del año, los palos de una baraja (OROS, COPAS, ESPADAS, BASTOS), los estados de un pedido (PENDIENTE, PAGADO, ENVIADO, ENTREGADO, CANCELADO), los niveles de log (TRACE, DEBUG, INFO, WARN, ERROR). Para esos casos, las constantes numéricas (static final int LUNES = 1; static final int MARTES = 2;...) son incómodas y propensas a errores: nada impide asignar int dia = 99; que no corresponde a ningún día.

Los enum (de enumeration) son la solución de Java para representar un conjunto cerrado de valores. Un enum define un tipo con un número finito de instancias, y la variable solo puede tomar una de ellas.

Sintaxis básica

public enum DiaSemana {
    LUNES, MARTES, MIERCOLES, JUEVES, VIERNES, SABADO, DOMINGO
}

public enum EstadoPedido {
    PENDIENTE, PAGADO, ENVIADO, ENTREGADO, CANCELADO
}

public enum PaloBaraja {
    OROS, COPAS, ESPADAS, BASTOS
}

Uso:

DiaSemana hoy = DiaSemana.MIERCOLES;
EstadoPedido pedido = EstadoPedido.PAGADO;

if (hoy == DiaSemana.SABADO || hoy == DiaSemana.DOMINGO) {
    System.out.println("Fin de semana");
}

switch (pedido) {
    case PENDIENTE -> System.out.println("Pendiente de pago");
    case PAGADO    -> System.out.println("Pagado, esperando envío");
    case ENVIADO   -> System.out.println("En camino");
    case ENTREGADO -> System.out.println("Entregado");
    case CANCELADO -> System.out.println("Cancelado");
}

Ventajas sobre constantes numéricas

Aspecto Constantes int enum
Seguridad de tipos int dia = 99; compila DiaSemana dia = 99; no compila
Legibilidad if (dia == 1) críptico if (dia == DiaSemana.LUNES) claro
Navegabilidad IntelliJ no sabe qué valores son válidos El IDE autocompleta los valores
Switch con exhaustividad default obligado El compilador avisa si falta un caso
Información de depuración Imprime 1, 2, 3... Imprime LUNES, MARTES...
Espacio de nombres Colisión entre enumeraciones Cada enum tiene su propio espacio
// Peligroso con constantes numéricas
public static final int LUNES = 1;
public static final int MARTES = 2;
// ...
public static final int ENERO = 1;   // ¡colisión! LUNES y ENERO valen 1
int x = LUNES;
if (x == ENERO) { ... }   // compila y es true: bug absurdo

// Seguro con enums
DiaSemana d = DiaSemana.LUNES;
Mes m = Mes.ENERO;
if (d == m) { ... }   // ERROR de compilación: no se pueden comparar tipos distintos

Ampliación

Los enum en Java son mucho más que listas de constantes: son clases especiales que pueden tener campos, métodos, constructores y implementar interfaces. Estos usos avanzados los verás en UD4 (Clases y Objetos Propios), donde los enum se estudian a fondo

Regla. Si una variable solo puede tomar un número finito y conocido de valores, usa enum. Si el valor es numérico pero libre (como un precio, una edad, un porcentaje), usa constante static final. Si el valor cambia durante la ejecución, usa variable normal.


6.6 final en referencias a objetos: la referencia vs. el contenido

La trampa sutil

Aquí hay un matiz que confunde incluso a programadores con experiencia: cuando declaras una referencia a un objeto como final, lo que es constante es la referencia, no el contenido del objeto.

import java.util.ArrayList;
import java.util.List;

public class Ejemplo {
    public static void main(String[] args) {
        final List<String> lista = new ArrayList<>();

        lista = new ArrayList<>();   // ERROR: no se puede reasignar la referencia

        lista.add("hola");            // BIEN: el contenido del objeto SÍ puede cambiar
        lista.add("mundo");
        lista.clear();
        System.out.println(lista);    // []
    }
}

Analogía

Imagina que final es un candado puesto en la etiqueta del cajón, no en su contenido. No puedes cambiar la etiqueta (que apunte a otro cajón), pero sí puedes meter y sacar cosas del cajón a tu antojo. Para que el contenido también sea inmutable, necesitarías un cajón con su propio candado interno (es decir, una clase diseñada como inmutable, como String).

flowchart TD
    R["final List<String> lista = new ArrayList<>()"] --> REF["Referencia lista<br/>(CANDADO final)"]
    REF --> OBJ["Objeto ArrayList<br/>(en el heap)"]
    OBJ --> C1["add(), remove(), clear()<br/>SÍ permitidos"]
    REF --> REAS["lista = new ArrayList()<br/>NO permitido<br/>(ERROR de compilación)"]

    style R fill:#1565c0,color:#fff,stroke:#0d47a1
    style REF fill:#fff3e0,stroke:#e65100,color:#000
    style OBJ fill:#e8f5e9,stroke:#2e7d32,color:#000
    style C1 fill:#e8f5e9,stroke:#2e7d32,color:#000
    style REAS fill:#ffebee,stroke:#c62828,color:#000

Diagrama 6.3 — final congela la referencia, no el contenido del objeto. La lista no puede apuntar a otra instancia, pero su contenido puede cambiar.

Implicaciones prácticas

Esta distinción es crucial en varios casos:

  1. Listas, maps y sets: final List<String> no es una lista inmutable; es una lista cuya referencia no puede cambiar. Para hacer la lista realmente inmutable, usa List.of(...) o Collections.unmodifiableList(...) (lo verás en UD6).

  2. Arrays: igual que las listas. final int[] arr = {1, 2, 3}; permite arr[0] = 99; pero no arr = new int[10];.

  3. Objetos propios: si declaras final Alumno a = new Alumno("Ana");, no puedes asignar a = new Alumno("Ben");, pero sí puedes llamar a a.setNombre("Ben"); si la clase Alumno tiene ese método.

  4. Strings: como String es inmutable por diseño, final String sí produce un valor realmente constante (no hay métodos que cambien el contenido).

Listas inmutables de verdad

Si necesitas una lista constante de verdad (que ni la referencia ni el contenido cambien), usa los métodos List.of, Set.of, Map.of (Java 9+):

1
2
3
4
public static final List<String> COLORES_PRIMARIOS = List.of("rojo", "verde", "azul");

COLORES_PRIMARIOS.add("amarillo");   // ERROR en ejecución: UnsupportedOperationException
COLORES_PRIMARIOS.set(0, "ROJO");    // ERROR en ejecución

Estos métodos devuelven listas inmutables: ni añadir, ni eliminar, ni modificar. Son perfectas para declarar constantes de tipo colección. Lo verás en detalle en UD6 (Tipos Avanzados de Datos).


6.7 Errores comunes y buenas prácticas

Los cinco errores más frecuentes

Error Síntoma Causa Solución
Reasignar final error: cannot assign a value to final variable X Intentaste X = ...; después de inicializar No reasignes; declara variable normal si necesitas cambio
Olvidar static en constantes compartidas Cada objeto tiene su copia; más memoria final double PI en lugar de static final double PI Añade static si el valor es común a toda la clase
No seguir UPPER_SNAKE_CASE Revisión de código rechazada Llamar ivaGeneral a una constante Renombra a IVA_GENERAL
Pensar que final hace inmutable el objeto Bug: lista "constante" cambia de contenido Confundir referencia final con objeto inmutable Usar List.of(...) u objetos inmutables
Magic numbers olvidados if (intentos > 3) sigue apareciendo en código Falta de refactorización Extraer a constante con nombre descriptivo

final en variables locales: ¿siempre?

Hay una corriente de opinión en la comunidad Java que defiende poner final a todas las variables locales que no van a cambiar. La idea es documentar la intención: "esta variable es un valor fijo, no la voy a reasignar". En la práctica, es muy verbose y la mayoría de equipos no lo exigen. IntelliJ tiene una inspección que te avisa si una variable podría ser final, pero no la activan por defecto.

1
2
3
4
5
6
7
8
9
// Estilo "todo final" (poco común, muy verbose)
final int edad = 18;
final double precio = 100.0;
final double total = precio * (1 + IVA_GENERAL);

// Estilo "final solo en constantes reales" (lo estándar)
int edad = 18;                                  // variable
double precio = 100.0;                          // variable
final double TOTAL_CON_IVA = precio * (1 + IVA_GENERAL);  // constante

Pista

Recomendación, usa final solo cuando declares una constante real (que conceptualmente no cambia). No lo añadas a variables locales normales aunque no las vayas a reasignar; sobra ruido. El equipo te lo agradecerá en revisiones de código.

Buenas prácticas resumidas

  1. Elimina magic numbers: cualquier número que no sea 0, 1 o evidente por el contexto debe tener nombre. Cero tolerancia con if (edad >= 18).
  2. Declara constantes de clase como private static final por defecto. Solo súbelas a public si son parte intencional de la API de tu clase.
  3. Usa UPPER_SNAKE_CASE para todas las constantes (IVA_GENERAL, MAX_INTENTOS_LOGIN). Es convención universal.
  4. Agrupa constantes relacionadas: si tienes varias constantes de configuración, ponlas en una clase Configuracion o Constantes para tenerlas localizadas.
  5. Documenta las constantes con Javadoc si su significado no es obvio: qué unidades tienen, qué rango es válido, dónde se configuran.
  6. Para conjuntos cerrados de valores, usa enum, no constantes int. Es más seguro, más legible y el IDE te ayuda más.
  7. Recuerda la diferencia referencia vs. contenido al usar final con objetos. Si necesitas inmutabilidad real, usa clases inmutables o List.of(...).
  8. No abuses de final en locales: añade ruido sin aportar valor. Resérvalo para constantes reales.

Ejemplo integrador

Para cerrar la sección, un ejemplo que aplica todas las buenas prácticas:

package dam.programacion.ud1;

/**
 * Configuración global de la aplicación de gestión académica.
 * Todas las constantes son inmutables y de clase (static final).
 */
public final class Configuracion {

    /** Porcentaje de IVA general aplicable a matrículas. */
    public static final double IVA_GENERAL = 0.21;

    /** Edad legal mínima para ser mayor de edad en España. */
    public static final int MAYORIA_EDAD = 18;

    /** Número máximo de intentos de login antes de bloquear la cuenta. */
    public static final int MAX_INTENTOS_LOGIN = 3;

    /** Duración máxima de una sesión inactiva, en segundos. */
    public static final int TIMEOUT_SESION_SEGUNDOS = 1800;   // 30 minutos

    /** Lista inmutable de colores primarios para la interfaz. */
    public static final List<String> COLORES_PRIMARIOS =
            List.of("rojo", "verde", "azul");

    /** Días de la semana lectivos. */
    public enum DiaLectivo {
        LUNES, MARTES, MIERCOLES, JUEVES, VIERNES
    }

    // Constructor privado: esta clase no debe instanciarse
    private Configuracion() {}
}

Fíjate en los detalles profesionales:

  • Todas las constantes son public static final con UPPER_SNAKE_CASE.
  • Cada una tiene Javadoc explicando unidades y significado.
  • La lista COLORES_PRIMARIOS es inmutable de verdad (List.of).
  • Los días lectivos son un enum, no constantes int.
  • El constructor es private para impedir instanciar la clase (es una clase de utilidad, solo tiene constantes).

Ejercicios propuestos

Ejercicio 6.1 (magic numbers). El siguiente código contiene tres magic numbers. Refactóralo declarando constantes con static final y UPPER_SNAKE_CASE:

public class Precios {
    public static void main(String[] args) {
        double precio = 100.0;
        precio = precio * 1.21;       // IVA
        if (precio > 500) {            // umbral de envío gratis
            System.out.println("Envío gratis");
        }
        // límite de artículos por carrito: 50
    }
}

Ejercicio 6.2 (final en referencias). Predice qué pasa sin ejecutarlo y comprueba:

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        final List<String> lista = new ArrayList<>();
        lista.add("hola");                    // ¿compila?
        lista = new ArrayList<>();            // ¿compila?
    }
}

Ejercicio 6.3 (enum). Declara un enum llamado EstadoPedido con los valores PENDIENTE, PAGADO, ENVIADO, ENTREGADO, CANCELADO. Luego escribe un switch que imprima un mensaje distinto para cada estado.

Ejercicio 6.4 (inmutabilidad real). Declara una lista inmutable de tres colores primarios usando List.of y demuestra que cualquier intento de modificarla lanza UnsupportedOperationException.

Ejercicio 6.5 (final en campos). Crea una clase Circulo con un campo final double radio inicializado en el constructor. Verifica que no puedes reasignar radio después de la construcción, pero sí puedes llamar a métodos de la clase.


Resumen de la sección 6

Antes de pasar a la siguiente sección, asegúrate de que controlas estos puntos:

  1. Un magic number es un literal numérico sin nombre en el código. Es la fuente número 1 de bugs de mantenimiento. La solución es declarar el valor como constante con nombre descriptivo.
  2. final es el modificador que "congela" una variable: una vez asignada, no puede reasignarse. Funciona en locales, campos y parámetros.
  3. static final declara constantes de clase: una sola copia compartida por toda la clase, accesible con NombreClase.CONSTANTE. Es el estándar para constantes en Java.
  4. Las constantes siguen la convención UPPER_SNAKE_CASE: IVA_GENERAL, MAX_INTENTOS_LOGIN. Es universal y ayuda a distinguirlas de variables en el código.
  5. Para conjuntos cerrados de valores (días de la semana, estados de un pedido, palos de baraja), usa enum en lugar de constantes int: es más seguro, más legible y el IDE autocompleta los valores.
  6. final en referencias a objetos congela la referencia, no el contenido. final List<String> no es una lista inmutable: puedes add, remove, clear. Para inmutabilidad real, usa List.of(...).
  7. Buenas prácticas: declarar private static final por defecto, agrupar constantes en clases Configuracion, documentar con Javadoc, no abusar de final en locales.