[icono]Manual de Java Volver a índice

Capítulo 4. Clases.


El elemento básico de la programación orientada a objetos en Java es un clase. Una clase define la forma y el comportamiento de un objeto. Cualquier concepto que desee representar en su programa en Java está encapsulado en una clase. En este capítulo abordaremos en profundidad los conceptos teóricos de la programación orientada a objetos: encapsulado, herencia, y polimorfismo. Aprenderemos a crear, ampliar y crear instancias de nuestras propias clases y comenzaremos a utilizar la potencia real del estilo orientado a objetos de Java. Para crear una clase sólo se necesita un archivo fuente que contenga la palabra clave class seguida de un identificador legal y un par de llaves para el cuerpo.

class Point {
}

Las clases de Java típicas incluirán variables y métodos de instancia. Los programas en Java completos constarán por lo general de varias clases de Java de distintos archivos fuente.
Una clase define la estructura de un objeto y su interfaz funcional, conocida como métodos. Cuando se ejecuta un programa en Java, el sistema utiliza definiciones de clase para crear instancias de las clases, que son objetos reales. La forma general de una clase se muestra a continuación.

class nombre_de_clase extends nombre_de_superclase {
	type variable_de_instancia1;
	type variable_de_instancia2;
	type variable_de_instanciaN;
	type nombre_de_método1 (lista_de_parámetros) {
		cuerpo_del_método;
	}
	type nombre_de_método2 (lista_de_parámetros) {
		cuerpo_del_método;
	}
	type nombre_de_métodoN (lista_de_parámetros) {
		cuerpo_del_método;
	}
}

Aquí, nombre_de_clase y nombre_de_superclase son identificadores. La palabra clave extends se utiliza para indicar que nombre_de_clase será una subclase de nombre_de_superclase. Hay una clase incorporada, llamada Object (objeto), que está en la raíz de la jerarquía de clases de Java. Si se desea realizar una subclase de Object directamente, se puede omitir la cláusula extends.

Referencias a objeto

Cada nueva clase que se crea aņade otro tipo que se puede utilizar igual que los tipos simples. Por lo tanto, cuando se declara una nueva variable, se puede utilizar un nombre de clase como tipo. A estas variables se las conoce como referencias a objeto.
A cada instancia se le puede llamar también objeto. Cuando se declara que el tipo de una variable es una clase, tiene como valor por omisión el null, que es una referencia al tipo Object, y, por lo tanto, es compatible en tipo con todas las otras clases. El objeto null no tiene valor; es distinto del entero 0, igual que el false de boolean. Este ejemplo declara una variable p cuyo tipo es de la clase Point.

Point p; Aquí la variable p tiene un valor null.

Variables de instancia

Los datos se encapsulan dentro de una clase declarando las variables dentro de las llaves de apertura y cierre de la declaración de la clase. A las variables que se declaran en este ámbito y fuera del ámbito de un método concreto se las conoce como variables de instancia. Este ejemplo declara una clase de nombre Point, con dos variables de instancia enteras llamadas x e y.

class Point {
	int x, y;
}

El operador new

El operador new crea una única instancia de una clase y devuelve una referencia a ese objeto. Aquí se crea una nueva instancia de Point y se almacena en una variable p.

Point p = new Point();

Aquí p referencia a una instancia de Point, pero realmente no lo contiene. Se pueden crear múltiples referencias al mismo objeto.

Point p = new Point();
Point p2 = p;

Cualquier cambio realizado en el objeto refernciado por p2 afectará al mismo objeto al cual se refiere p. La asignación de p a p2 no asignó memoria ni copió nada en el objeto original. De hecho las asignaciones posteriores a p simplemente desengancharán p del objeto original sin afectar al propio objeto, como se muestra a continuación.

Point p = new Point();
Point p2 = p;
p = null;

Aunque se haya asignado p a null, p2 todavía apunta al objeto creado por el operador new.

El operador punto (.)

El operador punto se utiliza para acceder a las variables de instancia y los métodos contenidos en un objeto. Esta es la forma general de acceder a las variables de instancia utilizando el operador punto.

referencia_a_objeto.nombre_de_variable

Aquí referencia_a_objeto es una referencia a un objeto y nombre_de_variable es el nombre de la variable de instancia contenida en el objeto al que se desea acceder. El siguiente fragmento de código muestra cómo se puede utilizar el operador punto para almacenar valores en variables de instancia y para referirnos a estos.

p.x = 10;
p.y = 20;
System.out.println("x = " + p.x + " y = " + p.y);

Declaración de método

Los métodos son subrutinas unidas a una definición de una clase específica. Se declaran dentro de una definición de clase al mismo nivel que las variables de instancia. Se debe llamar a los métodos en el contexto de una instancia concreta de esa clase.
En la declaración de los métodos se define que devuelve un valor de un tipo concreto y que tiene un conjunto de parámetros de entrada.

tipo nombre_de_método ( lista_formal_de_parámetros ) {
	cuerpo_del_método;
}

El nombre_de_método es cualquier identificador legal distinto de los ya utilizados en el ámbito actual. La lista_forma_de_parámetros es una secuencia de parejas de tipo e identificador separadas por comas. Si no se desean parámetros, la declaración del método deberá incluir un par de paréntesis vacío.
Podríamos crear un método en nuestra clase Point que inicialice las variables de instancia que quedaría de la forma siguiente:

clase Point {
	int x, y;
	void init(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

this

En Java es ilegal declarar dos variables locales con el mismo nombre dentro del mismo ámbito o uno que lo incluya. Observará que hemos utilizado x e y como parámetros para el método init y en el interior del método hemos utilizado un valor de referencia especial llamado this para referirnos directamente a las variables de instancia. Si no hubiéramos utilizado this entonces x e y se hubieran referido al parámetro formal y no a las variables de instancia lo que se conoce como ocultar variables de instancia.

El método main()

Dado que no hay funciones globales en Java, se debía idear alguna manera de iniciar un programa, de ahí el sentido del método main. Puesto que en otros lenguajes (p.e. C y C++) main se utilizaba a menudo para pasar parámetros desde la línea de órdenes, un concepto perdido en los usuarios de interfaces de usuario gráficas, el main de Java también pasa esos argumentos.
El compilador de Java compilará clases que no tengan el método main. El intérprete Java, sin embargo, no tiene ningún modo de ejecutar esas clases. El método main es simplemente un lugar de inicio para que el intérprete comience. Un programa complejo tendrá docenas de clases, y sólo una de ellas necesitará tener un método main. Para los applets (programas Java que están incrustados en los visualizadores de red) no se utiliza el método main, ya que los visualizadores (o navegadores) de red siguen un convenio distinto para inicializar applets.

Llamada a método

Se llama a los métodos dentro de una instancia de un clase utilizando el operador punto (.). La forma general de una llamada:

referencia_a_objeto . nombre_de_método ( lista_de_parámetros );

Aquí, referencia_a_objeto es cualquier variable que se refiere a un objeto, nombre_de_método es el nombre de un método de la clase con la que se declaró referencia_a_objeto y lista_de_parámetros es una lista de valores o expresiones separados por comas que coinciden en número y tipo con cualquiera de los métodos declarados como nombre_de_método en la clase.
En este caso, podríamos llamar al método init sobre cualquier objeto Point.

Point p = new Point();
p.init (10, 20);

Constructores

Las clases pueden implementar un método especial llamado constructor. Un constructor es un método que inicializa un objeto inmediatamente después de su creación. Tienen exactamente el mismo nombre de la clase en la que residen; de hecho no se puede tener ningún otro método que comparta su nombre con su clase. Una vez definido, se llama automáticamente al constructor después de crear el objeto, antes de que termine el operador new.
Como extensión a nuestro ejemplo Point, podemos inicializar las dos variables de instancia cuando construimos el objeto.

class Point {
	int x, y;
	Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
}
class PointCreate {
	public static void main (String args[]) {
		Point p = new Point(10, 20);
		System.out.println("x = " + p.x + " y = " + p.y);
	}
}

Se llama al método del constructor justo después de crear la instancia y antes de que new vuelva al punto de la llamada.

Sobrecarga de método

Es posible y a menudo deseable crear más de un método con el mismo nombre, pero con listas de parámetros distintas. A esto se le llama sobrecarga de método. Se sobrecarga un método siempre que se crea un método en una clase que ya tiene un método con el mismo nombre. Aquí presentamos una versión de la clase Point que utiliza sobrecarga de método para crear un constructor alternativo.

class Point {
	int x, y;
	Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
	Point() {
		x = -1;
		y = -1;
	}
}
class PointCreateAlt {
	public static void main (String args[]) {
		Point p = new Point();
		System.out.println("x = " + p.x + " y = " + p.y);
	}
}

Este ejemplo crea un objeto Point que llama al segundo constructor sin parámetros en vez de al primero.

this en los constructores

Un refinamiento adicional es que un constructor llame a otro para construir la instancia correctamente. A menudo es una buena idea crear constructores relacionados para que las clases puedan evolucionar con más suavidad a lo largo del ciclo de desarrollo. Por ejemplo.

class Point {
	int x, y;
	Point(int x, int y) {
		this.x = x;
		this.y = y;
	}
	Point() {
		this(-1, -1);
	}
}

Herencia

La herencia es un concepto que relaciona clases una encima de otra de un manera jerárquica. Esto permite que los descendientes de una clase hereden todas las variables y métodos de sus ascendientes, además de crear los suyos propios. A estos descendientes se les llama subclases. Al padre inmediato de una clase se le llama su superclase. En este ejemplo, extendemos la clase Point para que incluya un tercer componente llamado z.

class Point3D extends Point {
	int z;
	Point3D(int x, int y, int z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}
	Point3D() {
		this(-1, -1, -1);
	}
}

La palabra clave extends se utiliza para indicar que se desea crear una subclase de Point. No se necesita declarar las variables x e y en Point3D porque se habían heredado de Point. Todas las variables x, y, z están en el mismo ámbito desde la perspectiva de una instancia de Point3D.

super

Hay una variable especial en Java llamada super, que se refiere directamente a los constructores de la superclase. Este ejemplo define una nueva versión de Point3D que utiliza el constructor super para inicializar x e y.

class Point3D extends Point {
	int z;
	Point3D(int x, int y, int z) {
		super(x, y);   // aquí se llama al constructor Point(x, y)
		this.z = z;
	}
}

Selección de método dinámica

Durante la ejecución, la referencia a un objeto se puede referir a una instancia de alguna subclase del tipo de referencia declarado. En estos casos, Java utiliza la instancia real para decidir a qué método llamar en el caso que la subclase sobrescriba el método al que se llama. Por ejemplo, estas dos clases tienen una relación subclase/superclase simple con un único método que se sobrescribe en la subclase.

class A {
	void callme() {
		System.out.println("En el método callme de A");
	}
}
class B extends A {
	void callme() {
		System.out.println("En el método callme de B");
	}
}
class Dispatch {
	public static void main(String args[]) {
		A a = new B();
		a.callme();
	}
}

Hemos declarado que la variable es de tipo A y después hemos almacenado ella una referencia a una instancia de la clase B. Cuando llamamos al método callme de a, el compilador de Java verifica que realmente A tiene un método llamado callme, pero el interprete de Java observa que la referencia es realmente una instancia de B, por lo que llama al método de B, en vez de al método de A. Esta forma de polimorfismo dinámico durante la ejecución es uno de los mecanismos más poderosos que ofrece el diseņo orientado a objetos para soportar la reutilización de código y la robustez.

final

Todos los métodos y las variables de instancia se pueden sobrescribir por defecto. Si se desea declarar que ya no se quiere permitir que las subclases sobrescriban las variables o métodos, éstos se pueden declarar como final. El modificador de tipo final implica que todas las referencias futuras a este elemento se basarán en esta definición.
Se puede utilizar final como modificador en declaraciones de método cuando se desea no permitir que las subclases sobrescriban un método concreto.

finalize

Por lo general, en Java, no hay que preocuparse por liberar memoria. Sin embargo, hay circunstancias en las que se podría desear ejecutar algún código especial cuando el sistema de recogida de basura reclama un objeto.
Se aņade un método a cualquier clase con el nombre finalize y el intérprete de Java llama al método cuando vaya a reclamar el espacio de ese objeto.

static

A veces se desea crear un método que se utiliza fuera del contexto de cualquier instancia. Todo lo que se tiene que hacer es declarar estos métodos como static (estático). Los métodos estáticos sólo pueden llamar a otros métodos static directamente, y no se pueden referir a this o super de ninguna manera. Las variables también se pueden declarar como static, pero debe ser consciente que es equivalente a declararlas como variables globales, que son accesibles desde cualquier fragmento de código. Se puede declarar un bloque static que se ejecuta una sola vez si se necesitan realizar cálculos para inializar las variables static. El ejemplo siguiente muestra una clase que tiene un método static, algunas variables static y un bloque de inicialización static.

class Static {
	static int a = 3;
	static int b;
	static void method(int x) {
		System.out.println("x = " + x);
		System.out.println("a = " + a);
		System.out.println("b = " + b);
	}
	static {
		System.out.println("bloque static inicializado");
		b = a * 4;
	}
	public static void main(String args[]) {
		method(42);
	}
}

Esta es la salida del programa.

bloque static inicializado
x = 42
a = 3
b = 12

abstract

Hay situaciones que se necesita definir una clase que declara la estructura de una abstracción dada sin promocionar una implementación completa de cada método. Se puede indicar que se necesita que ciertos métodos se sobrescriban en subclases utilizando el modificador abstract. A estos métodos se les llama a veces responsabilidad de subclase. Cualquier clase que contenga métodos declarados como abstract también se tiene que declarar como abstract. No se pueden crear instancias de dichas clases directamente con el operador new, dado que su implementación completa no está definida. No se pueden declarar constructores abstract o métodos abstract static.
Cualquier subclase de una clase abstract debe implementar todos los métodos abstract de la superclase o ser declarada también como abstract.