Variables en Java: declaración, tipos y alcance

📅 Actualizado en febrero 2026 ✍️ Ángel López ⏱️ 20 min de lectura ✓ Nivel principiante ★ ★ ★ ★ ★ (5/5)

📦 ¿Qué es una variable en Java?

Una variable en Java es un espacio con nombre en la memoria del ordenador que almacena un valor de un tipo determinado. Puedes imaginarla como una caja etiquetada: la etiqueta es el nombre de la variable, el tamaño de la caja viene definido por el tipo de dato, y dentro se guarda el valor que le asignes.

Java es un lenguaje de tipado estático y fuerte, lo que significa dos cosas fundamentales: primero, toda variable debe declararse con un tipo de dato concreto antes de poder usarla; segundo, el compilador verifica que todas las operaciones y asignaciones sean compatibles con el tipo declarado. Esta característica es una de las razones por las que Java produce código robusto y con menos errores en tiempo de ejecución que los lenguajes de tipado dinámico.

Toda variable en Java se define mediante tres elementos esenciales: un tipo de dato (que determina qué valores puede almacenar y cuánta memoria ocupa), un nombre identificador (que permite referirse a ella en el código) y, opcionalmente, un valor inicial.

Anatomía de una variable en Java
// Tipo   Nombre      Valor inicial
   int    edad    =   25;
   String nombre  =   "María";
   boolean activo =   true;
💡
Concepto clave: En Java, una variable no es el valor en sí, sino una referencia a una posición de memoria donde se almacena ese valor. Para los tipos primitivos, la variable contiene el valor directamente; para los tipos de referencia, contiene la dirección del objeto en el heap.

✏️ Declaración e inicialización de variables

Declarar una variable significa informar al compilador de que existe un nombre asociado a un tipo de dato. Inicializar significa asignarle un valor por primera vez. Ambas operaciones pueden realizarse por separado o en una sola sentencia.

▶️ Declaración sin inicialización

Declaración y asignación separadas
int edad;           // Declaración: reserva espacio para un entero
edad = 25;          // Inicialización: asigna el valor 25

String ciudad;      // Declaración de una referencia a String
ciudad = "Madrid";  // Inicialización con un literal de cadena

▶️ Declaración con inicialización simultánea

Forma compacta (recomendada)
int edad = 25;
double salario = 2850.75;
boolean esActivo = true;
char inicial = 'J';
String nombre = "Ana García";

▶️ Declaración múltiple en una línea

Java permite declarar varias variables del mismo tipo separándolas con comas, aunque se recomienda una variable por línea para mayor legibilidad:

Declaración múltiple
int x = 0, y = 0, z = 0;   // Tres enteros en una línea
String nombre, apellido;     // Dos Strings sin inicializar
Buena práctica: Inicializa las variables lo más cerca posible del punto donde se usan por primera vez. Esto mejora la legibilidad y reduce la posibilidad de errores.

🔢 Tipos de datos primitivos

Java define exactamente ocho tipos primitivos. Son los bloques fundamentales del lenguaje: almacenan valores directamente en la pila (stack), no son objetos, y su tamaño en memoria es fijo e independiente de la plataforma. Esta es una de las garantías de portabilidad de Java.

Tipo Tamaño Rango de valores Valor por defecto Ejemplo
byte 8 bits -128 a 127 0 byte edad = 25;
short 16 bits -32.768 a 32.767 0 short puerto = 8080;
int 32 bits -2.147.483.648 a 2.147.483.647 0 int poblacion = 47000000;
long 64 bits -9,2 × 1018 a 9,2 × 1018 0L long distancia = 149597870700L;
float 32 bits ±3,4 × 1038 (7 dígitos decimales) 0.0f float pi = 3.1416f;
double 64 bits ±1,7 × 10308 (15 dígitos decimales) 0.0 double precio = 29.99;
char 16 bits Carácter Unicode ('\u0000' a '\uffff') '\u0000' char letra = 'A';
boolean 1 bit* true o false false boolean activo = true;

* El tamaño real de boolean depende de la JVM; típicamente ocupa 1 byte en la práctica, aunque solo necesita 1 bit de información.

🔹 Literales numéricos con formato legible (Java 7+)

Desde Java 7, los literales numéricos pueden incluir guiones bajos como separadores visuales, lo que mejora enormemente la legibilidad de números grandes:

Literales con guiones bajos
int poblacion = 47_000_000;          // Más legible que 47000000
long presupuesto = 1_500_000_000L;   // Mil quinientos millones
double pi = 3.141_592_653_589_793;   // Pi con alta precisión
int binario = 0b1010_1100;           // Literal binario: 172
int hex = 0xFF_EC_DE_5E;             // Literal hexadecimal

🔹 Sufijos obligatorios en literales

Algunos tipos requieren un sufijo en sus literales para distinguirlos del tipo por defecto:

Sufijos L y f
long distanciaLuna = 384_400_000L;   // L obligatoria para long
float temperatura = 36.6f;            // f obligatoria para float
double gravedad = 9.81;               // Sin sufijo: double por defecto
// Sin sufijo, un entero literal es int y un decimal es double
⚠️
Error frecuente: Olvidar el sufijo L en literales long que exceden el rango de int causa un error de compilación: long x = 3000000000; falla porque el literal se interpreta como int. La solución es long x = 3_000_000_000L;

📚 Tipos de referencia

Los tipos de referencia incluyen todo lo que no es un tipo primitivo: clases, interfaces, arrays y enumeraciones. A diferencia de los primitivos, las variables de referencia no almacenan el valor directamente sino la dirección de memoria donde reside el objeto en el heap.

Tipos de referencia comunes
// Clase String (la más usada)
String nombre = "María López";

// Arrays
int[] calificaciones = {9, 8, 10, 7, 9};
String[] ciudades = new String[3];

// Clases envoltorio (wrapper classes)
Integer edad = 25;           // Autoboxing: int → Integer
Double salario = 2500.50;    // Autoboxing: double → Double

// Colecciones (requieren tipos de referencia)
ArrayList<String> alumnos = new ArrayList<>();
HashMap<String, Integer> notas = new HashMap<>();

🔹 Primitivos vs. referencia: diferencias clave

Característica Tipo primitivo Tipo de referencia
Almacenamiento Valor directo en el stack Referencia en stack, objeto en heap
Puede ser null ❌ No ✅ Sí
Valor por defecto 0, false, '\u0000' null
Tiene métodos ❌ No ✅ Sí
Uso en genéricos ❌ No (ArrayList<int> no compila) ✅ Sí (ArrayList<Integer>)
Comparación con == Compara valores Compara referencias (usar .equals())
⚠️
Cuidado con == en objetos: new String("hola") == new String("hola") devuelve false porque compara direcciones de memoria, no contenido. Usa siempre .equals() para comparar el contenido de objetos.

🏗️ Tipos de variables: locales, de instancia y estáticas

Independientemente de su tipo de dato, las variables en Java se clasifican en tres categorías según dónde se declaran y a quién pertenecen:

🔹 Variables locales

Se declaran dentro de un método, constructor o bloque. Solo existen durante la ejecución de ese bloque y no reciben valor por defecto — el compilador exige que se inicialicen antes de usarlas.

Variables locales
public double calcularDescuento(double precio, int porcentaje) {
    // 'descuento' es una variable local
    double descuento = precio * porcentaje / 100.0;
    
    // 'precioFinal' también es local
    double precioFinal = precio - descuento;
    
    return precioFinal;
}
// Aquí 'descuento' y 'precioFinal' ya no existen

🔹 Variables de instancia (atributos de objeto)

Se declaran dentro de la clase pero fuera de cualquier método. Cada objeto creado de esa clase tiene su propia copia independiente. Reciben valores por defecto y pueden tener modificadores de acceso (private, protected, public).

Variables de instancia
public class Empleado {
    // Variables de instancia: cada objeto tiene su propia copia
    private String nombre;
    private double salario;
    private int antiguedad;
    
    public Empleado(String nombre, double salario) {
        this.nombre = nombre;       // this diferencia instancia de local
        this.salario = salario;
        this.antiguedad = 0;        // Valor inicial explícito
    }
}

🔹 Variables de clase (estáticas)

Se declaran con el modificador static. Pertenecen a la clase, no a ningún objeto concreto, y todas las instancias comparten la misma copia. Se accede a ellas con el nombre de la clase: NombreClase.variable.

Variables estáticas
public class Empleado {
    // Variable estática: compartida por TODOS los objetos
    private static int contadorEmpleados = 0;
    
    // Constante estática: convenio en MAYÚSCULAS
    public static final double SALARIO_MINIMO = 1134.0;
    
    private String nombre;
    
    public Empleado(String nombre) {
        this.nombre = nombre;
        contadorEmpleados++;   // Se incrementa por cada nuevo empleado
    }
    
    public static int getTotal() {
        return contadorEmpleados;
    }
}

// Uso desde fuera de la clase:
System.out.println(Empleado.SALARIO_MINIMO);   // 1134.0
System.out.println(Empleado.getTotal());        // Número de empleados creados

🔹 Tabla comparativa de los tres tipos

Característica Local De instancia Estática
Lugar de declaración Dentro de un método/bloque En la clase, fuera de métodos En la clase, con static
Pertenece a El método Cada objeto La clase
Valor por defecto ❌ Ninguno ✅ Sí (0, null, false) ✅ Sí (0, null, false)
Vida útil Mientras se ejecuta el bloque Mientras vive el objeto Mientras la clase esté cargada
Modificadores de acceso ❌ No permitidos ✅ private, public, protected ✅ private, public, protected

🎯 Alcance (scope) de las variables

El alcance o scope de una variable es la región del código donde puede ser accedida. Java usa alcance léxico (o estático): el ámbito de una variable se determina por su posición en el código fuente, no por el flujo de ejecución.

Niveles de alcance en Java
public class EjemploAlcance {
    // Alcance de CLASE: accesible en toda la clase
    private int valorClase = 100;
    
    public void metodoEjemplo() {
        // Alcance de MÉTODO: accesible en todo el método
        int valorMetodo = 50;
        
        for (int i = 0; i < 10; i++) {
            // Alcance de BLOQUE: 'i' solo existe dentro del for
            int temporal = valorMetodo + i;
            System.out.println(temporal);
        }
        
        // ❌ ERROR: 'i' y 'temporal' ya no existen aquí
        // System.out.println(i);        // No compila
        // System.out.println(temporal);  // No compila
        
        // ✅ CORRECTO: 'valorMetodo' sigue vivo
        System.out.println(valorMetodo);
    }
}

🔹 Shadowing (ensombrecimiento) de variables

Cuando una variable local tiene el mismo nombre que una variable de instancia, la local «ensombrace» a la de instancia dentro de su ámbito. Para acceder a la variable de instancia se usa this:

Shadowing con this
public class Producto {
    private String nombre;    // Variable de instancia
    private double precio;    // Variable de instancia
    
    public Producto(String nombre, double precio) {
        // Los parámetros 'nombre' y 'precio' ensombrecen a los atributos
        this.nombre = nombre;   // this.nombre → atributo de instancia
        this.precio = precio;   // nombre (sin this) → parámetro local
    }
}

🔒 Constantes con final

La palabra clave final indica que una variable no puede cambiar de valor después de su inicialización. Se puede aplicar a variables locales, parámetros, variables de instancia y variables estáticas.

Uso de final en distintos contextos
public class Configuracion {
    // Constante de clase: static + final, nombre en MAYÚSCULAS
    public static final double IVA = 0.21;
    public static final int MAX_INTENTOS = 3;
    public static final String EMPRESA = "Ciberaula S.L.";
    
    // Variable de instancia final: se asigna una vez (en constructor)
    private final String id;
    
    public Configuracion(String id) {
        this.id = id;   // Solo se puede asignar una vez
    }
    
    public void procesar() {
        // Variable local final
        final int limite = 100;
        // limite = 200;  // ❌ ERROR de compilación
        
        // Parámetro final (no se puede reasignar)
        calcular(limite);
    }
    
    private void calcular(final int valor) {
        // valor = 50;   // ❌ ERROR: parámetro final
        System.out.println("Calculando con: " + valor);
    }
}
💡
Convención de nombres: Las constantes static final se escriben en MAYÚSCULAS_CON_GUIONES_BAJOS por convención universal en Java. Ejemplos: MAX_SIZE, DEFAULT_TIMEOUT, PI.
⚠️
Matiz importante: final en una referencia a objeto impide reasignar la referencia, pero no impide modificar el contenido del objeto. Por ejemplo, final ArrayList<String> lista = new ArrayList<>(); permite hacer lista.add("elemento"), pero no lista = new ArrayList<>();.

🔮 Inferencia de tipos con var (Java 10+)

Desde Java 10, la palabra reservada var permite declarar variables locales sin especificar explícitamente su tipo: el compilador lo infiere del valor asignado. Es importante entender que no convierte a Java en un lenguaje dinámico — el tipo se fija en compilación y no cambia.

Inferencia de tipos con var
// Con tipo explícito
String mensaje = "Hola, Java";
ArrayList<String> nombres = new ArrayList<>();
HashMap<String, List<Integer>> mapa = new HashMap<>();

// Con var (equivalente exacto)
var mensaje = "Hola, Java";              // Inferido: String
var nombres = new ArrayList<String>();    // Inferido: ArrayList<String>
var mapa = new HashMap<String, List<Integer>>();  // Inferido

// Especialmente útil con tipos genéricos largos
var iterador = nombres.iterator();       // Inferido: Iterator<String>

🔹 Restricciones de var

Uso ¿Permitido? Ejemplo
Variable local con inicialización ✅ Sí var x = 10;
Variable en bucle for mejorado ✅ Sí for (var item : lista)
Variable en bucle for clásico ✅ Sí for (var i = 0; i < 10; i++)
Variable de instancia o estática ❌ No var nombre = "Ana"; en la clase
Parámetro de método ❌ No void metodo(var x)
Sin inicialización ❌ No var x; (no hay tipo que inferir)
Inicializado con null ❌ No var x = null; (tipo ambiguo)
Cuándo usar var: Es ideal cuando el tipo ya es obvio por el lado derecho de la asignación (var lista = new ArrayList<String>()) o cuando el tipo es excesivamente largo. Evítalo cuando el tipo no sea evidente y la legibilidad se resienta.

🔄 Conversión de tipos y casting

Java realiza automáticamente la conversión implícita (widening) cuando se asigna un valor de un tipo más pequeño a uno más grande, sin pérdida de información. Para la dirección contraria se necesita un casting explícito (narrowing), que puede provocar pérdida de datos.

▶️ Conversión implícita (widening)

Conversión automática (sin pérdida)
byte b = 42;
int i = b;           // byte → int: automático
long l = i;          // int → long: automático
float f = l;         // long → float: automático
double d = f;        // float → double: automático

// Cadena de promoción: byte → short → int → long → float → double

▶️ Casting explícito (narrowing)

Casting manual (posible pérdida)
double precio = 29.99;
int precioEntero = (int) precio;      // 29 (se pierde la parte decimal)

long grande = 130;
byte pequeno = (byte) grande;         // -126 (overflow: 130 excede rango byte)

float decimal = 3.14f;
int entero = (int) decimal;           // 3 (truncamiento, no redondeo)

// Para redondear correctamente:
int redondeado = (int) Math.round(decimal);  // 3 (Math.round devuelve long)

▶️ Autoboxing y unboxing

Java convierte automáticamente entre tipos primitivos y sus clases envoltorio:

Autoboxing y unboxing
// Autoboxing: primitivo → objeto (automático)
Integer edadObj = 25;                 // int → Integer
Double salarioObj = 2500.50;          // double → Double

// Unboxing: objeto → primitivo (automático)
int edad = edadObj;                   // Integer → int
double salario = salarioObj;          // Double → double

// Cuidado con null en unboxing:
Integer valor = null;
// int x = valor;  // ❌ NullPointerException en tiempo de ejecución

📋 Valores por defecto

Las variables de instancia y las estáticas que no se inicializan explícitamente reciben un valor por defecto. Las variables locales no tienen valor por defecto y el compilador exige su inicialización.

Tipo de dato Valor por defecto
byte, short, int0
long0L
float0.0f
double0.0
char'\u0000' (carácter nulo)
booleanfalse
Cualquier referencia (String, arrays, objetos)null
Demostración de valores por defecto
public class ValoresPorDefecto {
    // Variables de instancia: tienen valor por defecto
    int numero;
    boolean activo;
    String texto;
    double[] datos;
    
    public void mostrar() {
        System.out.println("int:     " + numero);   // 0
        System.out.println("boolean: " + activo);    // false
        System.out.println("String:  " + texto);     // null
        System.out.println("double[]:" + datos);     // null
    }
    
    public void errorLocal() {
        int local;
        // System.out.println(local);  // ❌ ERROR: variable might not have been initialized
    }
}
Salida:
int:     0
boolean: false
String:  null
double[]:null

📐 Convenciones de nomenclatura

Java tiene reglas estrictas sobre los identificadores y convenciones ampliamente aceptadas. Seguirlas hace que el código sea legible para cualquier desarrollador Java del mundo.

🔹 Reglas del lenguaje (obligatorias)

Un identificador en Java puede contener letras, dígitos, guiones bajos (_) y el signo de dólar ($). No puede empezar por un dígito ni coincidir con una palabra reservada del lenguaje (class, int, if, for, etc.). Java distingue mayúsculas de minúsculas: edad, Edad y EDAD son tres variables distintas.

🔹 Convenciones de estilo (mejores prácticas)

Tipo de identificador Convención Ejemplo
Variables y parámetros camelCase nombreCompleto, precioUnitario
Constantes (static final) MAYUSCULAS_CON_GUIONES MAX_INTENTOS, IVA_GENERAL
Clases e interfaces PascalCase Empleado, CuentaBancaria
Métodos camelCase (verbo) calcularTotal(), getNombre()
Paquetes todo en minúsculas com.ciberaula.modelo
Nombres descriptivos: Prefiere precioConIva a p, y indiceActual a i (excepto en bucles cortos). Un buen nombre elimina la necesidad de comentarios.

💼 Ejemplo integrador: sistema de registro de empleados

Este ejemplo combina todos los conceptos vistos: variables locales, de instancia y estáticas; tipos primitivos y de referencia; constantes; conversión de tipos; y buenas prácticas de nomenclatura.

RegistroEmpleados.java — Ejemplo completo
public class RegistroEmpleados {

    // ── Constantes de clase (static final) ──────────────────────
    public static final double SALARIO_MINIMO = 1134.0;
    public static final int MAX_EMPLEADOS = 500;
    public static final String EMPRESA = "Ciberaula S.L.";

    // ── Variable estática: compartida por todas las instancias ──
    private static int totalEmpleados = 0;

    // ── Variables de instancia: propias de cada objeto ──────────
    private final int id;           // final: no cambia después del constructor
    private String nombre;
    private String departamento;
    private double salarioBruto;
    private boolean activo;

    // ── Constructor ─────────────────────────────────────────────
    public RegistroEmpleados(String nombre, String departamento, double salarioBruto) {
        // Shadowing: parámetros 'nombre', 'departamento', 'salarioBruto'
        // ensombrecen a las variables de instancia
        this.id = ++totalEmpleados;
        this.nombre = nombre;
        this.departamento = departamento;
        this.salarioBruto = Math.max(salarioBruto, SALARIO_MINIMO);
        this.activo = true;
    }

    // ── Método con variables locales ────────────────────────────
    public double calcularSalarioNeto() {
        // Variables locales: solo existen dentro de este método
        final double IRPF = 0.15;
        final double SEGURIDAD_SOCIAL = 0.0635;

        double descuentoIrpf = salarioBruto * IRPF;
        double descuentoSS = salarioBruto * SEGURIDAD_SOCIAL;
        double salarioNeto = salarioBruto - descuentoIrpf - descuentoSS;

        return Math.round(salarioNeto * 100.0) / 100.0;  // Redondeo a 2 decimales
    }

    // ── Método con conversión de tipos ──────────────────────────
    public String generarFicha() {
        // Conversión implícita: int → String (concatenación)
        var ficha = new StringBuilder();
        ficha.append("╔══════════════════════════════════════╗\n");
        ficha.append("  FICHA DE EMPLEADO - ").append(EMPRESA).append("\n");
        ficha.append("  ID:           ").append(id).append("\n");
        ficha.append("  Nombre:       ").append(nombre).append("\n");
        ficha.append("  Departamento: ").append(departamento).append("\n");
        ficha.append("  Salario bruto: ").append(String.format("%.2f €", salarioBruto)).append("\n");
        ficha.append("  Salario neto:  ").append(String.format("%.2f €", calcularSalarioNeto())).append("\n");
        ficha.append("  Estado:       ").append(activo ? "Activo" : "Inactivo").append("\n");
        ficha.append("╚══════════════════════════════════════╝");
        return ficha.toString();
    }

    // ── Getters ─────────────────────────────────────────────────
    public int getId() { return id; }
    public String getNombre() { return nombre; }
    public boolean isActivo() { return activo; }
    public static int getTotalEmpleados() { return totalEmpleados; }

    // ── Método main: punto de entrada ───────────────────────────
    public static void main(String[] args) {
        // Variables locales en main
        var emp1 = new RegistroEmpleados("Ana García", "Desarrollo", 3200.0);
        var emp2 = new RegistroEmpleados("Carlos López", "Marketing", 2800.0);
        var emp3 = new RegistroEmpleados("María Torres", "RRHH", 900.0); // Se ajusta al mínimo

        System.out.println(emp1.generarFicha());
        System.out.println();
        System.out.println(emp2.generarFicha());
        System.out.println();
        System.out.println(emp3.generarFicha());

        System.out.println("\n📊 Total empleados registrados: " + RegistroEmpleados.getTotalEmpleados());
        System.out.println("💰 Salario mínimo vigente: " + RegistroEmpleados.SALARIO_MINIMO + " €");
    }
}
Salida esperada:
╔══════════════════════════════════════╗
  FICHA DE EMPLEADO - Ciberaula S.L.
  ID:           1
  Nombre:       Ana García
  Departamento: Desarrollo
  Salario bruto: 3200.00 €
  Salario neto:  2516.80 €
  Estado:       Activo
╚══════════════════════════════════════╝

╔══════════════════════════════════════╗
  FICHA DE EMPLEADO - Ciberaula S.L.
  ID:           2
  Nombre:       Carlos López
  Departamento: Marketing
  Salario bruto: 2800.00 €
  Salario neto:  2202.20 €
  Estado:       Activo
╚══════════════════════════════════════╝

╔══════════════════════════════════════╗
  FICHA DE EMPLEADO - Ciberaula S.L.
  ID:           3
  Nombre:       María Torres
  Departamento: RRHH
  Salario bruto: 1134.00 €
  Salario neto:  891.42 €
  Estado:       Activo
╚══════════════════════════════════════╝

📊 Total empleados registrados: 3
💰 Salario mínimo vigente: 1134.0 €

🚫 Errores frecuentes con variables

❌ Error 1: Usar una variable local sin inicializar

Error: variable might not have been initialized
public void ejemplo() {
    int resultado;
    // ❌ ERROR de compilación:
    System.out.println(resultado);  // "variable resultado might not have been initialized"
    
    // ✅ CORRECTO:
    int resultado2 = 0;
    System.out.println(resultado2);  // 0
}

❌ Error 2: Comparar Strings con == en lugar de .equals()

Error sutil con referencias
String a = new String("hola");
String b = new String("hola");

// ❌ INCORRECTO: compara referencias (direcciones de memoria)
if (a == b) {
    System.out.println("Iguales");  // No se ejecuta
}

// ✅ CORRECTO: compara contenido
if (a.equals(b)) {
    System.out.println("Iguales");  // Sí se ejecuta
}

❌ Error 3: Pérdida de precisión por casting inadvertido

División entera inesperada
// ❌ INCORRECTO: división entre enteros → resultado entero
int aprobados = 7;
int total = 10;
double porcentaje = aprobados / total;        // 0.0 (¡no 0.7!)

// ✅ CORRECTO: forzar al menos un operando a double
double porcentaje2 = (double) aprobados / total;  // 0.7
double porcentaje3 = aprobados / (double) total;   // 0.7
double porcentaje4 = aprobados * 1.0 / total;      // 0.7

❌ Error 4: NullPointerException por unboxing de null

Unboxing peligroso
// ❌ PELIGROSO: unboxing de null lanza NullPointerException
Integer edadObjeto = null;
int edad = edadObjeto;   // NullPointerException en tiempo de ejecución

// ✅ SEGURO: verificar null antes de unboxing
Integer edadObjeto2 = obtenerEdad();  // Puede devolver null
int edad2 = (edadObjeto2 != null) ? edadObjeto2 : 0;

❌ Error 5: Acceder a variable fuera de su alcance

Error de alcance
// ❌ INCORRECTO: declarar dentro del bloque y usar fuera
if (condicion) {
    String mensaje = "Aprobado";
}
System.out.println(mensaje);  // ERROR: "cannot find symbol"

// ✅ CORRECTO: declarar antes del bloque
String mensaje;
if (condicion) {
    mensaje = "Aprobado";
} else {
    mensaje = "Suspendido";
}
System.out.println(mensaje);  // OK

🧪 Ejercicios prácticos

📝 Ejercicio 1: Calculadora de IMC

Crea una clase CalculadoraIMC con variables de instancia para el nombre, peso (kg) y altura (m) de una persona. Incluye un método calcularIMC() que devuelva el índice de masa corporal (peso / altura²) y un método clasificar() que devuelva una cadena indicando si es bajo peso (<18.5), normal (18.5-24.9), sobrepeso (25-29.9) u obesidad (≥30). Usa constantes para los umbrales.

📝 Ejercicio 2: Conversor de temperaturas

Escribe una clase con métodos estáticos para convertir temperaturas entre Celsius, Fahrenheit y Kelvin. Usa constantes para los factores de conversión. El método main debe demostrar todas las conversiones con variables locales, usando var donde sea apropiado.

📝 Ejercicio 3: Inventario de productos con tipos mixtos

Crea una clase Producto que combine todos los tipos de variables vistos: una constante estática para el IVA (21%), un contador estático de productos, atributos de instancia para id, nombre, precio y stock, y un método calcularPrecioFinal() con variables locales. Incluye un método estático resumen() que reciba un array de productos y calcule el valor total del inventario.

📎 Artículos relacionados

❓ Preguntas frecuentes sobre Variables en Java: declaración, tipos y alcance

Las dudas más comunes respondidas de forma clara y directa.

Una variable local se declara dentro de un método, constructor o bloque, y solo existe mientras ese bloque se ejecuta. Una variable de instancia se declara dentro de una clase pero fuera de cualquier método, pertenece a cada objeto creado de esa clase y existe mientras el objeto viva en memoria. Las variables locales no tienen valor por defecto y deben inicializarse antes de usarse, mientras que las de instancia reciben valores por defecto automáticamente (0, null, false, etc.).
Significa que toda variable debe declararse con un tipo de dato concreto antes de poder usarla, y el compilador verifica en tiempo de compilación que las asignaciones y operaciones sean compatibles con ese tipo. No puedes asignar un String a una variable int, por ejemplo. Esto previene muchos errores en tiempo de ejecución y hace el código más predecible y seguro.
Introducida en Java 10, la palabra clave var permite que el compilador infiera el tipo de una variable local a partir del valor que se le asigna. Por ejemplo, var nombre = "Ana" infiere que nombre es de tipo String. Solo funciona con variables locales (dentro de métodos), requiere inicialización inmediata y no convierte a Java en un lenguaje dinámico: el tipo se fija en tiempo de compilación y no cambia después.
int es un tipo primitivo que almacena directamente un valor entero de 32 bits en la pila (stack) y no puede ser null. Integer es la clase envoltorio (wrapper) correspondiente: almacena el valor como un objeto en el heap, puede ser null, y proporciona métodos útiles como parseInt() o compareTo(). Java convierte automáticamente entre ambos mediante autoboxing y unboxing. Se recomienda usar int para cálculos y Integer cuando se necesita un objeto (por ejemplo, en colecciones genéricas como ArrayList<Integer>).
Depende del tipo de variable. Las variables de instancia y las estáticas reciben valores por defecto automáticamente (0 para numéricos, false para boolean, null para referencias), por lo que no es obligatorio inicializarlas explícitamente. Sin embargo, las variables locales no reciben valor por defecto: el compilador exige que se les asigne un valor antes de leerlas, y si no lo haces obtendrás un error de compilación "variable might not have been initialized".
El shadowing (ensombrecimiento) ocurre cuando una variable local o un parámetro tiene el mismo nombre que una variable de instancia de la clase. En ese caso, la variable local "oculta" a la de instancia dentro de su ámbito. Para acceder a la variable de instancia oculta se usa la palabra clave this. Por ejemplo, si un constructor recibe un parámetro nombre y la clase tiene un atributo nombre, se escribe this.nombre = nombre para distinguirlos.
Usa final siempre que una variable no deba cambiar de valor después de su inicialización. Las constantes de clase se declaran como static final y por convención se nombran en MAYÚSCULAS_CON_GUIONES_BAJOS (por ejemplo, static final double IVA = 0.21). También puedes usar final en variables locales y parámetros para indicar que son inmutables, lo que mejora la legibilidad y previene errores accidentales.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre Variables en Java: declaración, tipos y alcance? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!