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.
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:
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:
- Incomprensión: el lector no sabe qué significa el número ni por qué tiene ese valor.
- 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.
- Duplicación: el mismo número aparece varias veces; si cambias unas y olvidas otras, el programa se vuelve inconsistente.
- Búsqueda imposible: buscar
18en un proyecto de 50.000 líneas te da cientos de resultados, la mayoría falsos positivos. BuscarMAYORIA_EDADte 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:
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):
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:
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:
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.
Ejemplos del API de Java
El propio API de Java está lleno de constantes static final que ya has usado sin saberlo:
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 | Sí |
| ¿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:
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:
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
Uso:
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 |
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 constantestatic 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.
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:
-
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, usaList.of(...)oCollections.unmodifiableList(...)(lo verás en UD6). -
Arrays: igual que las listas.
final int[] arr = {1, 2, 3};permitearr[0] = 99;pero noarr = new int[10];. -
Objetos propios: si declaras
final Alumno a = new Alumno("Ana");, no puedes asignara = new Alumno("Ben");, pero sí puedes llamar aa.setNombre("Ben");si la claseAlumnotiene ese método. -
Strings: como
Stringes inmutable por diseño,final Stringsí 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+):
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.
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
- 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). - Declara constantes de clase como
private static finalpor defecto. Solo súbelas apublicsi son parte intencional de la API de tu clase. - Usa UPPER_SNAKE_CASE para todas las constantes (
IVA_GENERAL,MAX_INTENTOS_LOGIN). Es convención universal. - Agrupa constantes relacionadas: si tienes varias constantes de configuración, ponlas en una clase
ConfiguracionoConstantespara tenerlas localizadas. - Documenta las constantes con Javadoc si su significado no es obvio: qué unidades tienen, qué rango es válido, dónde se configuran.
- Para conjuntos cerrados de valores, usa
enum, no constantesint. Es más seguro, más legible y el IDE te ayuda más. - Recuerda la diferencia referencia vs. contenido al usar
finalcon objetos. Si necesitas inmutabilidad real, usa clases inmutables oList.of(...). - No abuses de
finalen 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:
Fíjate en los detalles profesionales:
- Todas las constantes son
public static finalcon UPPER_SNAKE_CASE. - Cada una tiene Javadoc explicando unidades y significado.
- La lista
COLORES_PRIMARIOSes inmutable de verdad (List.of). - Los días lectivos son un
enum, no constantesint. - El constructor es
privatepara 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:
Ejercicio 6.2 (final en referencias). Predice qué pasa sin ejecutarlo y comprueba:
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:
- 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.
finales el modificador que "congela" una variable: una vez asignada, no puede reasignarse. Funciona en locales, campos y parámetros.static finaldeclara constantes de clase: una sola copia compartida por toda la clase, accesible conNombreClase.CONSTANTE. Es el estándar para constantes en Java.- 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. - Para conjuntos cerrados de valores (días de la semana, estados de un pedido, palos de baraja), usa
enumen lugar de constantesint: es más seguro, más legible y el IDE autocompleta los valores. finalen referencias a objetos congela la referencia, no el contenido.final List<String>no es una lista inmutable: puedesadd,remove,clear. Para inmutabilidad real, usaList.of(...).- Buenas prácticas: declarar
private static finalpor defecto, agrupar constantes en clasesConfiguracion, documentar con Javadoc, no abusar definalen locales.