Miembros estáticos (static)

Intermedio
Java
Java
Actualizado: 18/04/2026

Qué significa static

La palabra clave static indica que un miembro pertenece a la clase en lugar de a una instancia concreta. Existe una sola copia compartida entre todas las instancias y puede usarse incluso sin crear objetos.

public class Contador {
    private static int totalCreados = 0; // pertenece a la clase
    private int id; // pertenece a cada instancia

    public Contador() {
        totalCreados++;
        this.id = totalCreados;
    }

    public static int getTotalCreados() {
        return totalCreados;
    }
}

Uso:

new Contador();
new Contador();
new Contador();
System.out.println(Contador.getTotalCreados()); // 3

Se accede con NombreClase.miembro (convención) sin necesitar instancia.

Campos estáticos

Un campo estático (o variable de clase) comparte valor entre todas las instancias:

public class ConfiguracionApp {
    public static final String VERSION = "2.6.1";
    public static final int MAX_USUARIOS = 1000;
    private static int usuariosActivos = 0;

    public static void conectarUsuario() {
        if (usuariosActivos >= MAX_USUARIOS) {
            throw new IllegalStateException("límite alcanzado");
        }
        usuariosActivos++;
    }
}
  • static final + MAYÚSCULAS = constantes (convención Java).
  • Los campos static se inicializan cuando la clase se carga por primera vez, antes de cualquier uso.

Métodos estáticos

Los métodos static no reciben this implícito: no tienen acceso a miembros de instancia directamente. Son perfectos para:

  • Funciones puras sobre parámetros (utilidades).
  • Factory methods que devuelven instancias.
  • Helpers de cálculo.
public class MathUtil {
    public static int max(int a, int b) {
        return a > b ? a : b;
    }

    public static double toRadians(double grados) {
        return grados * Math.PI / 180.0;
    }
}

// Uso
int m = MathUtil.max(10, 20);
double r = MathUtil.toRadians(90);

Factory methods (patrón muy común)

public class Punto {
    private final int x;
    private final int y;

    private Punto(int x, int y) { this.x = x; this.y = y; }

    public static Punto cartesiano(int x, int y) {
        return new Punto(x, y);
    }

    public static Punto origen() {
        return new Punto(0, 0);
    }

    public static Punto polar(double radio, double angulo) {
        int x = (int) (radio * Math.cos(angulo));
        int y = (int) (radio * Math.sin(angulo));
        return new Punto(x, y);
    }
}

// Uso más expresivo que `new Punto(...)`
Punto p1 = Punto.origen();
Punto p2 = Punto.polar(5.0, Math.PI / 4);

Ventajas sobre constructores directos:

  • Nombres descriptivos (origen(), polar(), cartesiano()).
  • Pueden devolver subtipos o cachear instancias.
  • Pueden devolver null o lanzar excepciones configuradas.

Bloques de inicialización estática

Un bloque static { ... } se ejecuta una vez cuando la clase se carga, en el orden en que aparece. Sirve para inicializaciones complejas:

public class Traductor {
    private static final Map<String, String> TRADUCCIONES = new HashMap<>();

    static {
        TRADUCCIONES.put("hola", "hello");
        TRADUCCIONES.put("adiós", "goodbye");
        TRADUCCIONES.put("gracias", "thanks");
        // Podría cargar de archivo aquí
    }

    public static String traducir(String palabra) {
        return TRADUCCIONES.getOrDefault(palabra, palabra);
    }
}

Orden de inicialización al cargar una clase:

  1. Campos estáticos con inicializador y bloques static {}: en el orden textual del código.
  2. Luego, cuando se instancia: campos de instancia y constructor.

Clases anidadas estáticas

Una clase anidada declarada static no captura la instancia externa. Es conceptualmente una clase independiente que vive dentro del namespace de la clase contenedora:

public class Nodo {
    private int valor;
    private Nodo siguiente;

    public static class Builder {
        private int valor;
        private Nodo siguiente;

        public Builder valor(int v) { this.valor = v; return this; }
        public Builder siguiente(Nodo s) { this.siguiente = s; return this; }

        public Nodo build() {
            Nodo n = new Nodo();
            n.valor = valor;
            n.siguiente = siguiente;
            return n;
        }
    }
}

// Uso
Nodo n = new Nodo.Builder().valor(42).build();

A diferencia de una clase anidada no-estática (inner class), Builder aquí no necesita una instancia de Nodo para existir. Se usan mucho para builders, iteradores y estructuras auxiliares.

static import

Permite referenciar miembros estáticos sin cualificar con el nombre de la clase:

import static java.lang.Math.*;
import static java.util.Arrays.asList;

public class Ejemplo {
    public double circulo(double r) {
        return PI * pow(r, 2); // en vez de Math.PI y Math.pow(...)
    }

    public List<Integer> nums() {
        return asList(1, 2, 3); // en vez de Arrays.asList
    }
}

Cuándo usarlo: en utilidades muy frecuentes (Math.*, assertions de tests, Collectors.*). Cuándo evitarlo: cuando oculta el origen de métodos y reduce legibilidad.

Antipatrones con static

Estado global mutable

// MAL: singleton de estado mutable compartido
public class Carrito {
    public static List<Producto> productos = new ArrayList<>();
}

Genera bugs impredecibles en concurrencia, dificulta los tests y rompe el aislamiento. Prefiere pasar dependencias explícitamente.

"Clase utilitaria" que crece sin control

Las clases con solo métodos estáticos son útiles, pero no conviertas todo en utilidad estática. Si crece el estado o la lógica empieza a acoplar implementaciones, convierte a clase con dependencias explícitas.

Constantes que deberían ser configuración

// MAL: hardcoded en una constante estática
public static final String API_URL = "https://api.prod.example.com";

Preferible inyectar por configuración (propiedades, variables de entorno). Deja static final para valores verdaderamente inmutables del dominio (máximos, códigos, etc.).

Resumen de uso

| Uso | Ejemplo | Buena idea | |-----|---------|------------| | Constantes (static final + mayúsculas) | MAX_CONEXIONES = 100 | ✅ Sí | | Factory methods | Point.origen() | ✅ Sí | | Funciones utilitarias puras | Math.max() | ✅ Sí | | Contadores / estadísticas compartidas | instances++ | ⚠️ Con cuidado (sincronización) | | Caches / lookup tables inmutables | Map<String, String> inicial | ✅ Sí | | Estado global mutable | public static List<Producto> | ❌ No | | Configuración dependiente de entorno | API_URL = "https://..." | ❌ Mejor inyección |

static es una herramienta utiles. Bien usada, da APIs claras y eficientes. Mal usada, crea acoplamientos invisibles y código imposible de testear.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Java

Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Distinguir entre miembros de instancia y miembros de clase (static). Declarar y usar campos y métodos estáticos. Comprender la inicialización de clases y el orden de los bloques static {}. Reconocer buenas prácticas: factory methods, constantes, utilidades. Evitar antipatrones: estado global mutable, dependencias ocultas. Aplicar static import sin abusar.