Tabla de Contenidos

08 - POO III: Métodos estáticos

Como hemos visto, para poder ejecutar métodos de una clase necesitamos crear antes un objeto de la misma. Si intentamos ejecutar un método de una clase sin haber creado un objeto, el compilador nos mostrará un error. Sin embargo, existe un mecanismo por el cual podemos ejecutar métodos de una clase sin necesidad de crear objetos: los métodos estáticos.

De hecho, el método main() de nuestra clase principal lleva asociado la palabra static precisamente por eso, para no tener que crear obligatoriamente un objeto de dicha clase:

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

Sin embargo, si creamos un método en nuestra clase principal y queremos ejecutarlo, veremos como el compilador nos avisará del error diciendo que no podemos ejecutar métodos sin crear antes un objeto de dicha clase. Por ejemplo, crea el método sumar() para sumar dos números enteros e intenta ejecutar la aplicación:

public class Main {
    
    private int sumar(int numero1, int numero2) {
        return numero1 + numero2;
    }

    public static void main(String[] args) {
        int suma;

        suma = this.sumar(3, 5);
        System.out.println("La suma es " + suma);
    }
}

Cannot use this in a static context

Lo mismo ocurre si creamos una clase aparte con varios métodos e intentamos usarlos en nuestra clase principal sin crear antes un objeto:

public class Calculadora {
    
    public int sumar(int numero1, int numero2) {
        return numero1 + numero2;
    }

    public int restar(int numero1, int numero2) {
        return numero1 - numero2;
    }

    public int muliplicar(int numero1, int numero2) {
        return numero1 * numero2;
    }

    public int dividir(int numero1, int numero2) {
        return numero1 / numero2;
    }
}

public class Main {
    
    public static void main(String[] args) {
        Calculadora calc;

        System.out.println("3 + 5 = " + calc.sumar(3, 5));
    }
}

The local variable calc may not have been initialized

Definir métodos estáticos

Para poder usar los métodos de nuestra clase Calculadora necesitamos definirlos como estáticos añadiendo la palabra reservada static a la cabecera del método:

public class Calculadora {
    
    public static int sumar(int numero1, int numero2) {
        return numero1 + numero2;
    }

    public static int restar(int numero1, int numero2) {
        return numero1 - numero2;
    }

    public static int muliplicar(int numero1, int numero2) {
        return numero1 * numero2;
    }

    public static int dividir(int numero1, int numero2) {
        return numero1 / numero2;
    }
}

Ejecución de métodos estáticos

Ahora, desde nuestra clase principal ya podemos ejecutar esos métodos sin necesidad de crear ningún objeto de tipo Calculadora:

public class Main {
    
    public static void main(String[] args) {
        System.out.println("3 + 5 = " + Calculadora.sumar(3, 5));
    }
}

3 + 5 = 8

Fíjate que, al no crear ningún objeto de tipo Calculadora, para ejecutar el método ponemos NombreClase.nombreMétodo, en lugar de nombreObjeto.nombreMétodo.

Calculadora.sumar(3, 5);

En cualquier caso, aunque el método esté definido como estático, todavía podemos ejecutarlo de forma normal creando un objeto de la clase como hemos hecho hasta ahora (aunque la mayoría de veces no tiene sentido hacerlo de esta forma):

        Calculadora calc = new Calculadora();

        System.out.println("2 + 4 = " + calc.sumar(2, 4));

Acceso a propiedades de clase en métodos estáticos

Crea una propiedad para almacenar el resultado de las operaciones en la clase Calculadora:

public class Calculadora {

    private int resultado;
    
    public static int sumar(int numero1, int numero2) {
        this.resultado = numero1 + numero2;
        return this.resultado;
    }

    public static int restar(int numero1, int numero2) {
        this.resultado = numero1 - numero2;
        return this.resultado;
    }

    public static int muliplicar(int numero1, int numero2) {
        this.resultado = numero1 * numero2;
        return this.resultado;
    }

    public static int dividir(int numero1, int numero2) {
        this.resultado = numero1 / numero2;
        return this.resultado;
    }
}

Si intentamos ejecutar alguno de los métodos desde nuestra clase principal, vemos que nos salta un error:

public class Main {

    public static void main(String[] args) {
        System.out.println("3 + 5 = " + Calculadora.sumar(3, 5));
    }
}

Cannot use this in a static context

Este comportamiento es lógico, ya que la propiedad resultado solo está disponible cuando creamos un objeto de la clase Calculadora. Entonces, ¿Cómo podemos acceder a propiedades de la clase desde métodos estáticos? La solución es sencilla: basta con declarar esas propiedades también como estáticas:

public class Calculadora {

    private static int resultado;
    
    public static int sumar(int numero1, int numero2) {
        this.resultado = numero1 + numero2;
        return this.resultado;
    }

    public static int restar(int numero1, int numero2) {
        this.resultado = numero1 - numero2;
        return this.resultado;
    }

    public static int muliplicar(int numero1, int numero2) {
        this.resultado = numero1 * numero2;
        return this.resultado;
    }

    public static int dividir(int numero1, int numero2) {
        this.resultado = numero1 / numero2;
        return this.resultado;
    }
}

Si intentamos ejecutar alguno de los métodos, nos sigue dando un error:

Cannot use this in a static context

¿Qué está pasando? Si te fijas, nuestros métodos utilizan el operador this para acceder a la propiedad resultado. Como vimos en el tema anterior, this hace referencia al objeto que se ha creado, pero en un método estático no creamos ningún objeto, de ahí el error. Si quitamos la referencia a this, vemos que todo funciona correctamente:

public class Calculadora {

    private static int resultado;
    
    public static int sumar(int numero1, int numero2) {
        resultado = numero1 + numero2;
        return resultado;
    }

    public static int restar(int numero1, int numero2) {
        resultado = numero1 - numero2;
        return resultado;
    }

    public static int muliplicar(int numero1, int numero2) {
        resultado = numero1 - numero2;
        return resultado;

    }

    public static int dividir(int numero1, int numero2) {
        resultado = numero1 - numero2;
        return resultado;
    }
}

3 + 5 = 8

Cuando usamos métodos estáticos, no podemos utilizar la referencia this, ya que no creamos ningún objeto. Si queremos diferenciar entre propiedades de clase y variables pasadas a un método en caso de conflicto de nombres, tenemos dos posibles soluciones: cambiar el nombre a alguna de las variables o hacer referencia a la propiedad de la clase como NombreClase.nombrePropiedad.

Otra cosa a tener en cuenta de las propiedades estáticas es que no se reserva memoria por cada objeto creado de la clase. Solo se reserva memoria una vez, y el resto de objetos acceden a la misma posición de memoria, con lo que comparten la variable.

Por ejemplo, crea una clase Contador con el siguiente código:

public class Contador {
    private int cont = 0;


    Contador(){
        cont++;
        System.out.println(cont);
    }
}

Como ves, es una clase muy sencilla con una propiedad cont inicializada a 0. Cada vez que se crea un objeto de la clase, se aumenta en uno cont y se muestra por pantalla. Crea 3 objetos de la clase Contador en tu clase principal y mira la salida:

public class Main {
    
    public static void main(String[] args) {
        Contador cont1 = new Contador();
        Contador cont2 = new Contador();
        Contador cont3 = new Contador();
    }
}

1
1
1

Esta salida es la esperada, ya que cada objeto de la clase Contador crea su propia variable cont, con lo que cada una es independiente de las otras. Cambiar ahora la propiedad cont en la clase Contador y hazla estática:

public class Contador {
    private static int cont = 0;


    Contador(){
        cont++;
        System.out.println(cont);
    }
}

Si ejecutar ahora la aplicación, verás que la salida ha cambiado:

1
2
3

Como ves, ahora los tres objetos comparten la propiedad cont, con lo que cada vez que creamos un nuevo objeto de la clase Contador se incremente en uno la propiedad teniendo en cuenta el valor anterior.

Puede que te preguntes para qué sirve definir métodos como estáticos. La mayoría de nuestros clases y métodos serán normales, sin ser estáticos, pero hay ciertas situaciones donde es bastante útil definir clases con métodos estáticos. Por ejemplo, si queremos agrupar una serie de métodos comunes a varias clases sin usar herencia (helpers), o para clases de configuración (donde almacenamos las diferentes configuraciones de nuestra aplicación) o conjunto de métodos útiles que queremos tener accesible sin necesidad de gastar memoria creando objetos (como nuestra clase Calculadora)…

Ejercicios

Ejercicio 1

Crea una clase Calculadora con 4 métodos estáticos: sumar(), restar(), multiplicar() y dividir(). Todos los métodos recibirán 2 números enteros y el resultado también será entero. Prueba la clase con diferentes operaciones.

Ejercicio 2

Haz que el resultado sea una propiedad de la clase Calculadora. En cada método, se deberá almacenar el resultado en esa propiedad y devolverlo.

Ejercicio 3

Uno de los patrones más útiles en programación es el patrón Singleton. Sirve para crear una sola instancia (un solo objeto) de una clase. Crea la clase Singleton con una propiedad privada llamada instancia de tipo de la misma clase (Singleton). Declara el constructor de la clase como private (con lo que no se pueden crear objetos de dicha clase).

Crea un método en la clase llamado getInstance(). Este método comprobará si la propiedad instance en null. Si es así (es la primera vez que se accede al método), se creará un nuevo objeto de la misma clase y se le asignará a la propiedad instance. Si no es null, se devolverá la propiedad instance de la clase.

Comprueba que puedes crear objetos de la clase Singleton desde otra clase.

Ejercicio 4

Crea una propiedad privada en la clase anterior (Singleton) llamada contador con valor inicial = 0. Crea un método llamado checkInstance() que devuelva la frase “Instancia creada. Contador: {contador}”.

Comprueba que siempre que se llama al método checkInstance() después de usar getInstance() devuelve siempre la frase “Instancia creada. Contador: 0”.

Ejercicio 5

Uno de los problemas de crear IDs autoincrementativos en una base de datos es que, al ser secuenciales, nos pueden leer todos los datos con un robot haciendo peticiones a los recursos con IDs sucesivas. Una posible solución es utilizar identificadores únicos universales. De esta forma, nuestros IDs no son secuenciales, con lo que no es tan fácil leer nuestros datos de forma automática.

Crea la clase GeneradorId. La clase debe seguir el patrón Singleton visto anteriormente (solo debe permitir crear una instancia). En el constructor (recuerda que es privado) se generará un ID aleatorio mediante el método randomUUIID() de la clase UUID.

Crea un nuevo método getUuid(), que deberá generar un nuevo ID aleatorio y devolverlo. Usa varias veces el método para comprobar (llamando antes siempre al método que recupera la instancia) que devuelve un ID diferente cada vez.