Está en la página 1de 17

CAPÍTULO

6
Clases

L
as clases son el núcleo de Java. Es la construcción lógica sobre la que se basa el lenguaje
Java porque define la forma y naturaleza de un objeto. De tal forma que son la base de la
programación orientada a objetos en Java. Cualquier concepto que se quiera implementar en
Java debe estar encapsulado dentro de una clase.
Dada la importancia que tienen las clases en Java, este capítulo y los próximos se dedican a este
tema. Aquí introduciremos los elementos básicos de una clase y aprenderemos cómo se usan las
clases para crear objetos. También veremos los métodos, constructores y la palabra clave this.

Fundamentos de clases
Las clases se han utilizado desde el comienzo de este libro. Sin embargo, hasta ahora se habían
utilizado sólo de una forma muy rudimentaria. Las clases creadas en los capítulos anteriores existían
simplemente para encapsular el método main( ), que ha permitido mostrar los fundamentos de la
sintaxis de Java. Como veremos, las clases son sustancialmente más potentes que las presentadas
hasta el momento.
Probablemente la característica más importante de una clase es que define un nuevo tipo de
dato. Una vez definido, este nuevo tipo de dato se puede utilizar para crear objetos de ese tipo o
clase. De este modo, una clase es un template (un modelo) para un objeto, y un objeto es una instancia
de una clase. Debido a que un objeto es una instancia de una clase, a menudo las dos palabras objeto
e instancia se usan indistintamente.

La forma general de una clase


Cuando se define una clase, se declara su forma y naturaleza exactas, especificando los datos que
contiene y el código que opera sobre esos datos. Las clases más sencillas pueden contener solamente
código o solamente datos, pero, en la práctica, la mayoría de las clases contienen datos y código.
Como veremos, el código de una clase define la interfaz con sus datos.
Una clase se declara mediante la palabra clave class. Las clases que se han utilizado hasta el
momento son realmente ejemplos muy limitados de su forma completa. Las clases pueden ser (y
normalmente lo son), mucho más complejas. La forma general de definir una clase es la siguiente:
class nombre_de_clase {
tipo variable_de:instancia1;
tipo variable_de_instancia2;

105
106 Parte I: El lenguaje Java

// ...
tipo variable_de_instanciaN;
tipo nombre_de_método1 (parámetros) {
// cuerpo del método
}
tipo nombre_de_método2 (parámetros) {
// cuerpo del método
}
// ...
tipo nombre_de_metodoN (parámetros) {
// cuerpo del método
}
}
Los datos, o variables, definidos en una clase se denominan variables de instancia. El código está
contenido en los métodos. El conjunto de los métodos y las variables definidos dentro de una
clase se denominan miembros de la clase. En la mayor parte de las clases, los métodos definidos
acceden y actúan sobre las variables de instancia, es decir, los métodos determinan cómo se
deben utilizar los datos de una clase.
Las variables definidas en una clase se llaman variables de instancia porque cada instancia
de la clase (esto es, cada objeto de la clase), contiene su propia copia de estas variables. Así, los
datos de un objeto son distintos y únicos de los de otros. Éste es un concepto importante sobre
el que volveremos más adelante.
Todos los métodos tienen el mismo formato general, similar al del método main( ) que
hemos estado utilizando hasta el momento. Sin embargo, la mayor parte de los métodos no se
especifican como static o public. Observe que la forma general de una clase no especifica un
método main( ). Las clases de Java no tienen necesariamente un método main( ). Solamente
se requiere un método main( ) si esa clase es el punto de inicio del programa. Los applets no
requieren un método main( ).

NOTA Si usted está familiarizado con C++, observará que en Java, la declaración de una clase y la
implementación de los métodos se almacenan en el mismo sitio y no se definen separadamente.
Esto, en ocasiones, da lugar a archivos .java muy largos, ya que cualquier clase debe estar
definida completamente en un solo archivo. Esta característica de diseño se estableció en Java,
ya que se supuso que, a largo plazo, tener en un sólo sitio las especificaciones, declaraciones e
implementación daría como resultado un código más fácil de mantener.

Una clase simple


Comencemos nuestro estudio con un ejemplo sencillo, la clase denominada Caja. Esta clase
define tres variables de instancia: ancho, alto y largo. En este caso, Caja no contiene método
alguno, más adelante los añadiremos.
c1ass Caja {
double ancho;
double alto;
Capítulo 6: Clases 107

double largo;
}

PARTE I
Como se ha dicho anteriormente, una clase define un nuevo tipo de dato. En este caso, el nuevo
tipo se llama Caja. Utilizaremos este nombre para declarar objetos de tipo Caja. Es importante
recordar que la declaración de una clase solamente crea un modelo o patrón y no un objeto real.
Así que el código anterior no crea ningún objeto de la clase Caja.
Para crear un objeto de tipo Caja habrá que utilizar una sentencia como la siguiente:
Caja miCaja = new Caja(); // crea un objeto de la clase Caja llamado miCaja

Cuando se ejecute esta sentencia, miCaja será una referencia a una instancia de Caja. Además,
será una realidad “física”. De momento no nos preocuparemos por los detalles de esta sentencia.
Cada vez que creemos una instancia de una clase, estaremos creando un objeto que
contiene su propia copia de cada variable de instancia definida por la clase. Por lo tanto, cada
objeto Caja contendrá sus propias copias de las variables de instancia ancho, alto y largo. Para
acceder a estas variables, utilizaremos el operador punto (.). El operador punto liga el nombre del
objeto con el nombre de una de sus variables de instancia. Por ejemplo, la siguiente sentencia
sirve para asignar a la variable ancho del objeto miCaja el valor l00.
miCaja.ancho = 100;

Esta sentencia indica al compilador que debe asignar a la copia de ancho que está contenida
en el objeto miCaja el valor 100. En general, el operador punto se usa para acceder tanto a las
variables como a los métodos de un objeto. El siguiente es un programa completo que utiliza la
clase Caja:
/* Un programa que utiliza la clase Caja.
El nombre de este archivo es CajaDemo.java
*/
class Caja {
double ancho;
double alto;
double largo;
}
// Esta clase declara un objeto de la clase Caja.
class CajaDemo {
public static void main (String args[]) {
Caja miCaja = new Caja();
double vol;
// asignación de valores a las variables del objeto miCaja
miCaja.ancho = 10;
miCaja.alto = 20;
miCaja.largo = 15;

// Se calcula el volumen de la caja


vol = miCaja.ancho * miCaja.alto * miCaja.largo;
System.out.println ("El volumen es " + vol);
}
}
108 Parte I: El lenguaje Java

Al archivo que contiene este programa se le debe llamar CajaDemo.java, ya que el método
main( ) está dentro de la clase denominada CajaDemo, no en la clase denominada Caja.
Cuando se compila este programa, se generan dos archivos .class, uno para Caja y otro para
CajaDemo. El compilador Java crea automáticamente para cada clase su propio archivo .class.
No es necesario que las clases Caja y CajaDemo estén en el mismo archivo fuente. Se puede
escribir cada clase en su propio archivo, es decir, en los archivos Caja.java y CajaDemo.java,
respectivamente.
Para ejecutar este programa, debemos ejecutar CajaDemo.class, y obtendremos la siguiente
salida:
El volumen es 3000.0

Tal y como se ha visto anteriormente, cada objeto tiene sus propias copias de las variables
de instancia. Esto significa que si tenemos dos objetos Caja, cada uno tiene sus propias copias de
largo, ancho y alto. Es importante tener en cuenta que los cambios en las variables de instancia
de un objeto no afectan a las variables de otro. Por ejemplo, el siguiente programa declara dos
objetos Caja.
// Este programa declara dos objetos Caja.
class Caja {
double ancho;
double alto;
double largo;
}
class CajaDemo2{
public static void main (String args[]) {
Caja miCajal = new Caja();
Caja miCaja2 = new Caja();
double vol;
// asignación de valores a las variables de la instancia miCaja1
miCaja1.ancho = 10;
miCaja1.alto = 20;
miCaja1.largo = 15;
/* asignación de valores diferentes a las variables de la instancia miCaja2
*/
miCaja2.ancho = 3;
miCaja2.alto = 6;
miCaja2.1argo = 9;
// calcula el volumen de la primera caja
vol = miCaja1.ancho * miCaja1.alto * miCaja1.largo;
System.out.println("E1 volumen es " + vol);
// calcula el volumen de la segunda caja
vol = miCaja2.ancho * miCaja2.alto * miCaja2.largo;
System.out.println("E1 volumen es " + vol);
}
}

La salida que se obtiene es la siguiente:


Capítulo 6: Clases 109

El volumen es 3000.0
El volumen es 162.0

PARTE I
Como se puede comprobar, los datos de miCajal son completamente independientes de los
datos contenidos en miCaja2.

Declaración de objetos
Tal y como se acaba de explicar, cuando se crea una clase, se está creando un nuevo tipo de datos
que se utilizará para declarar objetos de ese tipo. Sin embargo, la obtención de objetos de una
clase es un proceso que consta de dos etapas. En primer lugar, se debe declarar una variable del
tipo de la clase. Esta variable no define un objeto, sino que simplemente es una referencia a un
objeto. En segundo lugar, se debe obtener una copia física del objeto y asignarla a esa variable.
Para ello se utiliza el operador new que asigna dinámicamente, durante el tiempo de ejecución,
memoria a un objeto y devuelve una referencia al mismo. Esta referencia es algo así como la
dirección en memoria del objeto creado por la operación new. Luego se almacena esta referencia
en la variable. Todos los objetos de una clase en Java se asignan dinámicamente. Veamos con más
detalle este procedimiento.
En los ejemplos anteriores se utilizó una línea similar a la siguiente para declarar un objeto
de la clase Caja:
Caja miCaja = new Caja();
Esta sentencia combina las dos etapas descritas anteriormente y, para mostrar más claramente
cada una de ellas, dicha sentencia se puede volver a escribir del siguiente modo:
Caja miCaja; // declara la referencia a un objeto
miCaja = new Caja(); // reserva espacio en memoria para el objeto
La primera línea declara miCaja como una referencia a un objeto de la clase Caja. Después
de que se ejecute esta línea, miCaja contiene el valor null, que indica que todavía no apunta a
un objeto real. Cualquier intento de utilizar miCaja en esta situación dará lugar a un error de
compilación. En la siguiente línea se reserva memoria para un objeto real y se asigna miCaja
como la referencia a dicho objeto. Una vez que se ejecute la segunda línea, ya se puede utilizar
miCaja como si fuera un objeto de la clase Caja. En realidad, miCaja simplemente contiene la
dirección de memoria del objeto real. El efecto de estas dos líneas se describe en la Figura 6.1.

NOTA Los lectores familiarizados con C/C++ habrán observado, probablemente, que las referencias
a objetos son muy semejantes a los apuntadores. Básicamente, esto es correcto. Una referencia
a objeto es semejante a un apuntador a memoria. La principal diferencia –y la clave para la
seguridad de Java– es que no se pueden manipular las referencias tal y como se hace con los
apuntadores. Por lo tanto, una referencia no puede apuntar a una dirección arbitraria de memoria
ni se puede manipular como si fuese entero.

El operador new
Como se explicó, el operador new reserva memoria dinámicamente para un objeto. Su forma
general es:
variable = new nombre_de_clase ();
110 Parte I: El lenguaje Java

FIGURA 6-1 Declaración Efecto


Declaración de un objeto
de tipo Caja. nulo
Caja miCaja;
miCaja

miCaja = nueva Caja(); Ancho


miCaja Alto
Largo
objeto Caja

Aquí, variable es una variable cuyo tipo es la clase creada, y el nombre_de_clase es el nombre de
la clase que está siendo instanciada. El nombre de la clase seguido de paréntesis está especificando
una llamada al método constructor de la clase. Un constructor define lo que ocurre cuando se crea
un objeto de una clase. Los constructores son una parte importante de todas las clases y tienen
muchos atributos significativos. En la práctica, la mayoría de las clases definen explícitamente
sus propios constructores en la definición de la clase. Cuando no se definen explícitamente, Java
suministra automáticamente el constructor por omisión. Esto es lo que ha ocurrido con la clase
Caja. Por ahora seguiremos utilizando el constructor por omisión, aunque pronto veremos cómo
definir nuestros propios constructores.
En este momento nos podríamos plantear la siguiente pregunta: ¿Por qué no es necesario
utilizar el operador new en el caso de los enteros o de los caracteres? La respuesta es que los
tipos primitivos no se implementan como objetos sino como variables “normales”. Esto se
hace así con el objeto de lograr una mayor eficiencia. Los objetos tienen muchas características
y atributos que obligan a Java a tratarlos de forma diferente a la que utiliza con los tipos
básicos. Al no aplicar la misma sobrecarga a los tipos primitivos que a los objetos, Java puede
implementar a los tipos básicos más eficientemente. Más adelante se verán versiones con
objetos de los tipos primitivos, las cuales están disponibles para su uso en situaciones en las que
se necesitan objetos completos para trabajar con valores primitivos.
Es importante tener en cuenta que el operador new reserva memoria para un objeto durante
el tiempo de ejecución. La ventaja de hacerlo así es que el programa crea exactamente los objetos
que necesita durante su ejecución. Sin embargo, dado que la memoria disponible es finita, puede
ocurrir que ese operador new no sea capaz de reservar memoria para un objeto porque no exista
ya memoria disponible. Si esto ocurre, se producirá una excepción en tiempo de ejecución. (En el
Capítulo 10 se verá la gestión de ésta y otras excepciones). En los ejemplos que se presentan en
este libro no es necesario que nos preocupemos por el hecho de quedamos sin memoria, pero sí
es preciso considerar esta posibilidad en los programas reales.
Volvamos de nuevo a la distinción entre clase y objeto. Una clase crea un nuevo tipo de
dato que se utilizará para crear objetos, es decir, una clase crea un marco lógico que define las
relaciones entre sus miembros. Cuando se declara un objeto de una clase, se está creando una
instancia de esa clase. Por lo tanto, una clase es una construcción lógica, mientras que un objeto
Capítulo 6: Clases 111

tiene una realidad física, esto es, un objeto ocupa un espacio de memoria. Es importante tener en
cuenta esta distinción.

PARTE I
Asignación de variables de referencia a objetos
Las variables de referencia a objetos actúan de una forma diferente a la que se podría esperar
cuando tiene lugar una asignación. Por ejemplo, ¿qué hace el siguiente fragmento de código?
Caja bl = new Caja();
Caja b2 = bl;

Podríamos pensar que a b2 se le asigna una referencia a una copia del objeto que se referencia
mediante bl, es decir, que bl y b2 se refieren a objetos distintos. Sin embargo, esto no es así.
Cuando este fragmento de código se ejecute, bl y b2 se referirán al mismo objeto. La asignación
de bl a b2 no reserva memoria ni copia parte alguna del objeto original. Simplemente hace que
b2 se refiera al mismo objeto que bl. Por lo tanto, cualquier cambio que se haga en el objeto a
través de b2 afectará al objeto al que se refiere bl, ya que, en definitiva, se trata del mismo objeto.
Esta situación se representa gráficamente a continuación.

Ancho
b1
Alto objeto Caja
Largo

b2

Aunque bl y b2 se refieren al mismo objeto, no están relacionados de ninguna otra forma. Por
ejemplo, una asignación posterior a bl simplemente desenganchará bl del objeto original sin
afectar al objeto o a b2. Por ejemplo:
Caja bl = new Caja();
Caja b2 = bl;
// ...
bl = null;

En este caso, bl ha sido asignado a null, pero b2 todavía apunta al objeto original.

RECUERDE Cuando se asigna una variable de referencia a objeto a otra variable de referencia a
objeto, no se crea una copia del objeto, sino que sólo se hace una copia de la referencia.

Métodos
Como se mencionó al comienzo de este capítulo, las clases están formadas por variables de
instancia y métodos. El concepto de método es muy amplio ya que Java les concede una gran
potencia y flexibilidad. La mayor parte del siguiente capítulo se dedica a los métodos. Sin
112 Parte I: El lenguaje Java

embargo, es preciso introducir en este momento algunas nociones básicas para empezar a
incorporar métodos a las clases.
La forma general de un método es la siguiente:
tipo nombre_de_método (parámetros) {
// cuerpo del método
}
Donde tipo especifica el tipo de dato que devuelve el método, el cual puede ser cualquier tipo
válido, incluyendo los tipos definidos mediante clases creadas por el programador. Cuando el
método no devuelve ningún valor, el tipo devuelto debe ser void. El nombre del método se
especifica en nombre_de_método, que puede ser cualquier identificador válido que sea distinto
de los que ya están siendo utilizados por otros elementos del programa. Los parámetros son
una sucesión de pares de tipo e identificador separados por comas. Los parámetros son,
esencialmente, variables que reciben los valores de los argumentos que se pasa a los métodos
cuando se les llama. Si el método no tiene parámetros, la lista de parámetros estará vacía.
Los métodos que devuelven un tipo diferente del tipo void devuelven el valor a la rutina
llamante mediante la siguiente forma de la sentencia return:
return valor;
Donde valor es el valor que el método retorna.
En los siguientes apartados se verá cómo crear distintos tipos de métodos, incluyendo los
que tienen parámetros y los que devuelven valores.

Adición de un método a la clase Caja


Aunque crear una clase que contenga solamente datos es correcto, rara vez se hace. En la
mayor parte de las ocasiones se usarán métodos para acceder a las variables de instancia
definidas por la clase. De hecho los métodos definen la interfaz para la mayor parte de
las clases. Esto permite que la clase oculte la estructura interna de los datos detrás de las
abstracciones de un conjunto de métodos. Además de definir métodos que proporcionen el
acceso a los datos, también se pueden definir métodos cuyo propósito sea el de ser utilizados
internamente por la propia clase.
Comencemos por añadir un método a la clase Caja. En los programas anteriores se
calculaba el volumen de una caja en la clase CajaDemo; sin embargo, el volumen de la caja
depende del tamaño de la caja. Por este motivo tiene más sentido que sea la clase Caja la que se
encargue del cálculo del volumen. Para ello se debe añadir un método a la clase Caja, tal y como
se muestra a continuación:
// Este programa incluye un método en la clase Caja.
class Caja {
double ancho;
double alto;
double largo;
// presenta el volumen de una caja
void volumen () {
System.out.print ("El volumen es ");
System.out.println (ancho * alto * largo);
}
}
Capítulo 6: Clases 113

class CajaDemo3 {
public static void main (String args[]) {
Caja miCaja1 = new Caja();

PARTE I
Caja miCaja2 = new Caja();

// Se asignan valores a las variables del objeto miCaja1


miCaja1.ancho = 10;
miCaja1.alto = 20;
miCaja1.largo = 15;

/* asigna diferentes valores a las variables


del objeto de miCaja2 */
miCaja2.ancho = 3;
miCaja2.alto = 6;
miCaja2.largo = 9;

// muestra el volumen de la primera caja


miCaja1.volumen ();

// muestra el volumen de la segunda caja


miCaja2.volumen ();
}
}

Este programa genera la siguiente salida, que es la misma que se obtuvo en la versión
anterior.
El volumen es 3000.0
El volumen es 162.0

Analicemos más detenidamente las siguientes dos líneas de código:


miCaja1.volumen();
miCaja2.volumen();

La primera invoca al método volumen( ) en miCajal, es decir, llama al método volumen( ),


relativo al objeto miCajal, utilizando el nombre del objeto seguido por el operador punto. Por
lo tanto, la llamada al método miCaja1.volumen( ) presenta el volumen de la caja definida
por miCajal, y la llamada a miCaja2.volumen( ) presenta el volumen de la caja definida por
miCaja2. Cada vez que se llama a volumen( ) se presenta el volumen de la caja especificada.
Si no está familiarizado con el concepto de llamada a un método, el siguiente análisis
le ayudará a aclarar las cosas. Cuando se ejecuta miCaja1.volumen( ), el intérprete de Java
transfiere el control al código definido dentro del método volumen( ). Una vez que estas
sentencias se han ejecutado, el control es devuelto a la rutina llamante, y la ejecución continúa
en la línea de código que sigue a la llamada. En un sentido más general, un método de Java es
una forma de implementar subrutinas.
Dentro del método volumen( ), es muy importante observar que la referencia a las variables
de instancia ancho, alto y largo es directa sin que vayan precedidas del nombre de un objeto o
del operador punto. Cuando un método utiliza una variable de instancia definida por su propia
clase, lo hace directamente, sin referencia explícita a un objeto y sin utilizar el operador punto.
Siempre que se llama a un método, esté está relacionado con algún objeto de su clase. Una vez
que la llamada tiene lugar, el objeto es conocido.
114 Parte I: El lenguaje Java

Por lo tanto, en un método no es necesario especificar el objeto por segunda ocasión. Esto
significa que ancho, alto y largo dentro de volumen( ) se refieren implícitamente a las copias de
esas variables que están en el objeto que llama a volumen( ).
Revisando, cuando se accede a una variable de instancia por un código que no forma parte
de la clase en la que está definida la variable de instancia, se debe hacer mediante un objeto
utilizando el operador punto. Sin embargo, cuando el código forma parte de la misma clase en
la que se define la variable de instancia a la que accede dicho código, la referencia a esa variable
puede ser directa. Esto se aplica de la misma forma a los métodos.

Devolución de un valor
La implementación del método volumen( ) realiza el cálculo del volumen de una caja dentro de
la clase Caja a la que pertenece, sin embargo esta implementación no es la mejor. Por ejemplo,
puede ser un problema si en otra parte del programa se necesita el valor del volumen de la caja,
pero sin que sea necesario presentar dicho valor. Una mejor forma de implementar el método
volumen( ) es realizar el cálculo del volumen y devolver el resultado a la parte del programa que
llama al método. En el siguiente ejemplo, que es una versión mejorada del programa anterior, se
hace eso.
// Ahora volumen() devuelve el volumen de una caja.

class Caja {
double ancho;
double alto;
double largo;

// cálculo y devolución del valor


double volumen() {
return ancho * alto * largo;
}
}

class CajaDemo4 {
public static void main (String args[]) {
Caja miCajal = new Caja();
Caja miCaja2 = new Caja();
double vol;

// se asigna valores a las variables de instancia de miCaja1


miCaja1.ancho = 10;
miCaja1.alto = 20;
miCaja1.largo = 15;

/* se asigna diferentes valores a las variables


de instancia de miCaja2 */
miCaja2.ancho = 3;
miCaja2.alto = 6;
miCaja2.largo = 9;

// se obtiene el volumen de la primera caja


vol = miCajal. volumen ();
System.out.println ("El volumen es " + vol);
Capítulo 6: Clases 115

// se obtiene el volumen de la segunda caja


vol = miCaja2 .volumen ();
System.out.println ("El volumen es " + vol);

PARTE I
}
}

En este ejemplo, cuando se llama al método volumen( ), se coloca en la parte derecha de la


sentencia de asignación. En la parte izquierda está la variable, en este caso vol, que recibirá el
valor devuelto por volumen( ). Por lo tanto, después de que se ejecute la sentencia:
vol = miCajal.volumen();

el valor de miCajal.volumen( ) es 3,000 y este valor se almacena en vol.


Dos puntos importantes a considerar sobre la devolución de valores son:
• El tipo de datos devueltos por un método debe ser compatible con el tipo de retorno
especificado por el método. Por ejemplo, si el tipo de retorno de un método es booleano,
no se puede devolver un entero.
• La variable que recibe el valor devuelto por un método (vol, en este caso) debe ser
también compatible con el tipo de retorno especificado por el método.
Una cuestión más: el programa anterior se puede escribir de forma más eficiente teniendo
en cuenta que realmente no es necesario que exista la variable vol. Se puede utilizar la llamada
a volumen( ) directamente en la sentencia println( ), como se muestra a continuación.
System.out.println(“El volumen es “ + miCaja1.volumen());

En este caso, cuando se ejecuta println( ), se llama directamente a miCajal.volumen( ) y se


pasa su valor a println( ).

Métodos con parámetros


Mientras que algunos métodos no necesitan parámetros, la mayoría sí. Los parámetros
permiten generalizar un método, es decir, un método con parámetros puede operar sobre gran
variedad de datos y/o ser utilizado en un gran número de situaciones diferentes. Para ilustrar
este punto usaremos un ejemplo muy sencillo. El siguiente método devuelve el cuadrado del
número 10:
int cuadrado ()
{
return 10 * 10;
}

Efectivamente este método devuelve el cuadrado de 10, pero su utilización es muy limitada.
Sin embargo, si se modifica de forma que tome un parámetro, como se muestra a continuación,
entonces se consigue que cuadrado( ) tenga una mayor utilidad.
int cuadrado(int i)
{
return i * i;
}
116 Parte I: El lenguaje Java

Ahora, cuadrado( ) devolverá el cuadrado de cualquier valor usado en la llamada al método, es


decir, cuadrado( ) es ahora un método de propósito general que puede calcular el cuadrado de
cualquier número entero.
Aquí está un ejemplo de ello:
int x, y;
x = cuadrado(5); // x es igual a 25
x = cuadrado(9); // x es igual a 81
y = 2;
x = cuadrado (y) ; // x es igual a 4

En la primera llamada a cuadrado( ), se pasa el valor 5 al parámetro i. En la segunda, i recibirá el


valor 9. La tercera invocación pasa el valor de y, que en este ejemplo es 2. Como muestran estos
ejemplos, cuadrado( ) devuelve el cuadrado de cualquier valor que se pase al método.
Es importante tener una idea precisa de estos dos términos, parámetros y argumentos. Un
parámetro es una variable, definida por un método, que recibe un valor cuando se llama al
método. Por ejemplo, en cuadrado( ) el parámetro es i. Un argumento es un valor que se pasa
a un método cuando se le llama. Por ejemplo, cuadrado(l00) pasa 100 como un argumento.
Dentro de cuadrado( ), el parámetro i recibe ese valor.
Se puede utilizar un método parametrizado para mejorar la clase Caja. En los ejemplos
anteriores, las dimensiones de cada caja se establecen por separado mediante una sucesión de
sentencias:
micaja1.ancho = 10;
miCaja1.alto = 20;
micaja1.largo = 15;

Este código funciona, pero presenta problemas por dos razones. En primer lugar, resulta torpe y
propenso a errores; por ejemplo, fácilmente se puede olvidar dar valor a una de las dimensiones.
En segundo lugar, en los programas de Java correctamente diseñados, sólo se puede acceder a
las variables de instancia por medio de métodos definidos por sus clases. De ahora en adelante,
permitiremos alterar el comportamiento de un método, pero no el de una variable de instancia
accesible desde el exterior de la clase.
Una mejor solución es crear un método que tome las dimensiones de la caja dentro de sus
parámetros y establezca las variables de instancia apropiadamente. En el siguiente programa se
implementa este concepto:
// Este programa usa un método parametrizado.
class Caja {
double ancho;
double alto;
double largo;
// cálculo y devolución del volumen
double volumen () {
return ancho * alto * largo;
}
// establece las dimensiones de la caja
void setDim (double w, double h, double d) {
ancho = w;
Capítulo 6: Clases 117

alto = h;
largo = d;
}

PARTE I
}
class CajaDemo5 {
public static void main (String args[]) {
Caja miCajal = new Caja();
Caja miCaja2 = new Caja();
double vol;
// inicializa cada caja
miCaja1.setDim (10, 20, 15);
miCaja2.setDim (3, 6, 9);
// calcula el volumen de la primera caja
vol = miCaja1.volumen ();
System.out.println ("El volumen es " + vol);
// calcula el volumen de la segunda caja
vol = miCaja2.volumen ();
System.out.println ("El volumen es " + vol);
}
}

El método setDim( ) se utiliza para establecer las dimensiones de cada caja. Por ejemplo,
cuando se ejecuta:
miCaja1.setDim(10, 20, 15);

el valor 10 se copia en el parámetro w; el valor 20, en el parámetro h, y el valor 15, en el


parámetro d. Dentro del método setDim( ) los valores de w, h y d se asignan a las variables
ancho, alto y largo, respectivamente.
Para muchos lectores, los conceptos presentados en los apartados anteriores les resultarán
familiares. Sin embargo, si conceptos tales como la llamada a métodos, argumentos y parámetros
le resultan nuevos, puede resultar conveniente que dedique algún tiempo a familiarizarse con
ellos antes de seguir adelante, puesto que son fundamentales para la programación en Java.

Constructores
El proceso de inicializar todas las variables en una clase cada vez que se crea una instancia puede
resultar tedioso, incluso cuando se añaden métodos como setDim( ). Puede resultar más simple
y más conciso realizar todas las inicializaciones cuando el objeto se crea por primera vez. El
proceso de inicialización es tan común que Java permite que los objetos se inicialicen cuando
son creados. Esta inicialización automática se lleva a cabo mediante el uso de un constructor.
Un constructor inicializa un objeto inmediatamente después de su creación. Tiene el
mismo nombre que la clase en la que reside y, sintácticamente, es similar a un método. Una
vez definido, se llama automáticamente al constructor después de crear el objeto y antes de
que termine el operador new. Los constructores resultan un poco diferentes, a los métodos
convencionales, porque no devuelven ningún tipo, ni siquiera void. Esto se debe a que el
tipo implícito que devuelve un constructor de clase es el propio tipo de la clase. La tarea del
constructor es inicializar el estado interno de un objeto de forma que el código que crea a la
118 Parte I: El lenguaje Java

instancia pueda contar con un objeto completamente inicializado que pueda ser utilizado
inmediatamente.
Se puede modificar el ejemplo anterior de forma que las dimensiones de la caja se inicialicen
automáticamente cuando se construye el objeto. Para ello se sustituye el método setDim( ) por
un constructor. Comencemos definiendo un constructor sencillo que simplemente asigne los
mismos valores a las dimensiones de cada caja.
/* La clase Caja usa un constructor para inicializar
las dimensiones de las caja.
*/
class Caja {
double ancho;
double alto;
double largo;
// Este es el constructor para Caja.
Caja() {
System.out.println("Constructor de Caja");
ancho = 10;
alto = 10;
largo = 10;
}
// calcula y devuelve el volumen
doub1e volumen () {
return ancho * alto * largo;
}
}
c1ass CajaDemo6 {
pub1ic static void main (String args[]) {
// declara, reserva memoria, e inicial iza objetos de tipo Caja
Caja miCajal = new Caja();
Caja miCaja2 = new Caja();
doub1e vol;
// obtiene el volumen de la primera caja
vol = miCajal.volumen () ;
System.out.println ("E1 volumen es " + vol);
// obtiene el volumen de la segunda caja
vol = miCaja2.vo1umen ();
System.out.println ("El volumen es " + vol);
}
}

Cuando se ejecuta este programa, genera el siguiente resultado:


Constructor de Caja
Constructor de Caja
El volumen es 1000.0
El volumen es 1000.0

Como puede observarse, miCajal y miCaja2 han sido inicializados por el constructor
de Caja( ) en el momento de su creación. Como el constructor asigna el mismo valor, 10, a
Capítulo 6: Clases 119

todas las dimensiones de la caja, miCajal y miCaja2 tienen el mismo volumen. La sentencia
println( ) dentro de Caja( ) sólo sirve para mostrar cómo funciona el constructor. La mayoría

PARTE I
de los constructores no presentan alguna salida, sino que simplemente inicializan un objeto.
Antes de seguir, examinemos de nuevo el operador new. Cuando se reserva espacio de
memoria para un objeto, se hace de la siguiente forma:
variable = new nombre_de_clase ();
Ahora resulta más evidente la necesidad de los paréntesis después del nombre de clase. Lo que
ocurre realmente es que se está llamando al constructor de la clase. Por lo tanto, en la línea:
Caja miCajal = new Caja();

new Caja( ) es la llamada al constructor de Caja( ). Cuando no se define explícitamente un


constructor de clase, Java crea un constructor por defecto de clase. Este es el motivo de que la
línea anterior funcionara correctamente en las versiones previas de Caja en las que no se definía
constructor alguno. El constructor por omisión asigna, automáticamente, a todas las variables el
valor inicial igual a cero. Para clases sencillas, resulta suficiente utilizar el constructor por defecto,
pero no para clases más sofisticadas. Una vez definido el propio constructor, el constructor por
omisión ya no se utiliza.

Constructores con parámetros


Aunque el constructor de Caja( ) en los ejemplos previos inicializa un objeto Caja, no es muy
útil que todas las cajas tengan las mismas dimensiones. Necesitamos una forma de construir
objetos Caja de diferentes dimensiones. La solución más sencilla es añadir parámetros al
constructor, con lo que se consigue que éste sea mucho más útil. La siguiente versión de Caja
define un constructor con parámetros que asigna a las dimensiones de la caja los valores
especificados por esos parámetros.
Prestemos especial atención a la forma en que se crean los objetos de Caja.
/* Aquí, Caja usa un constructor parametrizado para
inicializar las dimensiones de una caja.
*/
class Caja {
double ancho;
double alto;
double largo;
// Este es el constructor de Caja.
Caja (double w, double h, double d) {
ancho = w;
alto = h;
largo = d;
}
// calcula y devuelve el volumen
double volumen () {
return ancho * alto * largo;
}
}
class CajaDemo7 {
public static void main(String args[]) {
120 Parte I: El lenguaje Java

// declara, reserva memoria, e inicializa los objetos de Caja


Caja miCajal = new Caja(10, 20, 15);
Caja miCaja2 = new Caja(3, 6, 9);
double vol;
// obtiene el volumen de la primera caja
vol = miCaja1.volumen();
System.out.println ("El volumen es " + vol);
// obtiene el volumen de la segunda caja
vol = miCaja2.volumen();
System.out.println ("El volumen es " + vol);
}
}

La salida de este programa es la siguiente:


El volumen es 3000.0
El volumen es 162.0

Como se puede ver, cada objeto es inicializado como se especifica en los parámetros de su
constructor. Por ejemplo, en la siguiente línea:
Caja miCajal = new Caja(l0, 20, 15);

Los valores 10, 20 y 15 se pasan al constructor de Caja( ) cuando new crea el objeto. Así las
copias de ancho, alto y largo de miCajal contendrán los valores 10, 20 y 15, respectivamente.

La palabra clave this


En algunas ocasiones, un método necesita referirse al objeto que lo invocó. Para permitir
esta situación, Java define la palabra clave this, la cual puede ser utilizada dentro de cualquier
método para referirse al objeto actual. this es siempre una referencia al objeto sobre el que
ha sido llamado el método. Se puede usar this en cualquier lugar donde esté permitida una
referencia a un objeto del mismo tipo de la clase actual.
Consideremos la siguiente versión de Caja( ) para comprender mejor cómo funciona this.
// Un uso redundante de this.
Caja (double w, double h, double d) {
this.ancho = w;
this.alto = h;
this.largo = d;
}

Esta versión de Caja( ) opera exactamente igual que la versión anterior. El uso de this es
redundante pero correcto. Dentro de Caja( ), this se refiere siempre al objeto llamante. Aunque
en este caso es redundante, en otros contextos this es útil; uno de esos contextos se explica en la
siguiente sección.

Ocultando variables de instancia


En Java es ilegal declarar a variables locales con el mismo nombre dentro del mismo contexto.
Curiosamente, puede haber variables locales, desde parámetros formales hasta métodos, que
coincidan en parte con los nombres de las variables de instancia de clase. Sin embargo, cuando
Capítulo 6: Clases 121

una variable tiene el mismo nombre que una variable de instancia, la variable local esconde a
la variable de instancia. Por esta razón, ancho, alto y largo no se utilizaron como los nombres

PARTE I
de los parámetros en el constructor Caja( ) dentro de la clase Caja. Si se hubieran utilizado,
entonces ancho se hubiera referido al parámetro formal, ocultando la variable de instancia
ancho. Si bien normalmente será más sencillo utilizar nombres diferentes, this permite hacer
referencia directamente al objeto y resolver de esta forma cualquier colisión entre nombres,
que pudiera darse entre las variables de instancia y las variables locales. La siguiente versión de
Caja( ) utiliza ancho, alto, y largo como nombres de parámetros y, después, this para acceder a
variables de instancia que tienen los mismos nombres.
// Uso de this para resolver colisiones en el espacio de nombres
Caja (double ancho, double alto, double largo) {
this.ancho = ancho;
this.alto = alto;
this.largo = largo;
}

NOTA El uso de this en este contexto puede ser confuso, y algunos programadores tienen la
precaución de no utilizar nombres de variables locales y parámetros formales que puedan ocultar
variables de instancia. Otros programadores creen precisamente lo contrario, es decir, que puede
resultar conveniente, para una mayor claridad, utilizar los mismos nombres, y usan this para
superar el ocultamiento de la variable de instancia. Adoptar una tendencia u otra es una cuestión
de preferencias.

Recolección automática de basura


Ya que en Java se reserva espacio de memoria para los objetos dinámicamente mediante la
utilización de operador new, surge la pregunta sobre cómo destruir los objetos y liberar el
correspondiente espacio de memoria para su posterior utilización. En algunos lenguajes como
C++, la memoria asignada dinámicamente debe ser liberada de forma manual mediante
el operador delete. Esta situación se resuelve en Java de forma diferente. Java gestiona
automáticamente la liberación de la memoria. Esta técnica se denomina recolección de basura y
consiste en lo siguiente: cuando no existen referencias a un objeto, se asume que el objeto no se
va a necesitar más, y la memoria ocupada por dicho objeto puede ser liberada. No es necesario
destruir objetos explícitamente como en C++. La recolección de basura sólo se produce
esporádicamente durante la ejecución del programa. No se producirá simplemente porque haya
uno o dos objetos que no se utilicen más. Los diferentes intérpretes de Java siguen distintos
procedimientos de recolección de basura, pero en realidad no hay que preocuparse mucho por
ello al escribir nuestros programas.

El método finalize( )
En algunas ocasiones es necesario realizar alguna acción cuando se destruye un objeto. Por
ejemplo, si un objeto sustenta algún recurso que no pertenece a Java, como un descriptor de
archivo o un tipo de letra del sistema de ventanas, entonces es necesario liberar estos recursos
antes de destruir el objeto.

También podría gustarte