</>waridocu

Java

static y final

Qué significa que algo pertenezca a la clase y no al objeto, por qué main es static, y qué garantiza exactamente final en una variable, un método y una clase.

javastaticfinalconstantespoo

Dos palabras que vienes arrastrando

Desde la primera línea de Java que escribiste está ahí:

javajava
public static void main(String[] args) { }

Y en Métodos apareció otra vez, con la explicación en suspenso: “el método pertenece a la clase, no requiere crear un objeto”. Ahora que existen los objetos, esa frase se puede terminar.

static: pertenecer a la clase, no al objeto

static se traduce como “estático”, en el sentido de “fijo, que no se mueve”. La idea: un miembro static existe una sola vez, en la clase, en vez de una vez por cada objeto.

Ejemplo: un atributo por objeto y uno compartidojava
public class Alumno {
    private String nombre;              // uno por cada Alumno
    private static int totalAlumnos;    // UNO para todos los Alumno

    public Alumno(String nombre) {
        this.nombre = nombre;
        totalAlumnos++;
    }

    public static int getTotalAlumnos() {
        return totalAlumnos;
    }
}
javajava
new Alumno("Ana");
new Alumno("Beto");
new Alumno("Caro");
System.out.println(Alumno.getTotalAlumnos());
texttext
3

Cada new Alumno(...) crea su propio nombre, pero totalAlumnos es uno solo que todos comparten e incrementan. Y fíjate en cómo se llama al método: Alumno.getTotalAlumnos(), con el nombre de la clase, sin ningún objeto de por medio. Eso es lo que significa “pertenece a la clase”.

Miembro de instancia (sin static)Miembro de clase (static)
Cuántas copias hayUna por cada objetoUna sola, compartida
Cómo se accedeobjeto.metodo()Clase.metodo()
Cuándo existeDesde que se hace newDesde que la clase se carga, antes de cualquier new
Puede usar thisNo — no hay ningún objeto al que referirse

Por qué main es static

Con eso, la respuesta es directa: cuando lanzas java Main, la JVM tiene que ejecutar main antes de que exista ningún objeto — de hecho, main es el lugar donde se crea el primero. Si main no fuera static, harían falta un objeto para llamarlo y un main para crear ese objeto: un círculo sin salida. Declararlo static lo hace invocable con solo el nombre de la clase, que es todo lo que la JVM tiene al arrancar.

El error clásico: non-static … cannot be referenced from a static context

Este mensaje aparece siempre, y ahora tiene explicación:

Ejemplo: el error que todo el mundo comete una vezjava
public class Main {
    private int contador = 0;

    public static void main(String[] args) {
        contador++;   // ❌ non-static variable contador
                      //    cannot be referenced from a static context
    }
}

contador existe una vez por objeto, pero main corre sin ningún objeto: no hay ningún contador concreto al que sumarle uno. Las dos salidas:

Opción A: crear un objeto y trabajar sobre éljava
public static void main(String[] args) {
    Main app = new Main();
    app.contador++;
}
Opción B: si el dato es realmente único, declararlo staticjava
private static int contador = 0;

public static void main(String[] args) {
    contador++;
}
Advertencia

La tentación al ver ese error es agregar static a todo hasta que compile. Funciona, pero convierte el programa en una única bolsa de variables globales, que es justo lo que la encapsulación evita. Pregúntate primero si el dato es de verdad único para todo el programa; si es de cada objeto, la respuesta es la opción A.

Métodos static útiles de verdad

El uso más limpio de static son los métodos que no dependen de ningún estado: reciben datos, calculan y devuelven. Ya vienes usando varios:

javajava
Math.max(3, 7);              // Math nunca se instancia
Integer.parseInt("42");
Double.compare(a, b);
Collections.sort(lista);

Ninguna de esas llamadas necesitó un new. Son funciones de utilidad, y static es exactamente su lugar.

final: esto ya no cambia

final se traduce como “final” en el sentido de “definitivo”. Significa cosas ligeramente distintas según dónde se escriba:

DóndeQué garantiza
En una variableSe le asigna un valor una sola vez; reasignarla no compila
En un métodoNinguna subclase puede sobrescribirlo
En una claseNinguna clase puede heredar de ella (extends no compila)

final en variables: constantes

Ejemplo: una constante de clasejava
public class Circulo {
    private static final double PI = 3.14159;
    private final String id;

    public Circulo(String id) {
        this.id = id;   // ✅ primera y única asignación
    }
}

static final juntos es la forma canónica de una constante: una sola en toda la aplicación (static) e inmodificable (final). La convención de nombres para estas es MAYÚSCULAS_CON_GUION_BAJO, y verla escrita así en código ajeno es la señal de que estás mirando una constante:

javajava
public static final int MAX_ALUMNOS = 40;
public static final String VERSION = "2.1";

id en cambio es final sin static: cada círculo tiene el suyo, asignado en el constructor, y después nadie puede cambiarlo. Es la forma de expresar en el propio tipo lo que en Encapsulación se resolvía “poniendo solo getter”: ahora ni siquiera la propia clase puede reasignarlo por accidente.

Importante

final en una variable de tipo objeto congela la referencia, no el contenido. Una final List<String> nombres no se puede hacer apuntar a otra lista, pero nombres.add("Ana") funciona perfectamente. Es el mismo “la variable guarda una referencia” de Object, toString y equals, visto desde el otro lado.

final en métodos y clases

javajava
public class Cuenta {
    public final double getSaldo() { ... }   // ninguna hija puede cambiarlo
}

public final class Config { ... }            // de esta nadie hereda

Se usa cuando el comportamiento es parte de una garantía que no debe poder romperse desde afuera. El ejemplo que ya conoces es String: es una clase final, y por eso nadie puede escribir un MiString extends String que se comporte raro donde el resto del programa espera un String normal.

static y final juntos, en una tabla

DeclaraciónSignifica
int x;Un valor por objeto, modificable
final int x;Un valor por objeto, fijado en el constructor
static int x;Un valor compartido por toda la clase, modificable
static final int X;Una constante: única y fija — el caso más común

Pon a prueba

🎯 ¿Qué imprime?
javajava
public class Contador {
    int personales = 0;
    static int compartido = 0;

    public Contador() {
        personales++;
        compartido++;
    }
}

// en main:
new Contador();
new Contador();
Contador c = new Contador();

System.out.println(c.personales);
System.out.println(Contador.compartido);
  • Correcto. personales no es static: cada objeto tiene el suyo, y cada constructor lo dejó en 1. compartido sí es static: existe una sola vez y los tres constructores lo incrementaron, así que llegó a 3.

  • Sería así si los dos atributos fueran static. personales no lo es, así que c.personales es el contador propio de ese objeto, incrementado una sola vez cuando se lo creó.

  • Sería así si ninguno fuera static. Pero compartido sí lo es, y por eso los tres new suman sobre la misma y única variable.

  • Al revés: un miembro static se accede precisamente con el nombre de la clase, sin objeto. Lo que no compilaría sería Contador.personales, porque ese sí necesita saber de qué objeto se trata.

💡 ¿Por qué no compila?

Este código tiene tres errores de compilación. ¿Cuáles son y cómo se arregla cada uno?

javajava
public class Config {
    private static final int MAX = 100;
    private String entorno = "dev";

    public static void mostrar() {
        System.out.println(entorno);
    }

    public static void subirTope() {
        MAX = 200;
    }

    public void usarThis() {
        System.out.println(this.MAX);
    }
}

1. System.out.println(entorno) dentro de mostrar(). mostrar() es static y entorno es de instancia: no hay ningún objeto del que leer el entorno. Se arregla haciendo entorno también static, o quitándole el static a mostrar().

2. MAX = 200;. MAX es final: ya tiene su valor y reasignarlo da cannot assign a value to final variable MAX. Si el tope tiene que poder cambiar, no debería ser final.

3. this.MAX. Este es más sutil y en muchos compiladores es solo una advertencia, no un error: acceder a un miembro static a través de this (o de un objeto) funciona pero es engañoso, porque sugiere que hay un MAX por objeto cuando hay uno solo. La forma correcta es Config.MAX o simplemente MAX.

El patrón detrás de los tres: desde un contexto static no existe ningún objeto, y final significa que la asignación ya ocurrió.

✏️ Completa las declaraciones

Banco tiene una tasa fija compartida por todas las cuentas, un número de cuenta propio que no cambia nunca una vez creado, y un método de utilidad que no necesita objeto.

public class Banco {
  public static  double TASA = 0.03;

  private  String numeroCuenta;

  public Banco(String numeroCuenta) {
      .numeroCuenta = numeroCuenta;
  }

  public  double interesDe(double monto) {
      return monto * TASA;
  }
}

🧩 Ordena el código

Ordena las líneas para escribir una clase con una constante, un contador compartido y un getter estático.

  1. activas++;
  2. public class Sesion {
  3. }
  4. public static int getActivas() {
  5. }
  6. private static int activas = 0;
  7. public static final int MAX_INTENTOS = 3;
  8. return activas;
  9. public Sesion() {
  10. }

Siguiente paso

Con esto cierra la orientación a objetos. Falta lo que pasa cuando el programa se encuentra con algo que no puede resolver — una nota inválida, un archivo que no existe, una división por cero — y en vez de seguir como si nada, corta y avisa: ver Excepciones: try, catch y throw.