Universidad Carlos III de Madrid

Ingeniería de Telecomunicación

Enero-Mayo 2010 / January-May 2010

Atributos y métodos de clase (la palabra reservada static en Java)

Los métodos y atributos que habitualmente utilizamos en clase están asociados a las instancias de las clases, esto es, a los objetos. Por ejemplo, dada la clase siguiente:

public class Complejo {
        private double parteReal;
        private double parteImaginaria;

        public Complejo(double parteReal, double parteImaginaria) {
                this.parteReal = parteReal;
                this.parteImaginaria = parteImaginaria;
        }

        public Complejo(double parteReal) {
                this(parteReal, 0.0);
        }

        public Complejo() {
                this(0.0, 0.0);
        }

        public double getParteReal() {
                return parteReal;
        }

        public double getParteImaginaria() {
                return parteImaginaria;
        }

        public void sumar(Complejo numero) {
                parteReal = parteReal + numero.getParteReal();
                parteImaginaria = parteImaginaria + numero.getParteImaginaria();
        }

        public void sumar(double real) {
                parteReal = parteReal + real;
        }

        public String toString() {
                String cadena = "" + parteReal;
                if (parteImaginaria >= 0.0) {
                        cadena = cadena + "+";
                }
                cadena = cadena + parteImaginaria + "i";
                return cadena;
        }
}

los atributos parteReal y parteImaginaria pertenecen a cada objeto de la clase. De hecho, cada objeto poseerá su propia copia de los atributos. Estos podrán tomar un valor independientemente de los valores de los atributos del resto de las instancias de la clase Complejo. Gracias a ello, podemos representar a la vez varios números complejos en nuestro programa, cada uno con su propia parte real y parte imaginaria.

Lo mismo ocurre con los métodos. Cada objeto tiene su propio método toString. Lo que diferencia al método toString de una instancia de Complejo del método toString de otra instancia de Complejo es que cada uno accede a los atributos de su propia instancia, aunque ambos métodos tengan el mismo código.

Estos atributos y métodos se llaman atributos de instancia y métodos de instancia, debido precisamente al hecho que hemos visto de que están asociados a las instancias de las clases.

Existen otro tipo de atributos y métodos que no están asociados a las instancias, sino a las clases. Debido a ello, se llaman atributos de clase y métodos de clase.

De los atributos de clase existe una copia única para toda la clase, al contrario de lo que ocurre con los de instancia, para los cuales hay una copia por cada una de las instancias de la clase. De hecho, el atributo de clase existe por el hecho de existir la clase, incluso aunque no se haya creado ninguna instancia de esa clase.

Un ejemplo habitual de uso de los atributos de clase son las constantes. Por ejemplo, la clase Math, que forma parte de las bibliotecas estándar de Java, define las constantes E y PI como atributos de clase. Se definen de esta forma porque, dado que el valor de pi y e es universal, se malgastarían recursos si se guardase una copia de cada uno de ellos en cada instancia, siempre con el mismo valor.

Otro nombre que se usa habitualmente para referirse a atributos de clase es el de atributo estático. Los atributos estáticos (de clase) se declaran con la palabra reservada static. A continuación se muestra un ejemplo (aunque le mejoraremos un par de detalles un poco más adelante):

public class Complejo {
        private double parteReal;
        private double parteImaginaria;
	private static Complejo i = new Complejo(0.0, 1.0);
(...)

En el ejemplo se declara un atributo de clase llamado i, que representa la unidad imaginaria. El código es correcto, pero hay unos cuantos detalles que se pueden mejorar:

  • El atributo debería ser público, porque puede ser útil utilizarlo desde fuera de la clase Complejo. Por ejemplo, PI y E son públicos en Math por este mismo motivo.
  • No tiene sentido que nos cambien posteriormente el valor de i. Por ello, conviene que lo declaremos como final. De hecho, es habitual que este tipo de constantes se declaren con static y final a la vez.
  • Las constantes, por convenio, se nombran en mayúsculas en el lenguaje Java. Si el nombre está formado por más de una palabra, las palabras se separan por un carácter de subrayado (por ejemplo, PROPORCION_AUREA).

Si corregimos estos detalles, nos queda el siguiente código:

public class Complejo {
        private double parteReal;
        private double parteImaginaria;
	public static final Complejo I = new Complejo(0.0, 1.0);
(...)

Dado que un atributo de clase está asociado a la clase, es recomendable que se acceda a él (para leer o escribir su valor) mediante el nombre de la clase, y no mediante una referencia a un objeto, que es lo que se haría si fuese un atributo de instancia:

cero = new Complejo(-1.0, 0.0);
cero.sumar(Complejo.I);

En el ejemplo se está accediendo a I mediante la notación Complejo.I (nombre de clase, un punto, y nombre de atributo). De la misma forma se accede a Math.PI y Math.E.

Los métodos de clase están también asociados a una clase y no a una instancia. Una consecuencia importante de esto es que desde el código de un método de clase no se puede acceder a atributos ni métodos de instancia de la misma clase directamente. El motivo es que, dado que el método no está asociado a ninguna instancia, ¿cómo podría saber la máquina virtual en cuáles de los objetos de la clase están los atributos o métodos a los que acceder? Eso sí, es posible acceder a atributos de instancia a través de una referencia a un objeto, con la notación habitual (referencia, punto, nombre de atributo o método). El motivo de que se pueda a través de una referencia es que la propia referencia indica ya claramente a qué instancia pertenecen los atributos o métodos a los que queremos acceder. Otra cosa que es posible hacer es acceder desde un método de clase a otros métodos o atributos de clase de dicha clase.

Un método que siempre se define como de clase es el método especial main. Por eso siempre lo declaramos con la palabra clase static. Añadamos a la clase Complejo un método main:

public class Complejo {
        private double parteReal;
        private double parteImaginaria;
	public static final Complejo I = new Complejo(0.0, 1.0);
        (...)
        public String toString() {...}
        (...)
        public static void main(String[] args) {
            // Sentencias correctas:
            System.out.println(I);
            System.out.println(Complejo.I);
            Complejo c = new Complejo(1.0, -2.0);
            System.out.println(c.toString());

            // Sentencias incorrectas (no compilan):
            System.out.println(toString());  // ¿sobre qué instancia?
            System.out.println(parteReal);   // ¿de qué instancia?
        }
}

En el ejemplo se muestran sentencias que serían correctas, como invocar el método de instancia toString mediante una referencia a un objeto, y sentencias incorrectas, como intentar acceder directamente, sin una referencia, al mismo método toString o al atributo parteReal.

Otro ejemplo de método de clase es sqrt de la clase Math, que recibe un double y devuelve otro con su raíz cuadrada. Su invocación se realiza también mediante el nombre de la clase:

double raiz = Math.sqrt(16.4);

¿Cuándo es más adecuado declarar un atributo o método de instancia, y cuándo es más adecuado declararlo como de clase? Esto requiere un poco de experiencia, que iremos adquiriendo con la práctica. A nuestro nivel actual, nos podemos quedar con un consejo simple: salvo que tengamos un buen motivo para declarar un atributo o método como de clase, declarémoslo siempre como de instancia. ¿Cuál sería un buen motivo? Con lo que ya sabemos se nos pueden ocurrir unos cuantos:

  • Para declarar una constante cuyo valor es único para toda la clase, como el ejemplo de I.
  • Para declarar el método main.
  • Para declarar un método de una clase que no tenga sentido asociar a una instancia concreta, por ser su funcionamiento universal para toda la clase, y por tanto no acceder en ningún momento a atributos ni métodos de instancia. Un ejemplo de esto es el método sqrt del cual hemos hablado anteriormente.

Hay otras situaciones en que se deben declarar atributos y métodos de clase, pero las dejaremos para cuando tengamos un poco más de experiencia programando. De momento no es probable que nos surjan estos casos.