Java

Tutorial Java: Variables

Java variables: declaración y ejemplos. Aprende a declarar y usar variables en Java con ejemplos prácticos y detallados.

Aprende Java y certifícate

Declaración de variables

En Java, las variables son espacios de memoria que se utilizan para almacenar datos durante la ejecución de un programa. Antes de poder utilizar una variable, es necesario declararla, lo que implica especificar su tipo de dato y asignarle un nombre identificador.

La sintaxis básica para declarar una variable en Java es:

tipo nombreVariable;

Donde tipo representa el tipo de dato que almacenará la variable y nombreVariable es el identificador que se utilizará para referenciarla en el código.

Tipos de datos para variables

En Java se pueden declarar variables de diferentes tipos de datos, que se clasifican principalmente en dos categorías:

  • Tipos primitivos: Almacenan valores simples y están predefinidos en el lenguaje
  • Tipos de referencia: Almacenan referencias a objetos

Para los tipos primitivos, se puede declarar variables de la siguiente manera:

int edad;                // Declara una variable entera
double salario;          // Declara una variable de punto flotante
boolean activo;          // Declara una variable booleana
char inicial;            // Declara una variable de carácter
byte pequeño;            // Declara una variable byte
short corto;             // Declara una variable short
long grande;             // Declara una variable long
float decimal;           // Declara una variable float

Para los tipos de referencia, la declaración sigue el mismo patrón:

String nombre;           // Declara una variable para cadenas de texto
Integer numero;          // Declara una variable de la clase envoltorio Integer
ArrayList<String> lista; // Declara una variable para una lista de cadenas

Declaración múltiple de variables

Se pueden declarar varias variables del mismo tipo en una sola línea, separándolas por comas:

int x, y, z;             // Declara tres variables enteras
String nombre, apellido; // Declara dos variables de tipo String

Declaración con inferencia de tipos

Desde Java 10, se puede utilizar la palabra clave var para que el compilador infiera el tipo de la variable a partir del valor asignado. Sin embargo, esto solo funciona con variables locales y requiere inicialización inmediata:

var contador = 0;        // El compilador infiere que contador es de tipo int
var mensaje = "Hola";    // El compilador infiere que mensaje es de tipo String
var lista = new ArrayList<String>(); // Infiere ArrayList<String>

Es importante destacar que el uso de var no convierte a Java en un lenguaje de tipado dinámico. El tipo se sigue determinando en tiempo de compilación y no puede cambiar durante la ejecución.

Ámbito de las variables declaradas

El ámbito o scope de una variable determina dónde es accesible dentro del código. En Java, las variables pueden tener diferentes ámbitos:

public class EjemploAmbito {
    int variableInstancia;  // Variable de instancia, accesible en toda la clase
    
    public void metodo() {
        int variableLocal;  // Variable local, solo accesible dentro de este método
        
        if (true) {
            int variableBloque;  // Variable de bloque, solo accesible dentro del if
        }
        // variableBloque no es accesible aquí
    }
    // variableLocal no es accesible aquí
}

Consideraciones importantes

Al declarar variables en Java, se deben tener en cuenta algunas consideraciones:

  • Las variables deben declararse antes de usarse.
  • El nombre de la variable debe ser un identificador válido en Java.
  • Se recomienda que el nombre sea descriptivo y siga las convenciones de nomenclatura.
  • Las variables locales no se inicializan automáticamente, a diferencia de las variables de instancia.
public class EjemploInicializacion {
    int variableInstancia;  // Se inicializa automáticamente a 0
    
    public void metodo() {
        int variableLocal;  // No se inicializa automáticamente
        // System.out.println(variableLocal); // Error: variable no inicializada
        
        variableLocal = 10; // Ahora sí se puede usar
        System.out.println(variableLocal);
    }
}

Declaración de variables en bucles y estructuras de control

Es común declarar variables dentro de estructuras de control como bucles for o bloques try-catch:

// Declaración en bucle for
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}
// La variable i no es accesible fuera del bucle

// Declaración en try-with-resources (desde Java 7)
try (var archivo = new FileInputStream("datos.txt")) {
    // Código que usa el archivo
} catch (IOException e) {
    System.err.println("Error: " + e.getMessage());
}
// La variable archivo se cierra automáticamente y no es accesible aquí

Inicialización de variables

La inicialización de variables es el proceso mediante el cual se asigna un valor inicial a una variable después de su declaración. En Java, esta operación es fundamental para garantizar que las variables contengan valores válidos antes de ser utilizadas en operaciones o expresiones.

Sintaxis básica de inicialización

Existen dos formas principales de inicializar variables en Java:

// Declaración e inicialización separadas
tipo nombreVariable;
nombreVariable = valor;

// Declaración e inicialización en una sola línea
tipo nombreVariable = valor;

La segunda forma es más concisa y se utiliza con mayor frecuencia en el código Java moderno.

Inicialización de tipos primitivos

Los tipos primitivos se pueden inicializar con valores literales que correspondan a su tipo:

int contador = 0;
double precio = 29.99;
char letra = 'A';
boolean activo = true;
byte pequeño = 127;
short medio = 32000;
long grande = 9223372036854775807L; // La 'L' indica literal de tipo long
float decimal = 3.14f;              // La 'f' indica literal de tipo float

Es importante notar que para los literales long se debe añadir el sufijo L y para los float el sufijo f para distinguirlos de los tipos int y double respectivamente.

Inicialización de tipos de referencia

Los tipos de referencia generalmente se inicializan mediante el operador new que crea una nueva instancia del objeto:

String mensaje = "Hola mundo";           // Inicialización especial para String
String otraMensaje = new String("Hola"); // Forma alternativa menos común

ArrayList<Integer> numeros = new ArrayList<>(); // Inicialización con diamante vacío
Date fechaActual = new Date();                  // Crea objeto con la fecha actual

Para String, Java proporciona una sintaxis especial que permite inicializar directamente con literales de cadena entre comillas dobles, sin necesidad de usar new.

Inicialización con valores por defecto

Las variables de instancia (campos de clase) y las variables estáticas se inicializan automáticamente con valores por defecto si no se especifica un valor inicial:

public class EjemploValoresPorDefecto {
    int numero;            // Se inicializa a 0
    double decimal;        // Se inicializa a 0.0
    boolean condicion;     // Se inicializa a false
    char caracter;         // Se inicializa a '\u0000' (carácter nulo)
    String texto;          // Se inicializa a null
    Object objeto;         // Se inicializa a null
    
    public void mostrarValores() {
        System.out.println("Número: " + numero);
        System.out.println("Decimal: " + decimal);
        System.out.println("Condición: " + condicion);
        System.out.println("Carácter: " + (int)caracter); // Muestra el valor ASCII
        System.out.println("Texto: " + texto);
        System.out.println("Objeto: " + objeto);
    }
}

Sin embargo, las variables locales (declaradas dentro de métodos) deben inicializarse explícitamente antes de usarse, ya que no reciben valores por defecto:

public void metodoEjemplo() {
    int contador;
    // System.out.println(contador); // Error de compilación: variable no inicializada
    
    contador = 10; // Inicialización explícita
    System.out.println(contador); // Ahora funciona correctamente
}

Inicialización en bloque

Para inicializaciones más complejas, se pueden utilizar bloques de inicialización:

public class EjemploInicializacionBloque {
    private final List<String> elementos;
    
    {
        // Bloque de inicialización de instancia
        elementos = new ArrayList<>();
        elementos.add("Elemento 1");
        elementos.add("Elemento 2");
        elementos.add("Elemento 3");
    }
    
    // Constructor que aprovecha la inicialización en bloque
    public EjemploInicializacionBloque() {
        System.out.println("Constructor llamado con elementos ya inicializados");
    }
}

También existen bloques de inicialización estáticos para variables de clase:

public class ConfiguracionApp {
    private static final Map<String, String> CONFIG;
    
    static {
        // Bloque de inicialización estática
        CONFIG = new HashMap<>();
        CONFIG.put("timeout", "30");
        CONFIG.put("maxConnections", "100");
        CONFIG.put("debugMode", "false");
        
        // Se podría cargar desde un archivo o base de datos
        try {
            // Código de carga de configuración
            System.out.println("Configuración cargada");
        } catch (Exception e) {
            System.err.println("Error al cargar configuración");
        }
    }
}

Inicialización con expresiones

Las variables pueden inicializarse con expresiones que se evalúan en tiempo de ejecución:

int a = 5;
int b = 10;
int suma = a + b;                  // Inicialización con una expresión aritmética
int max = (a > b) ? a : b;         // Inicialización con operador ternario
String nombre = obtenerNombre();   // Inicialización con el resultado de un método
double aleatorio = Math.random();  // Inicialización con método de la biblioteca estándar

Inicialización de arrays

Los arrays en Java tienen una sintaxis especial para su inicialización:

// Declaración e inicialización en una línea
int[] numeros = {1, 2, 3, 4, 5};

// Declaración e inicialización separadas
String[] nombres;
nombres = new String[] {"Ana", "Juan", "Carlos"};

// Inicialización con tamaño específico (elementos con valores por defecto)
double[] precios = new double[5]; // Crea array de 5 elementos inicializados a 0.0

// Inicialización con bucle
char[] letras = new char[26];
for (int i = 0; i < letras.length; i++) {
    letras[i] = (char)('A' + i);
}

Inicialización con inferencia de tipos

Con la inferencia de tipos mediante var, la inicialización es obligatoria:

var contador = 0;                     // Infiere tipo int
var mensaje = "Hola";                 // Infiere tipo String
var numeros = new int[] {1, 2, 3};    // Infiere array de int
var lista = new ArrayList<String>();  // Infiere ArrayList<String>

// No es válido:
// var numero; // Error: no se puede inferir el tipo sin inicialización

Inicialización perezosa (lazy initialization)

En algunos casos, se prefiere retrasar la inicialización hasta que realmente se necesite el valor, especialmente para recursos costosos:

public class ServicioCostoso {
    private ExpensiveResource recurso;
    
    public ExpensiveResource getRecurso() {
        if (recurso == null) {
            recurso = new ExpensiveResource(); // Inicialización perezosa
        }
        return recurso;
    }
}

Este patrón es común en aplicaciones donde se busca optimizar el rendimiento, aunque en entornos multihilo puede requerir sincronización adicional.

Buenas prácticas de inicialización

  • Inicializar variables lo más cerca posible de su primer uso.
  • Preferir la inicialización en la misma línea de la declaración cuando sea posible.
  • Utilizar valores significativos para la inicialización, evitando valores "mágicos".
  • Para constantes, utilizar nombres en mayúsculas y valores claros.
  • Considerar la inmutabilidad cuando sea apropiado, usando final para variables que no cambiarán.
// Ejemplo de buenas prácticas
final int MAX_INTENTOS = 3;
final String PREFIJO_CODIGO = "USR";
int contador = 0;
String nombreUsuario = obtenerNombreDeBaseDeDatos();

Reglas y convenciones en Java para nombrar variables

En Java, el nombrado de variables sigue un conjunto de reglas sintácticas obligatorias y convenciones de estilo recomendadas.

Reglas sintácticas obligatorias

Las reglas que deben cumplirse para que el código compile correctamente son:

  • Los nombres de variables pueden contener letras, dígitos, el símbolo de dólar ($) y el guion bajo (_).
  • Deben comenzar con una letra, el símbolo de dólar o el guion bajo, nunca con un dígito.
  • No pueden ser palabras reservadas de Java (como if, class, while, etc.).
  • Java distingue entre mayúsculas y minúsculas (case-sensitive), por lo que contador y Contador serían variables diferentes.
  • No hay límite práctico para la longitud del nombre, aunque se recomienda que sean concisos.
// Nombres válidos de variables
int edad;
double $precio;
String _nombre;
boolean esActivo;
long número123; // Los caracteres Unicode son válidos, pero no recomendados

// Nombres no válidos (errores de compilación)
int 1numero;    // No puede comenzar con un dígito
float if;       // No puede ser una palabra reservada
char class;     // No puede ser una palabra reservada

Convenciones de estilo recomendadas

Aunque no son obligatorias para la compilación, estas convenciones son ampliamente seguidas en la comunidad Java:

Notación camelCase

Para variables y métodos, se utiliza la notación camelCase: la primera palabra en minúsculas y cada palabra adicional comienza con mayúscula:

int edadUsuario;
String nombreCompleto;
boolean estaActivo;
double tasaInteresAnual;

Nombres descriptivos y significativos

Los nombres deben ser autoexplicativos, indicando claramente el propósito de la variable:

// Preferir esto:
int edadEmpleado;
double precioTotal;
boolean estaCompletado;

// Evitar esto:
int e;
double x;
boolean flag;

Abreviaturas y acrónimos

Las abreviaturas y acrónimos generalmente se tratan como palabras normales en camelCase, excepto cuando aparecen al inicio:

String url;                  // No URL (cuando está al inicio)
String nombreHtml;           // No nombreHTML
HttpServletRequest request;  // Excepción común para tipos bien conocidos
int identificadorId;         // Redundante, mejor usar solo "identificador"

Prefijos y sufijos

Se evita el uso de notación húngara (prefijos que indican el tipo) y otros prefijos como:

// Evitar esto:
int iEdad;        // Prefijo de tipo
String strNombre; // Prefijo de tipo
boolean bActivo;  // Prefijo de tipo

// Preferir esto:
int edad;
String nombre;
boolean activo;

Variables de un solo uso o temporales

Para variables de ámbito muy limitado o índices de bucles, se aceptan nombres cortos:

for (int i = 0; i < 10; i++) {
    // 'i' es aceptable como índice de bucle
}

int x = punto.getX(); // 'x' es claro en este contexto

Constantes

Las constantes (variables static final) se escriben en mayúsculas con palabras separadas por guiones bajos:

public static final int MAX_USUARIOS = 100;
public static final String PREFIJO_CODIGO = "USR";
public static final double PI = 3.14159265359;

Variables booleanas

Para variables booleanas, se recomienda usar prefijos como es, tiene, puede o similares:

boolean esValido;
boolean tienePermisos;
boolean puedeEditar;
boolean debeActualizar;

Casos especiales

Variables de instancia (campos)

Aunque siguen las mismas convenciones que las variables locales, a veces se les añade un guion bajo al final para distinguirlas de parámetros con el mismo nombre:

public class Usuario {
    private String nombre_;  // Campo de instancia
    
    public void setNombre(String nombre) {
        this.nombre_ = nombre;  // Distingue el parámetro del campo
    }
}

Sin embargo, la práctica más común es usar this para referirse a los campos:

public class Usuario {
    private String nombre;  // Campo de instancia
    
    public void setNombre(String nombre) {
        this.nombre = nombre;  // Uso de 'this' para distinguir
    }
}

Parámetros de métodos

Siguen las mismas convenciones que las variables locales:

public void procesarPago(double monto, String moneda, boolean esUrgente) {
    // Implementación
}

Prácticas a evitar

  • Nombres de una sola letra (excepto para índices de bucles o coordenadas).
  • Nombres muy similares que pueden causar confusión.
  • Nombres engañosos que no reflejan el propósito real de la variable.
  • Nombres excesivamente largos que dificultan la lectura.
// Evitar esto:
int a;                          // Poco descriptivo
int longitudDeNombreDeVariable; // Excesivamente largo
int longitud1, longitud2;       // Nombres muy similares
int size;                       // Engañoso si no es realmente un tamaño

Herramientas de verificación

Existen herramientas como Checkstyle, PMD y SonarQube que pueden configurarse para verificar automáticamente que el código siga estas convenciones:

<!-- Ejemplo de configuración de Checkstyle para verificar nombres de variables -->
<module name="LocalVariableName">
    <property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
</module>

Variables locales y de instancia

En Java, las variables se clasifican en diferentes categorías según su ámbito y ciclo de vida. Dos de las categorías más importantes son las variables locales y las variables de instancia, cada una con características y usos específicos.

Variables locales

Las variables locales son aquellas que se declaran dentro de un método, constructor o bloque de código. Sus características principales son:

  • Se crean cuando se ejecuta el bloque donde están declaradas.
  • Se destruyen cuando finaliza la ejecución del bloque.
  • No son accesibles desde fuera del bloque donde se declaran.
  • No tienen un valor por defecto, deben inicializarse explícitamente antes de usarse.
  • No pueden tener modificadores de acceso (public, private, etc.).
  • Pueden declararse con el modificador final para crear constantes locales.
public void calcularTotal() {
    int cantidad = 5;                // Variable local
    double precioUnitario = 19.99;   // Variable local
    final double TASA_IMPUESTO = 0.21; // Constante local (final)
    
    double subtotal = cantidad * precioUnitario;
    double impuesto = subtotal * TASA_IMPUESTO;
    double total = subtotal + impuesto;
    
    System.out.println("Total: " + total);
} // Aquí todas las variables locales dejan de existir

Las variables locales también incluyen los parámetros de método, que son variables especiales inicializadas con los valores pasados al método cuando se invoca:

public double calcularArea(double longitud, double anchura) {
    // longitud y anchura son variables locales (parámetros)
    return longitud * anchura;
}

Variables de instancia

Las variables de instancia, también llamadas campos o atributos, se declaran dentro de una clase pero fuera de cualquier método. Sus características principales son:

  • Se crean cuando se instancia un objeto de la clase.
  • Se destruyen cuando el objeto es eliminado por el recolector de basura.
  • Cada objeto tiene su propia copia de las variables de instancia.
  • Tienen valores por defecto si no se inicializan explícitamente.
  • Pueden tener modificadores de acceso (public, private, protected).
  • Pueden declararse como final para crear constantes de instancia.
  • Pueden declararse como static para convertirlas en variables de clase.
public class Producto {
    // Variables de instancia
    private String nombre;           // Por defecto: null
    private double precio;           // Por defecto: 0.0
    private int stock;               // Por defecto: 0
    private final String codigo;     // Constante de instancia, debe inicializarse
    
    public Producto(String nombre, double precio, String codigo) {
        this.nombre = nombre;
        this.precio = precio;
        this.codigo = codigo;        // Inicialización de constante de instancia
    }
    
    public void mostrarInfo() {
        System.out.println("Producto: " + nombre);
        System.out.println("Precio: " + precio);
        System.out.println("Stock: " + stock);
        System.out.println("Código: " + codigo);
    }
}

Diferencias clave entre variables locales y de instancia

Característica Variables locales Variables de instancia
Ámbito Limitado al bloque donde se declaran Accesibles en toda la clase
Ciclo de vida Temporal (durante la ejecución del bloque) Ligado al objeto (hasta que se destruye)
Inicialización Obligatoria antes de usar Automática con valores por defecto
Modificadores de acceso No permitidos Permitidos (public, private, etc.)
Valor por defecto No tienen Sí tienen (0, false, null, etc.)
Visibilidad Solo en el bloque donde se declaran Según el modificador de acceso

Sombreado de variables (Variable Shadowing)

Cuando una variable local tiene el mismo nombre que una variable de instancia, la variable local "sombrea" o "oculta" a la variable de instancia en ese ámbito:

public class Empleado {
    private String nombre;  // Variable de instancia
    
    public void setNombre(String nombre) {  // Parámetro con el mismo nombre
        // Aquí, 'nombre' se refiere al parámetro, no a la variable de instancia
        this.nombre = nombre;  // 'this' se usa para acceder a la variable de instancia
    }
    
    public void ejemploSombreado() {
        int edad = 30;  // Variable local
        
        if (edad > 18) {
            String nombre = "Anónimo";  // Variable local que sombrea a la de instancia
            System.out.println(nombre);  // Imprime "Anónimo"
        }
        
        System.out.println(this.nombre);  // Accede a la variable de instancia
    }
}

El sombreado puede causar confusión y errores, por lo que se recomienda evitarlo cuando sea posible, excepto en los casos comunes como los parámetros de constructores y métodos setter.

Variables estáticas (de clase)

Además de las variables locales y de instancia, existe un tercer tipo importante: las variables estáticas o variables de clase. Se declaran con el modificador static:

public class Contador {
    private static int total = 0;  // Variable estática (de clase)
    private int valor;             // Variable de instancia
    
    public Contador() {
        valor = 0;
        total++;  // Incrementa el contador de instancias
    }
    
    public static int getTotal() {
        return total;  // Acceso a variable estática
    }
}

Las variables estáticas pertenecen a la clase en lugar de a instancias individuales, por lo que:

  • Solo existe una copia de cada variable estática, compartida por todas las instancias.
  • Se pueden acceder incluso sin crear instancias de la clase.
  • Se inicializan cuando la clase se carga en memoria.
  • Suelen utilizarse para constantes, contadores globales o recursos compartidos.

Buenas prácticas

  • Minimizar el ámbito: Declarar variables en el ámbito más pequeño posible.
  • Encapsulación: Hacer las variables de instancia private y proporcionar métodos de acceso.
  • Inicialización temprana: Inicializar variables lo más cerca posible de su declaración.
  • Evitar variables globales: Preferir parámetros y valores de retorno para comunicar datos.
  • Usar nombres descriptivos: Especialmente importante para variables con ámbitos amplios.
public class EjemploBuenasPracticas {
    // Variables de instancia encapsuladas
    private String nombre;
    private int edad;
    
    // Constructor que inicializa todas las variables
    public EjemploBuenasPracticas(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }
    
    public void procesarDatos() {
        // Variable local con ámbito limitado
        boolean datosValidos = validar();
        
        if (datosValidos) {
            // Variables locales solo usadas en este bloque
            String mensajeExito = "Procesamiento completado para: " + nombre;
            System.out.println(mensajeExito);
        }
    }
    
    private boolean validar() {
        return edad >= 18;
    }
}

Variables finales constantes

En Java, las variables finales o constantes son aquellas cuyo valor no puede cambiar una vez asignado y se declaran utilizando el modificador final.

Declaración de variables finales

La sintaxis básica para declarar una variable final es:

final tipo NOMBRE_VARIABLE = valor;

Por convención, las constantes se nombran en mayúsculas con palabras separadas por guiones bajos, aunque esto es obligatorio solo para constantes estáticas.

Tipos de variables finales

Dependiendo de su ámbito, las variables finales pueden ser:

Constantes locales

Se declaran dentro de métodos o bloques:

public void calcularImpuesto(double precio) {
    final double TASA_IVA = 0.21;
    double impuesto = precio * TASA_IVA;
    System.out.println("Impuesto: " + impuesto);
}

Estas constantes son útiles cuando se necesita un valor que no cambiará dentro del método, pero que no tiene sentido definir a nivel de clase.

Constantes de instancia

Son variables de instancia declaradas como final:

public class Persona {
    private final String dni;
    private String nombre;
    private int edad;
    
    public Persona(String dni, String nombre, int edad) {
        this.dni = dni;        // Debe inicializarse en el constructor
        this.nombre = nombre;
        this.edad = edad;
    }
    
    // No puede existir un método que modifique dni
    // public void setDni(String dni) { this.dni = dni; } // Error de compilación
}

Las constantes de instancia deben inicializarse:

  • En la declaración
  • En un bloque de inicialización de instancia
  • En todos los constructores

Una vez inicializadas, no pueden modificarse durante toda la vida del objeto.

Constantes estáticas (de clase)

Son las constantes más comunes en Java, declaradas como static final:

public class Configuracion {
    public static final int MAX_CONEXIONES = 100;
    public static final String VERSION = "1.0.3";
    public static final double PI = 3.14159265359;
    
    // Para constantes complejas, se puede usar un bloque estático
    public static final List<String> PAISES_SOPORTADOS;
    static {
        List<String> paises = new ArrayList<>();
        paises.add("España");
        paises.add("Francia");
        paises.add("Portugal");
        paises.add("Italia");
        PAISES_SOPORTADOS = Collections.unmodifiableList(paises);
    }
}

Estas constantes:

  • Se inicializan cuando la clase se carga en memoria
  • Son compartidas por todas las instancias de la clase
  • Se acceden directamente a través del nombre de la clase
  • Son ideales para valores que son verdaderamente constantes en toda la aplicación

Parámetros finales

Los parámetros de métodos también pueden declararse como final:

public void procesarDatos(final String datos, final int maxIntentos) {
    // datos = "Nuevos datos"; // Error de compilación
    // maxIntentos = 5;        // Error de compilación
    
    // Uso normal de los parámetros
    System.out.println("Procesando: " + datos);
}

Esto garantiza que los parámetros no se modificarán dentro del método, lo que puede ser útil para:

  • Evitar errores accidentales
  • Mejorar la legibilidad del código
  • Facilitar la programación concurrente

Referencias finales vs. objetos inmutables

Es importante entender que final solo hace que la referencia sea constante, no el objeto al que apunta:

final List<String> nombres = new ArrayList<>();
// nombres = new ArrayList<>();  // Error: no se puede reasignar la referencia

// Pero sí se puede modificar el contenido del objeto
nombres.add("Ana");  // Esto es válido
nombres.add("Juan"); // Esto también es válido

Para crear objetos verdaderamente inmutables, se deben tomar medidas adicionales:

// Creación de una lista inmutable
final List<String> COLORES = List.of("Rojo", "Verde", "Azul");
// COLORES.add("Amarillo"); // Lanza UnsupportedOperationException en tiempo de ejecución

// Alternativa con Collections
final List<String> DIAS = Collections.unmodifiableList(
    Arrays.asList("Lunes", "Martes", "Miércoles", "Jueves", "Viernes")
);

Ventajas de usar variables finales

  • Seguridad: Previene modificaciones accidentales de valores que deberían ser constantes.
  • Claridad: Comunica la intención de que el valor no debe cambiar.
  • Optimización: Permite al compilador realizar ciertas optimizaciones.
  • Concurrencia: Facilita la programación concurrente segura.
  • Diseño: Fomenta la inmutabilidad, un principio de diseño valioso.

Constantes en interfaces

Las interfaces pueden contener constantes, que son implícitamente public static final:

public interface ConfiguracionSistema {
    int TIMEOUT_CONEXION = 30000;  // Implícitamente public static final
    String SERVIDOR_PRINCIPAL = "srv01.ejemplo.com";
    double VERSION_API = 2.1;
}

Estas constantes se pueden acceder directamente a través del nombre de la interfaz:

int timeout = ConfiguracionSistema.TIMEOUT_CONEXION;

Sin embargo, este uso ha sido parcialmente reemplazado por clases de utilidad o enumeraciones en el código Java moderno.

Enumeraciones como alternativa a constantes

Para conjuntos de constantes relacionadas, las enumeraciones (enum) ofrecen una alternativa más robusta:

// En lugar de:
public class EstadoPedido {
    public static final int PENDIENTE = 0;
    public static final int PROCESANDO = 1;
    public static final int ENVIADO = 2;
    public static final int ENTREGADO = 3;
}

// Mejor usar:
public enum EstadoPedido {
    PENDIENTE, PROCESANDO, ENVIADO, ENTREGADO
}

Las enumeraciones proporcionan:

  • Seguridad de tipo (type-safety)
  • Métodos y campos adicionales
  • Integración con switch
  • Prevención de valores inválidos
public enum DiaSemana {
    LUNES(true), MARTES(true), MIERCOLES(true), JUEVES(true), 
    VIERNES(true), SABADO(false), DOMINGO(false);
    
    private final boolean diaLaboral;
    
    DiaSemana(boolean diaLaboral) {
        this.diaLaboral = diaLaboral;
    }
    
    public boolean esDiaLaboral() {
        return diaLaboral;
    }
}

Buenas prácticas con variables finales

  • Usar final liberalmente: Declarar como final cualquier variable que no necesite cambiar.
  • Preferir constantes a literales: Evitar "números mágicos" o cadenas literales en el código.
  • Agrupar constantes relacionadas: Usar clases de utilidad o enumeraciones para organizar constantes.
  • Nombrar claramente: Usar nombres descriptivos en mayúsculas para constantes estáticas.
  • Considerar la inmutabilidad: Para referencias a objetos, considerar hacer inmutable el objeto referenciado.
public class ConfiguracionApp {
    // Agrupación de constantes relacionadas
    public static final class Tiempos {
        public static final int TIMEOUT_CONEXION = 30000;
        public static final int INTERVALO_REINTENTO = 5000;
        public static final int MAX_TIEMPO_ESPERA = 120000;
        
        private Tiempos() {} // Constructor privado para evitar instanciación
    }
    
    public static final class Rutas {
        public static final String DIRECTORIO_DATOS = "/var/data/";
        public static final String ARCHIVO_CONFIG = "config.json";
        
        private Rutas() {}
    }
}

// Uso:
int timeout = ConfiguracionApp.Tiempos.TIMEOUT_CONEXION;

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Java online

Ejercicios de esta lección Variables

Evalúa tus conocimientos de esta lección Variables con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Clases abstractas

Test

Streams: reduce()

Test

Streams: flatMap()

Test

Llamada y sobrecarga de funciones

Puzzle

Métodos referenciados

Test

Métodos de la clase String

Código

Representación de Fecha

Puzzle

Operadores lógicos

Test

Tipos de datos

Código

Estructuras de iteración

Puzzle

Streams: forEach()

Test

Objetos

Puzzle

Funciones lambda

Test

Uso de Scanner

Puzzle

CRUD en Java de modelo Customer sobre un ArrayList

Proyecto

Tipos de variables

Puzzle

Streams: collect()

Puzzle

Operadores aritméticos

Puzzle

Interfaz funcional Consumer

Test

API java.nio 2

Puzzle

API Optional

Test

Interfaz funcional Function

Test

Encapsulación

Test

Interfaces

Código

Uso de API Optional

Puzzle

Representación de Hora

Test

Herencia básica

Test

Clases y objetos

Código

Interfaz funcional Supplier

Puzzle

HashMap

Puzzle

Sobrecarga de métodos

Test

Polimorfismo de tiempo de ejecución

Puzzle

OOP en Java

Proyecto

Creación de Streams

Test

Streams: min max

Puzzle

Métodos avanzados de la clase String

Puzzle

Polimorfismo de tiempo de compilación

Test

Excepciones

Puzzle

Herencia avanzada

Puzzle

Estructuras de selección

Test

Uso de interfaces

Test

HashSet

Test

Objeto Scanner

Test

Streams: filter()

Puzzle

Operaciones de Streams

Puzzle

Interfaz funcional Predicate

Puzzle

Streams: sorted()

Test

Configuración de entorno

Test

CRUD en Java de modelo Customer sobre un HashMap

Proyecto

Uso de variables

Test

Clases

Test

Streams: distinct()

Puzzle

Streams: count()

Test

ArrayList

Test

Datos de referencia

Test

Interfaces funcionales

Puzzle

Métodos básicos de la clase String

Test

Instalación

Test

Funciones

Código

Estructuras de control

Código

Herencia de clases

Código

Streams: map()

Puzzle

Funciones y encapsulamiento

Test

Streams: match

Test

Gestión de errores y excepciones

Código

Datos primitivos

Puzzle

Todas las lecciones de Java

Accede a todas las lecciones de Java y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Instalación De Java

Introducción Y Entorno

Configuración De Entorno Java

Introducción Y Entorno

Ecosistema Jakarta Ee De Java

Introducción Y Entorno

Tipos De Datos

Sintaxis

Variables

Sintaxis

Operadores

Sintaxis

Estructuras De Control

Sintaxis

Funciones

Sintaxis

Excepciones

Programación Orientada A Objetos

Clases Y Objetos

Programación Orientada A Objetos

Encapsulación

Programación Orientada A Objetos

Herencia

Programación Orientada A Objetos

Clases Abstractas

Programación Orientada A Objetos

Interfaces

Programación Orientada A Objetos

Sobrecarga De Métodos

Programación Orientada A Objetos

Polimorfismo

Programación Orientada A Objetos

La Clase Scanner

Programación Orientada A Objetos

Métodos De La Clase String

Programación Orientada A Objetos

Listas

Framework Collections

Conjuntos

Framework Collections

Mapas

Framework Collections

Funciones Lambda

Programación Funcional

Interfaz Funcional Consumer

Programación Funcional

Interfaz Funcional Predicate

Programación Funcional

Interfaz Funcional Supplier

Programación Funcional

Interfaz Funcional Function

Programación Funcional

Métodos Referenciados

Programación Funcional

Creación De Streams

Programación Funcional

Operaciones Intermedias Con Streams: Map()

Programación Funcional

Operaciones Intermedias Con Streams: Filter()

Programación Funcional

Operaciones Intermedias Con Streams: Distinct()

Programación Funcional

Operaciones Finales Con Streams: Collect()

Programación Funcional

Operaciones Finales Con Streams: Min Max

Programación Funcional

Operaciones Intermedias Con Streams: Flatmap()

Programación Funcional

Operaciones Intermedias Con Streams: Sorted()

Programación Funcional

Operaciones Finales Con Streams: Reduce()

Programación Funcional

Operaciones Finales Con Streams: Foreach()

Programación Funcional

Operaciones Finales Con Streams: Count()

Programación Funcional

Operaciones Finales Con Streams: Match

Programación Funcional

Api Optional

Programación Funcional

Api Java.nio 2

Entrada Y Salida (Io)

Api Java.time

Api Java.time

Accede GRATIS a Java y certifícate

Certificados de superación de Java

Supera todos los ejercicios de programación del curso de Java y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Identificar y declarar tipos de variables en Java
  • Diferenciar entre variables locales, de instancia y estáticas
  • Aplicar la inferencia de tipos en Java desde la versión 10
  • Entender el ámbito y la visibilidad de las variables
  • Aplicar buenas prácticas en la declaración de variables