====== 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 [[https://en.wikipedia.org/wiki/Singleton_pattern|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 [[https://es.wikipedia.org/wiki/Identificador_%C3%BAnico_universal|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 [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/UUID.html|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.