En PC Resumen hablaremos de los fundamentos de las clases. Las clases son el núcleo de Java. Son la construcción lógica sobre la que se basa el lenguaje Java, definen la forma y la naturaleza de un objeto y constituyen los fundamentos de la programación orientada a objetos en Java. Cualquier concepto que se quiera implementar en un programa Java debe encapsularse dentro de una clase.
Fundamentos de las clases
Las clases las hemos utilizado desde el principio, pero hasta ahora sólo lo habíamos visto en la forma más simple, dado que existían siquiera para encapsular el método main.
Lo más importante que hay que comprender sobre una clase es que define un nuevo tipo de datos. Una vez se ha definido este nuevo tipo se puede utilizar para crear objetos de este tipo. Dado que un objeto es una instancia de una clase, a menudo veremos que estas dos palabras se confunden.
Forma general de una clase
Para definir una clase, se declara su forma y su naturaleza exactas, especificando los datos que contiene y el código que opera sobre ellas. Mientras que pueden haber clases muy simples que sólo contienen código o datos, en la práctica las clases contienen tanto código como datos. Como podremos ver, el código de una clase define la interfaz con sus datos.
Una clase se declara mediante la palabra clave class. La forma general de definir una clase es la siguiente:
class nombre de la clase {
tipos variable1;
......
tipos variableN;
tipos nombre del metodo1 (lista de parámetros) {
código del método
}
.......
tipos nombre del método (lista de parámetros) {
código del método
}
}
Los datos, o variables que se definen dentro de una clase se llaman variables de instancia, el código que contienen denomina método y en conjunto los métodos y variables definidos en la clase se denominan miembros de la clase. En la mayoría de las clases, los métodos definidos acceden y actúan sobre las variables de instancia, es decir, los métodos determinan cómo deben utilizarse los datos de una clase.
Las variables definidas llaman variables de instancia para cada instancia de una clase, es decir cada objeto de la clase, contiene su copia de las variables. Por lo tanto, los datos de un objeto están separadas y son individuales de los datos de otro objeto de la misma clase.
Todos los métodos tienen el mismo formato general que el main. De todas formas, la mayoría de métodos no se especifican como static o público. Fíjese en que la forma general de la clase no necesita tener el método main, sólo se utiliza si esta clase es el punto de entrada del programa.
Ejemplo con una clase sencilla
Veremos cómo hacer una clase con un ejemplo muy simple: la clase Caja que define tres variables de instancia: anchura, altura y profundidad. En este caso no tenemos ningún método pero ya le añadiremos después.
class Caja {
double anchura;
double altura;
double profundidad;
}
Como hemos dicho, una clase define un nuevo tipo de datos. En este caso, el nuevo tipo se denomina Caja. Utilizaremos este nombre para declarar objetos de la clase Caja. Cabe recordar que la declaración de una clase sólo crea una plantilla, no un objeto real. El código anterior no crea ningún objeto del tipo Caja. Para poder hacerlo deberemos utilizar una sentencia como la siguiente:
Caja cajaMia = new Caja () // Crea un objeto Caja llamado cajaMia
Cuando se ejecute esta sentencia, cajaMia será una referencia a Caja. Además tendrá una realidad "física". Más adelante veremos bien cómo funciona, pero puede pensar que el operador new es similar al malloc de C.
Cada vez que creamos una instancia de una clase, estaremos creando un objeto que contiene una copia propia de cada variable de instancia definida por la clase. Por lo tanto cada objeto de tipo Caja contendrá copias propias de las variables de instancia anchura, altura y profundidad. Para acceder a las variables, se utiliza el operador punto (.). Este operador vincula el nombre del objeto con el nombre de una variable de instancia (piensa que es un funcionamiento similar al de los struct de C).
Por ejemplo, si quisiéramos darle el valor 100 a la variable anchura de cajaMia, haríamos:
cajaMia.anchura = 100;
Esta sentencia le indica al compilador que debe asignar a la copia de ancho que contiene cajaMia el valor 100. En general, el operador punto se utiliza para acceder tanto a las variables de instancia como los métodos de un objeto.
/ *
El siguiente programa utiliza la clase Caja.
El nombre de este archivo es CajaDemo.java
* /
class Caja {
double anchura;
double altura;
double profundidad;
}
// Esta clase declara un objeto de tipo Caja.
class CajaDemo {
public static void main (String args []) {
Caja cajaMia = new Caja ();
double volumen;
// Asignación de valores a las variables de instancia de caixameva.
cajaMia.anchura = 10;
cajaMia.altura = 20;
cajaMia.profundidad = 15;
// Cálculo del volumen de caixameva.
volumen = cajaMia.anchura * cajaMia.altura * cajaMia.profundidad;
System.out.println ( "volumen es:" + vuelo);
}
}
El archivo que contiene este programa se llama CajaDemo.java porque el método main está en la clase llamada así. Cuando se compila el programa se crean dos ficheros .class, uno para Caja y otro para CajaDemo. El compilador Java crea automáticamente un archivo .class para cada clase. No es necesario que estas dos clases estén en el mismo archivo fuente, podríamos colocarlas cada una en un archivo diferente. Como hemos dicho antes, cada objeto tiene sus propias copias de las variables. Esto significa que si tenemos dos objetos de tipo Caja, cada uno de ellos tendrá su copia de las variables y por tanto los cambios en las variables de un objeto no afectan a las variables de otro objeto. Vemos el siguiente programa:
// Este programa declara dos objetos Caja.
class Caja {
double anchura;
double altura;
double profundidad;
}
class CajaDemo2 {
public static void main (String args []) {
Caja cajaMia1= new Caja ();
Caja cajaMia2= new Caja ();
double volumen;
// Asignación de valores a las variables de instancia de cajaMia1.
cajaMia1.anchura = 10;
cajaMia1.altura = 20;
cajaMia1.profundidad = 15;
// Asignación de valores a las variables de instancia de cajaMia2.
cajaMia2.anchura= 3;
cajaMia2.altura = 6;
cajaMia2.profundidad = 9;
// Cálculo del volumen de la primera caja.
volumen = cajaMia1.anchura * cajaMia1.altura * cajaMia1.profundidad;
System.out.println ( "volumen se" + vuelo);
// Cálculo del volumen de la segunda caja.
volumen = cajaMia2.anchura * cajaMia2.altura * cajaMia2.profundidad;
System.out.println ( "volumen se" + vuelo);
}
}
La salida que se obtiene es la siguiente:
- Volumen de CajaMia1 es: 3000.0
- Volumen de CajaMia2 es: 162.0
Declaración de objetos
Como hemos dicho antes, cuando se crea una clase, se crea un nuevo tipo de datos que puede utilizarse para declarar objetos de este tipo. De todas formas, para obtener los objetos de una clase es necesario un proceso en dos etapas. En primer lugar, debemos declarar una variable del tipo de la clase. En segundo lugar, se necesitará una copia física del objeto y asignarle a esta variable. Esto puede hacerse utilizando el operador new. Este operador asigna dinámicamente memoria a un objeto y le devuelve una referencia. Esta referencia se almacena en una variable. Luego en Java, todos los objetos de una clase deben asignarse dinámicamente.
En el programa de antes hemos utilizado una sentencia de este tipo:
Caja cajaMia1= new Caja ();
Esta sentencia combina los dos pasos que acabamos de describir. Para verlo claramente también podríamos escribir así:
Caja cajaMia1; // Declara la referencia a un objeto.
cajaMia1 = new Caja(); // Reserva el espacio para el objeto
La primera línea declara cajaMia como una referencia a un objeto del tipo Caja. Después de ejecutar esta línea cajaMia contiene el valor null, que indica que aún no apunta a un objeto real. Cualquier intento de utilizar cajaMia dará lugar a un error de compilación. La siguiente línea reserva un objeto real y asigna una referencia este objeto en cajaMia. Una vez ejecutada la segunda línea ya se puede utilizar cajaMia como si fuera un objeto de la clase Caja.
El operador new
Como hemos visto, este operador asigna automáticamente memoria para un objeto. Su forma general es la siguiente:
variable = new nombreDeClase();
El nombre de la clase seguido de los paréntesis especifica el constructor de la clase. Un constructor define lo que pasa cuando se crea un objeto de la clase. Los constructores son una parte importante de todas las clases y tienen atributos importantes. En la práctica, la mayoría de las clases define de forma explícita sus constructores en su definición de clase. Pero si no se define explícitamente, Java proporcionará un constructor por defecto.
Es importante comprender que el operador new reserva memoria para un objeto en tiempo de ejecución. La ventaja de hacerlo así es que el programa crea exactamente los objetos que necesita durante la ejecución del programa. Dado que la memoria es finita podría ser que new no fuera capaz de asignar memoria para que no hubiera memoria suficiente. En este momento supondremos que esto no pasa nunca y que siempre hay memoria disponible.
Debe quedar clara la diferencia entre clase y objeto. Una clase crea un nuevo tipo de datos que se utilizará para crear objetos. Es decir, una clase crea un marco lógico que define la relación entre sus miembros. Cuando se declara el objeto de una clase se crea una instancia de esta clase. Por lo tanto, una clase es una construcción lógica, mientras que el objeto tiene una realidad física, o sea ocupa espacio de memoria.
Asignación de variables de referencia a objetos
Las variables de referencia a objetos actúan de forma diferente a la que cabría esperar cuando tiene lugar una asignación. Por ejemplo:
Caja b1 = new Caja ();
Caja b2 = b1;
Cuando este fragmento se ejecute, b1 y b2 harán referencia al mismo objeto. La asignación de b1 a b2 no reserva memoria ni copia de ningún lugar del objeto original, simplemente hace que b2 haga referencia al mismo objeto que b1. Por lo tanto, cualquier cambio que se haga en el objeto a través de b2 afectará al objeto al que se refiere b1, ya que se refieren al mismo (por decirlo de alguna forma, los dos apuntan al mismo objeto).
Aunque b1 y b2 se refieren al mismo objeto, entre ellos no hay ningún vínculo. Por ejemplo, una asignación posterior a b1 simplemente despegará b1 de el objeto original sin afectar al objeto o a b2.