CLASES El elemento básico de la programación orientada a objetos en Java es la clase. Una clase define la forma y comportamiento de un objeto. Cualquier concepto que se desee representar en un programa en Java está encapsulado en una clase. Para crear una clase sólo se necesita un archivo fuente que contenga la palabra clave class seguida de un identificador y un par de llaves para el cuerpo. class Punto { // ... Cuerpo de la Clase } La clase se almacena en un archivo fuente con el mismo nombre de la clase y la extensión java, en este caso sería Punto.java. Las clases en Java incluyen variables y métodos de instancia. Los programas en Java constan por lo general de varias clases de Java provenientes de distintos archivos fuente. Una clase es una plantilla para un objeto. 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 los objetos reales. La forma general de una definición de 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; } { } La palabra clave extends se utiliza para indicar que nombre de clase será una subclase de nombre de superclase. En la raíz de la jerarquía de clases de Java se encuentra la clase Object, si se desea realizar una subclase de Object directamente, se puede o mitir la cláusula extends porque el compilador la incluirá automáticamente. Referencia a objetos Cada nueva clase que se crea añade un nuevo 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 referencia a objeto . Una instancia u objeto es una copia individual de la plantilla de la clase, con su propio conjunto de datos llamado variables de instancia. 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 Obiect, y, por lo tanto, es compatible en tipo con todas las otras clases. Declaramos una variable p cuyo tipo es de la clase Punto de la siguiente forma:. Punto p ; Aquí, la variable p tiene un valor null. null p Una referencia a un objeto es simplemente un puntero a memoria. Java que no permite manipular estos punteros como si fueran enteros, porque se provocaría que una referencia a objeto apuntase a posiciones de memoria arbitrarias. De hecho, la instancia real de una clase a la que se referencia no se almacena en la dirección de una referencia a objeto. Las referencias a objeto apuntan a una estructura de datos intermedia que almacena la información del tipo junto con la dirección en el montículo actual de los datos de instancia reales. V a r i a bl es d e i ns t a nc i a 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 se las conoce como variables de instancia. Las variables de instancia se declaran fuera del ámbito de un método. Por ejemplo, si se declara una clase de nombre Punto, con dos variables de instancia enteras llamadas x e y, tendremos: class Punto { int x, y; } El operador new El operador new crea una instancia de una clase y devuelve una referencia a ese objeto. Por ejemplo en: Punto p = new Punto(); se crea una nueva instancia de Punto y se almacena en una variable p. Aquí p es una referencia a una instancia de Punto, es decir, la variable p es una referencia al objeto, realmente no lo contiene. Se pueden crear múltiples referencias al mismo objeto. Presentamos aquí un ejemplo, que crea un único objeto Punto, pero crea dos variables que lo referencían. Punto p = new Punto(); Punto p2 = p; Cualquier cambio realizado en el objeto referenciado por p2 afectará al mismo objeto al cual se refiere p. La asignación de p a p2 no asignó memoria ni copió nada del objeto original. De hecho, las asignaciones posteriores a p simplemente desligarán a p del objeto original sin afectar al propio objeto, como se muestra a continuación. Punto p = new Punto(); Punto p2 = p; p = null; Aunque se haya asignado p a null, p2 todavía apunta al objeto creado por el operador new. Cuando ya no haya ninguna variable que haga referencia a un objeto, Java reclama automáticamente la memoria utilizada por ese objeto. A esto se le llama recolección automática de basura . En el ejemplo anterior, hemos creado un único objeto y lo hemos referenciado dos veces. Esto permitía que dos variables afectaran al mismo objeto. Se obtiene un conjunto distinto de variables de instancia cada vez que se crea un nuevo objeto. Cada objeto tiene su propia copia de las variables de instancia de su clase, por lo que los cambios sobre las variables de instancia de un objeto no tienen efecto sobre las variables de instancia de otro. Ahora crearemos dos objetos Punto diferentes y les pondremos distintos valores a cada uno: class DosPuntos { public static void main(String args[]) { Punto pl = new Punto(); Punto p2 = new Punto(); pl.x = 10; pl.y = 20; p2.x = 42; p2.y = 99; System.out.println("x = " + pl.x + 'y = " + pl.y); System.out.println("x = " + p2.x + " y = " + p2.y); } } Este ejemplo reutiliza la clase Punto , pero crea dos puntos y establece los valores de x e y. El aspecto de la salida cuando lo ejecutamos es:. C:\> java TwoPuntos x = 10 y = 20 x = 42 y = 99 Dado que main sólo se ejecuta en la clase que se indica en la línea de comandos, se ignora completamente el método main de la clase Punto en este caso. 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 fragmento de código siguiente muestra cómo se puede utilizar el operador punto para almacenar valores en variables de instancia: p.x = 10; p.y = 20; La línea siguiente muestra cómo referirnos a valores de variables de instancia utilizando el operador punto: System.out.println("x = " + p.x + " y = " + p.y); En el siguiente código añadimos un método main a la clase Punto creamos un Punto, almacenamos algunos valores en él e imprimimos después los valores resultantes: class Punto { int x, y; public static void main(String args[]) { Punto p = new Punto(); p.x = 10; p.y = 20; System.out.println("x = " + p.x + " y = " + p.y); } } Cuando se ejecuta este programa, se observa la siguiente salida C:\> java Punto x = 10 y = 20 Declaración de método Un método es la interfaz funcional de una clase. Los métodos son subrutinas unidas a una definición de clase específica. Se declaran dentro de una definición de clase al mismo nivel que las variables de instancia. En la declaración de los métodos se define que devuelven un valor de un tipo concreto y que tienen un conjunto de parámetros de entrada. La forma general de una declaración de método es tipo nombre_del_método ( lista-formal-de-parámetros ) { cuerpo-del-método; } Aquí,. tipo es el tipo que el método va a devolver, incluyendo void en el caso de que no se desee devolver ningún valor. El nombre_del_método es cualquier identificador distinto de los ya utilizados por los nombres de clase en el ámbito actual. La lista-formal-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ía incluir un par de paréntesis vacío. Continuando con nuestro ejemplo Punto, se podría utilizar el método siguiente para inicializar las dos variables de instancia a la vez. El método se declara dentro de las llaves de apertura y cierre de la clase, en el mismo ámbito de las dos variables de instancia. class Punto { int x, , y; void iniciar (int a, int b) { x = a; y= b; } } Nos podemos referir directamente a x e y sin tener que utilizar p1. x debido a que los métodos se pueden referir directamente a las variables de instancia. Como iniciar no devuelve ningún valor se antecede del tipo void . Llamada a un método Se llama a los métodos dentro de una instancia de una clase utilizando el operador punto ( . ). La forma general de una llamada a método es: referencia_a_objeto . nombre_del_método ( lista-de-parámetros ); Aquí, referencia_a_objeto es cualquier variable que se refiere a un objeto, nombre_del_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 exactamente en número y tipo con cualquiera de los métodos declarados en la clase. En este caso, podríamos llamar al método iniciar sobre cualquier objeto Punto para establecer x e y. Este es un fragmento de código que muestra cómo funciona: Punto p = new Punto(); p.iniciar (10, 20); Este ejemplo crea una instancia nueva de Punto, almacenando la referencia en p. A continuación se utiliza el operador punto para llamar al método iniciar sobre la instancia, pasando 10 y 20 a los parámetros a y b respectivamente. Dentro del método iniciar, x e y se refieren directamente a las variables de instancia del objeto referenciado por p, mientras que a y b toman el valor 10 y 20 respectivamente. Las asignaciones de x a 10 y de y a 20 vienen a continuación y el método termina. No hay ningún modo en Java de pasar un tipo primitivo por referencia, ya que todos los tipos primitivos se pasan por valor, lo que significa que un método no tiene acceso a la variable que fue utilizada para pasar el valor. Las referencias a instancias de objeto se pasan también por valor, lo qué significa que no se puede cambiar la variable que hace referencia al objeto, pero de hecho se puede cambiar el contenido del objeto al que se refiere la referencia, dado que todos los objetos se pasan por referencia. This Java incluye un valor de referencia especial llamado this, que se utiliza dentro de cualquier método para referirse al objeto actual. El valor this se refiere al objeto sobre el que ha sido llamado el método actual. Se puede utilizar this siempre que se requiera una referencia a un objeto del tipo de la clase actual. En el código ejemplo anterior, llamamos a p. iniciar. Una vez dentro del método iniciar, this apuntará al mismo objeto al que apunte p. Si hay otros objetos que utilicen el mismo código, seleccionados a través de otras instancias, cada uno tiene su propio concepto único de this. Ocultar variables de instancia En Java es ilegal declarar dos variables locales con el mismo nombre dentro del mismo ámbito o uno que lo incluya. Sí se permite el declarar variables locales, incluyendo parámetros formales de métodos, que se solapen con los nombres de las variables de instancia, pero se ocultarían dichas variables. Para accesarlas se usa el valor de referencia this. Esta es otra versión de iniciar , que utiliza x e y como nombres de parámetro formales y después utiliza this para acceder a las variables de instancia del objeto actual. void iniciar (int x, int y) { this.y = x; this.y = y; } Si lo hubiésemos hecho sin this, entonces x se hubiera referido al parámetro formal, ocultando la variable de instancia x. this nos permite referirnos directamente al objeto en sí, en lugar de que el ámbito actual defina a que variable nos referimos. El ejemplo DosPuntos se podría modificar para usar el nuevo método iniciar, que ahora cuenta con la referencia this, como se muestra aquí: class Punto { int x, y; void iniciar(int x, int y) { this.x = x; this.y = y; } } class DosPuntosIniciar { public static void main(String args[]) { Punto. p1 = new Punto(); Punto. p2 = new Punto(); p1.iniciar(10, 20); p2.iniciar(42, 99); System.out.println("x = " + p1.x + " y = " + p1.y); System.out.println("x = “ + p2.x + " y = " + p2.y); } } Este ejemplo muestra que se crean dos objetos Punto y se llama a sus respectivos métodos iniciar con un par de valores diferente. La salida de este programa es idéntica a la del ejemplo DosPuntos anterior. Constructores Un constructor es un método especial que inicializa un objeto inmediatamente después de su creación. Tiene exactamente el mismo nombre de la clase en la que reside; de hecho, ningún otro método puede tener su nombre igual al de la clase a la que pertenece. Después de crear el objeto se llama automáticamente al constructor, antes de que termine el operador new. Los constructores 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. Es tarea del constructor inicializar todo el estado interno de un objeto para que el código que crea una instancia con el operador new tenga un objeto íntegro y utilizable inmediatamente. Retomando el ejemplo Punto, podemos inicializar las dos variables de instancia cuando construimos el objeto. Aquí, sustituiremos el método iniciar por un constructor: class Punto { int x, y; Punto(int x, int y) { this.y = x; this.y = y; } } class PuntoCreate { public static void main(String args[]) { Punto p = new Punto(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. La lista de parámetros especificada después del nombre de la clase en una sentencia new se utiliza para pasar parámetros al constructor correspondiente. Sobrecarga de métodos Se pueden 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. La sobrecarga de métodos se utiliza para soportar el polimorfismo en Java. 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. Por ejemplo, podemos añadir a la clase Punto dos constructores utilizando la sobrecarga de métodos: class Punto { int x, y; Punto(int x, int y) { this.y = x; this.y = y; } Punto() { x = 0; y = 0; } } class CreaPunto { public static void main(String args[}) { Punto p = new Punto(); System.out.println("x = " + p.x + " y = " + p.y); } } Este ejemplo crea un objeto Punto que llama al segundo constructor sin parámetros en vez de al primero. Esta es la salida. C:\> java CreaPunto x = 0 y = 0 En las llamadas a los constructores éstos se seleccionan en base al número y tipo de parámetros que se les pase. Al número de parámetros con tipo de una secuencia específica se le llama signatura de tipo . Java utiliza estas signaturas de tipo para decidir a qué método llamar. En cualquier llamada a un método la signatura de tipo debe coincidir exactamente. Es ilegal declarar dos métodos en la misma clase con el mismo nombre, compartiendo las mismas signaturas de tipo. No se consideran los nombres de los parámetros formales de los métodos cuando se distingue entre métodos, sólo sus tipos. This en los constructores Un refinamiento adicional es que un constructor llame a otro para construir la instancia correctamente. Por ejemplo: class Punto { int x, y; Punto(int x, int y) { this.y = x; this.y = y; } Punto() { this(0, 0); El segundo constructor llama ahora al primero para terminar de inicializar la instancia. También se pueden crear métodos sin constructor que utilicen la sobrecarga. Se muestra aquí un ejemplo que añade dos versiones de un método llamado distancia a la clase Punto. La función distancia devuelve la distancia euclidiana entre dos puntos. Un método toma un par x, e y el otro toma otro objeto Punto. En este ejemplo, se utiliza el método estático sqrt de la clase Math para calcular la raíz cuadrada de su parámetro. class Punto { int x, y; Punto(int x, int y) { this.y = x; this.y = y; } double distancia(int x, int y) { int dx = this.y - x; int dy = this.y - y; return Math.sgrt(dx*dx + dy*dy); } double distancia(Punto p) { return distancia(p.x, p.y); } class DistPunto { public static void main(String args[]) { Punto pl = new Punto(0, 0); Punto p2 = new Punto(30, 40); System.out.println("p1 = " + pl.x + + pl.y); System.out.println("p2 = " + p2.x + + p2.y); System.out.println("pl.distancia(p2) _ " + pl.distancia(p2)); System.out.println("pl.distancia(60, 80) _ " + pl.distancia(60, 80)); Este ejemplo muestra cómo se puede sobrecargar un método para tener dos maneras alternativas de invocar el mismo cálculo. Observe que la segunda forma de distancia llama a la primera para obtener el valor que devuelve. Esta es la salida de este ejemplo: C:\> java DistPunto P1 = 0, 0 p2 = 30, 40 p1.distancia(p2) = 50 p1.distancia(60, 80) = 100 Java no se toma como criterio de selección de un método sobrecargado el tipo que devuelve dicho método. Dos métodos no pueden compartir la misma signatura de tipos, independientemente de los tipos que devuelvan.