Download Unidad 2.- Mensajes y métodos 2.1 Atributos Const (final en java) Y
Document related concepts
no text concepts found
Transcript
Unidad 2.- Mensajes y métodos 2.1 Atributos Const (final en java) Y Static CONST EN LAS CLASES En una aplicación posiblemente nos encontremos con algún valor que permanece constante durante la ejecución. Podemos definirla como una variable común pero perderíamos el control. Por allí, en algún descuido, se cambiaría de valor pero no nos enteraríamos. Podemos agregar a la definición de variable el modificador final. La sintaxis es la siguiente: final tipo_variable nombre de variable [= valor]; Por ejemplo: final int unaConstante = 10; Si tratamos de modificar el valor de esta constante, el compilador indicará un error. Una vez definido el valor no se puede modificar. En java la palabra reservada final es utilizada en diversos contextos para definir una entidad que no podrá ser modificada posteriormente. Clases final. Métodos final. Variables final. Clases final Una clase con el atributo final no puede heredar. Esto puede utilizarse por razones de seguridad y eficiencia. Muchas de las clases de la librería estándar de java contienen el atributo final, como por ejemplo java.lang.System y java.lang.String. Todos los métodos en una clase final son implisitamente final también. Ejemplo: public final class MyFinalClass {...} Métodos final. Un método con el atributo final no puede redefinirse (override) en una subclase. Esto se usa para prevenir que el método sea alterado en una subclase si el funcionamiento de este es crucial en el funcionamiento o consistencia de la clase. Ejemplo. Public class MiClase { Public final void miMetodoFinal() { --} } Variables final. Una variable final sólo se puede asignar una vez. Esta asignación no le otorga el estado inmutable a la variable. Si la variable es un campo de una clase, debe asignarse en el constructor de su clase. (Nota: si la variable es una referencia, esto significa que la variable no puede ser re-bound para hacer referencia a otro objeto. Pero el objeto que hace referencia es todavía mutable, si era originalmente mutable). A diferencia del valor de una constante, el valor de una variable final no necesariamente se conoce en tiempo de compilación. Ejemplo. public class Sphere { public static final public final double public final double public final double public final double double PI= 3.141592653589793; //Esto es esencialmente una constante radius; xpos; ypos; zpos; Sphere(double x, double y, double z, double r) { radius = r; xpos = x; ypos = y; zpos = z; } [...] } Cualquier intento para reasignar un valor a estas variables producira un error de compilación. Para ilustrar que finalmente esto no garantiza inmutabilidad: supongamos que reemplace las variables de tres posiciones con una sola: public final Position pos; Donde pos es un objeto con tres propiedades pos.x, pos.y y pos.z. Entonces, no se puede reasignar pos, pero las tres propiedades si, a menos que estas sean explícitamente finales también. El uso de una constante dentro de una clase significa “Esto es constante durante la vida del objeto”. Por otra parte, en cada objeto la constante puede contener un valor diferente. Por eso, cuando crea una constante ordinaria (no estática) dentro de una clase, no puede darle un valor inicial. Esta inicialización debe ocurrir en el constructor. Como la constante se debe inicializar en el punto en que se crea, en el cuerpo del constructor la constante debe estar ya inicializada. De otro modo, le quedaría la opción de esperar hasta algún punto posterior en el constructor, lo que significaría que la constante no tendría valor por un momento. Y nada impediría cambiar el valor de la constante en varios sitios del constructor. ATRIBUTO STATIC A pesar de lo que podría parecer por su nombre, heredado de la terminología de C++, el modificador static no sirve para crear constantes, sino para crear miembros que pertenecen a la clase, y no a una instancia de la clase. Esto implica, entre otras cosas, que no es necesario crear un objeto de la clase para poder acceder a estos atributos y métodos. Este es el motivo por el cual es obligatorio que main se declare como static; de esta forma no tenemos que ofrecer un constructor vacío para la clase que contiene el método, o indicar de alguna forma a la máquina virtual cómo instanciar la clase. Un uso del modificador static sería, por ejemplo, crear un contador de los objetos de la clase que se han creado, incrementando la variable estática en el constructor: class Usuario { static int usuarios = 0; Usuario() { usuarios++; } } Como es de esperar, dado que tenemos acceso a los atributos sin necesidad de crear un objeto, los atributos estáticos como usuarios no se inicializan al crear el objeto, sino al cargar la clase. Podemos acceder a estos métodos y atributos bien desde la propia clase public class Ejemplo { public static void main(String[] args) { Usuario raul = new Usuario(); Usuario juan = new Usuario(); System.out.println("Hay " + Usuario.usuarios + " usuarios"); } } o bien desde una instancia cualquiera de la clase: public class Ejemplo { public static void main(String[] args) { Usuario raul = new Usuario(); Usuario juan = new Usuario(); System.out.println("Hay " + raul.usuarios + " usuarios"); } } Otro uso sería el de crear una recopilación de métodos y atributos relacionados a los que poder acceder sin necesidad de crear un objeto asociado, que podría no tener sentido o no ser conveniente, como es el caso de la clase Math. public class Ejemplo { public static void main(String[] args) { System.out.println("PI es " + Math.PI); System.out.println("El coseno de 120 es " + Math.cos(120)); } } Una característica no muy conocida que se introdujo en Java 1.5 son los static imports, una sentencia similar al import habitual, con la salvedad de que esta importa miembros estáticos de las clases, en lugar de clases de los paquetes, permitiendo utilizar estos miembros sin indicar el espacio de nombres en el que se encuentran. El ejemplo anterior podría haberse escrito también de la siguiente forma utilizando esta característica: import static java.lang.Math.*; public class Ejemplo { public static void main(String[] args) { System.out.println("PI es " + Math.PI); System.out.println("El coseno de 120 es " + Math.cos(120)); } } Si por algún motivo requerimos cualquier tipo de computación para inicializar nuestras variables estáticas, utilizaremos lo que se conoce como bloque estático o inicializador estático, el cual se ejecuta una sola vez, cuando se carga la clase. public class Reunion { static { int zona_horaria = Calendar.getInstance().get(Calendar.ZONE_OFFSET)/(60 * 60 * 1000); } } Por último, una curiosidad relacionada que podéis utilizar para romper el hielo con una programadora Java es que podemos utilizar un bloque static para escribir un programa sencillo sin necesidad de un main, añadiendo una llamada a System.exit para que el programa termine tras cargar la clase sin intentar llamar al método main. public class Ejemplo { static { System.out.println("Hola mundo"); System.exit(0); } } 2.2 Concepto de Método En Java toda la lógica de programación (Algoritmos) está agrupada en funciones o métodos. Un método es: un subprograma Un bloque de código que tiene un nombre recibe unos parámetros o argumentos (opcionalmente) contiene sentencias o instrucciones para realizar algo (opcionalmente) devuelve un valor de algún Tipo conocido (opcionalmente). Los métodos son las acciones funciones o procedimientos que realiza nuestro programa; los métodos son subrutinas que manipulan los datos definidos por una clase. CARACTERISTICAS DE LOS METODOS: 1. Contiene una o más declaraciones 2. Cada método tiene un nombre y este nombre se utiliza para llamar al método (las palabras clave no pueden ser utilizadas como el nombre del método). 3. Debe llevar paréntesis después del nombre. 4. El método main() está reservado por java como el método que inicializa la ejecución del programa. 2.3 Declaración de Métodos La sintaxis global es: Tipo_Valor_devuelto nombre_método ( lista_argumentos ) { bloque_de_codigo; } y la lista de argumentos se expresa declarando el tipo y nombre de los mismos (como en las declaraciones de variables). Si hay más de uno se separan por comas. Por ejemplo: int sumaEnteros ( int a, int b ) { int c = a + b; return c; } •El método se llama sumaEnteros. •Recibe dos parámetros también enteros. Sus nombres son a y b. •Devuelve un entero. En el ejemplo la claúsula return se usa para finalizar el método devolviendo el valor de la variable c. 2.4 Llamadas Métodos Mensajes El modelado de objetos no sólo tiene en consideración los objetos de un sistema, sino también sus interrelaciones. Mensaje. Los objetos interactúan enviándose mensajes unos a otros. Tras la recepción de un mensaje el objeto actuará. La acción puede ser el envío de otros mensajes, el cambio de su estado, o la ejecución de cualquier otra tarea que se requiera que haga el objeto. Método. Un método se implementa en una clase, y determina cómo tiene que actuar el objeto cuando recibe un mensaje. Cuando un objeto A necesita que el objeto B ejecute alguno de sus métodos, el objeto A le manda un mensaje al objeto B. Al recibir el mensaje del objeto A, el objeto B ejecutará el método adecuado para el mensaje recibido. Llamar a Métodos de un Objeto. Llamar a un método de un objeto es similar a obtener una variable del objeto. Para llamar a un método del objeto, simplemente se añade al nombre del objeto referenciado el nombre del método, separados por un punto (‘.’), y se proporcionan los argumentos del método entre paréntesis. Si el método no necesita argumentos, se utilizan los paréntesis vacios. objetoReferenciado.nombreMétodo(listaArgumentos); o objetoReferenciado.nombreMétodo(); Las llamadas a métodos se hacen directamente a un objeto específico; el objeto especificado en la llamada al método es el que responde a la instrucción. Las llamadas a métodos también se conocen como mensajes. Como en la vida real, los mensajes se deben dirigir a un receptor particular. Se pueden obtener distintos resultados dependiendo del receptor de su mensaje. Una llamada a un método es una expresión y evalúa a algún valor. El valor de una llamada a un método es su valor de retorno, si tiene alguno. Normalmente se asignará el valor de retorno de un método a una variable o se utilizará la llamada al método dentro del ámbito de otra expresión o sentencia. Recuerda que una llamada a un método es un mensaje al objeto nombrado. Como se explicó anteriormente, el objetoReferenciado en la llamada al método objetoReferenciado.metodo() debe ser una referencia a un objeto. Como se puede utilizar un nombre de variable aquí, también se puede utilizar en cualquier expresión que devuelva una referencia a un objeto. Recuerda que el operador new devuelve una referencia a un objeto. Por eso, se puede utilizar el valor devuelto por new para acceder a las variables del nuevo objeto. new Rectangle(0, 0, 100, 50).equals(anotherRect) La expresión new Rectangle(0, 0, 100, 50) evalúa a una referencia a un objeto que se refiere a un objeto Rectangle. Entonces, como verás, se puede utilizar la notación de punto (‘.’) para llamar al método equals() del nuevo objeto Rectangle para determinar si el rectangúlo nuevo es igual al especificado en la lista de argumentos de equals(). 2.6 Referencia This Los métodos de un objeto, como se explicó anteriormente, definen las acciones que un objeto puede llevar a acabo. Para utilizar los métodos de un objeto, usamos la misma técnica que para asignar colores a los datos de un objeto: Circulo c = new Circulo(); double a; c.r = 2.5; a = c.area(); En este último ejemplo se asignó el valor del método area( ) a la variable a. Observe la última línea. No se escribió a = area(); sino: a = c.area(); Esto se debe a que se utilizó la programación “Orientada a Objetos”; aquí el objeto es el centro de atención y no así la llamada del método. Esto es probablemente la más simple e importante característica del paradigma orientado a objetos. Note que no se pasó ningún argumento a c.area(). El objeto sobre el que se está operando, c, se encuentra implícito en la sintaxis. Observe de nuevo el ejemplo 1: notará lo mismo en la definición del método area() - no toma argumentos. Esta implícito en el lenguaje, que un método opera sobre una instancia de la clase de la cual es definido. De este modo el método area() puede utilizar libremente el campo r de la clase - es claro que esta refiriéndose al radio de cualquier instancia Circulo que invoca el método. ¿Qué es lo que pasa aquí? ¿Cómo puede saber un método, que no toma ningún parámetro, sobre que dato operar? En efecto, el método area() simula tener un argumento, y se define con un argumento implícito que no se muestra en la declaración del mismo. El argumento implícito es llamado this, y se refiere a “este objeto” - el objeto Circulo del cual el método se invoco. This. es frecuentemente llamado la referencia “this”. El argumento implícito this, no se muestra en la declaración del método, ya que usualmente no es necesario - donde quiera un método en Java puede accesar los campos en su clase. El código de un método constructor de la clase Circulo se muestra en el siguiente ejemplo: public Circulo(double a, double b, double c) { x = a; y = b; r = c; } En este caso, el método constructor inicializa los campos de datos del objeto que se está creando. Este método es equivalente al que se muestra en este otro ejemplo: public Circulo(double a, double b, double c) { this.x = a; this.y = b; this.r = c; } Cada vez que este último constructor se ejecuta, this hace referencia al objeto que se está creando en ese instante. La palabra this se puede usar explícitamente cuando se quiere poner en claro que un método accesa sus propias variables y/o métodos. Por ejemplo, se puede re-escribir el método area() de la siguiente forma: public double area() { return 3.14159 * this.r * this.r; } En un método tan sencillo como éste, no es necesario ser explícito. Sin embargo, en casos más complicados, parece que utilizar un this explícito incrementa la claridad del código aunque no sea estrictamente necesario. 2.7 Forma de pasar argumentos Algunos métodos requieren que se les pasen argumentos (parámetros). Los tipos de los parámetros deberán especificarse en la declaración de cada método. En general, existen dos formas de pasar parámetros: Por valor y Por referencia. Paso por valor. Todas las variables que aparezcan en una lista de parámetros serán consideradas parámetros por valor, a menos que la lista contenga la palabra clave out o la palabra clave ref. En el paso por valor se envía una copia del valor del parámetro, de manera que el método que recibe ese valor no puede cambiar el contenido de la variable utilizada en el envío del mensaje. Dado que java no soporta el paso por referencia pondré el ejemplo en C#. Ejemplo: // pasoPorValor.cs : Ejemplifica el paso de parámetros por valor. using System; using C = System.Console; class Receptor { int s ; public Receptor( int x ) { x+=10 ; C.Write Line?(“ El valor de x es : {0} “, x ) ; s = x ; } } class Principal { public static void Main( ) { int a = 20 ; Receptor r = new Receptor ( a ) ; C.Write Line(“ El valor de a es : {0}”, a ) ; } } Paso por referencia. En el paso de parámetros por referencia, en lugar de pasar una copia del valor almacenado en la variable, se pasa la dirección de memoria de ella. Así, el método receptor puede modificar el contenido de la variable. En C#, los parámetros por referencia se crean utilizando la palabra clave ref en la lista de parámetros del método. Ejemplo: // pasoPorRef.cs : Ejemplifica el paso de parámetros por referencia. using System; using C = System.Console; class Receptor { int s ; public Receptor( ref int x ) { x+=10 ; C.Write Line(“ El valor de x es : {0} “, x ) ; s = x ; } } class Principal { public static void Main( ) { int a = 20 ; Receptor r = new Receptor ( ref a ) ; C.Write Line(“ El valor de a es : {0} “, a ) ; } } 2.8 Devolver un valor desde un método Devolver un Valor desde un Método Java necesita que un método declare el tipo de dato del valor que devuelve. Si un método no devuelve ningún valor, debe ser declarado para devolver void (nulo). Los métodos pueden devolver tipos de datos primitivos o tipos de datos de referencia. El método estaVacio() de la clase Pila devuelve un tipo de dato primitivo, un valor booleano. class Pila { static final int PILA_VACIA = −1; Object[] stackelements; int topelement = PILA_VACIA; . . . boolean estaVacio() { if (topelement == PILA_VACIA) return true; else return false; } } Sin embargo, el método pop de la clase PILA devuelve un tipo de dato de referencia: un objeto. class Pila { static final int PILA_VACIA = −1; Object[] stackelements; int topelement = PILA_VACIA; . . . Object pop() { if (topelement == PILA_VACIA) return null; else { return stackelements[topelement—]; } } } Los métodos utilizan el operador return para devolver un valor. Todo método que no sea declarado como void debe contener una sentencia return. El tipo de dato del valor devuelto por la sentencia return debe corresponder con el tipo de dato que el método tiene que devolver; no se puede devolver un objeto desde un método que fue declarado para devolver un entero. Cuando se devuelva un objeto, el tipo de dato del objeto devuelto debe ser una subclase o la clase exacta indicada. Cuando se devuelva un tipo interface, el objeto retornado debe implementar el interface especificado. 2.9 Estructura del código Estructura básica de un programa en Java En Java, como en cualquier otro lenguaje orientado a objetos, abandonamos el modo de entender un programa que utilizábamos anteriormente para aproximarnos a un modo más cercano a “la vida misma”. Los programas ahora estarán divididos en clases. Una clase en si se puede entender como un programa independiente, tiene sus propios datos y también maneja esos datos “a su modo”. La relación con la vida misma la podemos ver en el siguiente comentario: Imaginemos que dos clases tal y como las hemos explicado anteriormente son “la clase mecánico” y la “clase panadero”. Cada una de estas clases tiene sus propias herramientas y sus propias tareas, por ejemplo, el panadero tiene “harina” y una de sus tareas es “amasar”, mientras que el mecánico tiene “bujías” y una de sus tareas es “limpiar bujías”. Lo importante de todo esto es que cada uno hace muy bien su tarea pero no tiene sentido “llevar el coche a la panadería de la esquina” ni “pedir una baguette junto con un cambio de aceite”. Vamos a estudiar unas pequeñas clases que nos servirán en un futuro para implementar un ejercicio un poco más complicado. Estas clases son la clase “ficha” y la clase “tablero” que nos servirán para implementar con el tiempo un juego de las “cuatro en raya”. public class Fichas { String color; public Fichas(String c) { color=c; } public String dameColor() { return(color); } } Esta va a ser la clase ficha. Veámosla un poco: Esta clase tiene una variable “color” que es un String. El tipo String no es un tipo primitivo en Java, es una clase que está dentro del API de Java, mas concretamente dentro del paquete “Java.lang” y que además siempre se incluye por defecto en cada programa Java que hagamos. Por tanto, lo que estamos haciendo en la segunda línea de nuestro programa es declarar un objeto sin valor de la clase String. La tercera y la cuarta línea son dos métodos de la clase “Fichas” que estamos definiendo. El primer método es un constructor. Un constructor es un método que se llama con la sentencia “new”, es decir cuando alguien quiera crear un objeto y que nos define bajo que condiciones se crea ese objeto, ya lo entenderás mejor. En este caso para que alguien quiera crear una ficha tiene que pasar un objeto “String” como parámetro y obtendrá a cambio un objeto de la clase “Fichas” del color que ha solicitado. El segundo método nos devuelve un objeto “String” con el valor del color que tiene el objeto ficha en ese momento. public class Tablero { Fichas estadoTablero[][]; public Tablero() { estadoTablero=new Fichas [6][7]; } public boolean verSiLlena(int indice) { return(estadoTablero[7][indice]==null); } } Bueno, esta segunda clase que estudiamos tiene también un objeto interno llamado “estadoTablero” que en este caso es un “array” cuyas posiciones son de la clase anteriormente declarada “Fichas”. También tenemos un “constructor” para el objeto “estadoTablero” que crea ese “array” con las dimensiones que queremos. Y en este caso hay una función que nos dice si la columna por la que nos interesamos con el parámetro de entrada “índice” está llena.