|
![]() |
Métodos |
Anterior | Siguiente |
Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras clases. La implementación de un método consta de dos partes, una declaración y un cuerpo. La declaración en Java de un método se puede expresar esquemáticamente como: tipoRetorno nombreMetodo( [lista_de_argumentos] ) { cuerpoMetodo } En C++, el método puede declararse dentro de la definición de la clase, aunque también puede colocarse la definición completa del método fuera de la clase, convirtiéndose en una función inline. En Java, la definición completa del método debe estar dentro de la definición de la clase y no se permite la posibilidad de métodos inline, por lo tanto, Java no proporciona al programador distinciones entre métodos normales y métodos inline. Los métodos pueden tener numerosos atributos a la hora de declararlos, incluyendo el control de acceso, si es estático o no estático, etc. La sintaxis utilizada para hacer que un método sea estático y su interpretación, es semejante en Java y en C++. Sin embargo, la sintaxis utilizada para establecer el control de acceso y su interpretación, es muy diferente en Java y en C++. La lista de argumentos es opcional, tanto en Java como en C++, y en los dos casos puede limitarse a su mínima expresión consistente en dos paréntesis, sin parámetro alguno en su interior. Opcionalmente, C++ permite utilizar la palabra void para indicar que la lista de argumentos está vacía, en Java no se usa. Los parámetros, o argumentos, se utilizan para pasar información al cuerpo del método. La sintaxis de la declaración completa de un método es la que se muestra a continuación con los items opcionales en itálica y los items requeridos en negrilla: especificadorAcceso static abstract final native synchronized tipoRetorno nombreMetodo( lista_de_argumentos ) throws listaEscepciones especificadorAcceso, determina si otros objetos pueden acceder al método y cómo pueden hacerlo. Está soportado en Java y en C++, pero la sintaxis e interpretación es considerablemente diferente. static, indica que los métodos pueden ser accedidos sin necesidad de instanciar un objeto del tipo que determina la clase. C++ y Java son similares en el soporte de esta característica. abstract, indica que el método no está definido en la clase, sino que se encuentra declarado ahí para ser definido en una subclase (sobreescrito). C++ también soporta esta capacidad con una sintaxis diferente a Java, pero con similar interpretación. final, evita que un método pueda ser sobreescrito. native, son métodos escritos es otro lenguaje. Java soporta actualmente C y C++. synchronized, se usa en el soporte de multithreading, que se verá también en este Tutorial. lista_de_argumentos, es la lista opcional de parámentros que se pueden pasar al método throws listaExcepciones, indica las excepciones que puede generar y manipular el método. También se verán en este Tutorial a fondo las excepciones en Java. En Java es imprescindible que a la hora de la declaración de un método, se indique el tipo de dato que ha de devolver. Si no devuelve ningún valor, se indicará el tipo void como retorno. Los métodos y funciones en C++ pueden devolver una variable u objeto, bien sea por valor (se devuelve una copia), por puntero o por referencia. Java no soporta punteros, así que no puede devolver nada por puntero. Todos los tipos primitivos en Java se devuelven por valor y todos los objetos se devuelven por referencia. El retorno de la referencia a un objeto en Java es similar a devolver un puntero a un objeto situado en memoria dinámica en C++, excepto que la sintaxis es mucho más simple en Java, en donde el item que se devuelve es la dirección de la posición en memoria dinámica donde se encuentra almacenado el objeto. Para devolver un valor se utiliza la palabra clave return. La palabra clave return va seguida de una expresión que será evaluada para saber el valor de retorno. Esta expresión puede ser compleja o puede ser simplemente el nombre de un objeto, una variable de tipo primitivo o una constante. El ejemplo java506.java ilustra el retorno por valor y por referencia. // Un objeto de esta clase sera devuelto por referencia class miClase { int varInstancia = 10; } Si un programa Java devuelve una referencia a un objeto y esa referencia no es asignada a ninguna variable, o utilizada en una expresión, el objeto se marca inmediatamente para que el reciclador de memoria en su siguiente ejecución devuelve la memoria ocupada por el objeto al sistema, asumiendo que la dirección no se encuentra ya almacenada en ninguna otra variable. En C++, si un programa devuelve un puntero a un objeto situado en memoria dinámica y el valor de ese puntero no se asigna a una variable, la posibilidad de devolver la memoria al sistema se pierde y se producirá un memory leak, asumiendo que la dirección no está ya disponible para almacenar ninguna otra variable. Tanto en Java como en C++ el tipo del valor de retorno debe coincidir con el tipo de retorno que se ha indicado en la declaración del método; aunque en Java, el tipo actual de retorno puede ser una subclase del tipo que se ha indicado en la declaración del método, lo cual no se permite en C++. En Java esto es posible porque todas las clases heredan desde un objeto raíz común a todos ellos: Object. En general, se permite almacenar una referencia a un objeto en una variable de referencia que sea una superclase de ese objeto. También se puede utilizar un interfaz como tipo de retorno, en cuyo caso, el objeto retornado debe implementar dicho interfaz. El nombre del método puede ser cualquier identificador legal en Java. Java soporta el concepto de sobrecarga de métodos, es decir, permite que dos métodos compartan el mismo nombre pero con diferente lista de argumentos, de forma que el compilador pueda diferenciar claramente cuando se invoca a uno o a otro, en función de los parámetros que se utilicen en la llamada al método. El siguiente fragmento de código muestra una clase Java con cuatro métodos sobrecargados, el último no es legal porque tiene el mismo nombre y lista de argumentos que otro previamente declarado: class MiClase { . . . void miMetodo( int x,int y ) { . . . } void miMetodo( int x ) { . . . } void miMetodo( int x,float y ) { . . . } // void miMetodo( int a,float b ) { . . . } // no válido } Todo lenguaje de programación orientado a objetos debe soportar las características de encapsulación, herencia y polimorfismo. La sobrecarga de métodos es considerada por algunos autores como polimorfismo en tiempo de compilación. En C++, dos versiones sobrecargadas de una misma función pueden devolver tipos diferentes. En Java, los métodos sobrecargados siempre deben devolver el mismo tipo. Cuando se incluye un método en una definición de una clase Java sin utilizar la palabra clave static, estamos generando un método de instancia. Aunque cada objeto de la clase no contiene su propia copia de un método de instancia (no existen múltiples copias del método en memoria), el resultado final es como si fuese así, como si cada objeto dispusiese de su propia copia del método. Cuando se invoca un método de instancia a través de un objeto determinado, si este método referencia a variables de instancia de la clase, en realidad se están referenciando variables de instancia específicas del objeto específico que se está invocando. La llamada a los métodos de instancia en Java se realiza utilizando el nombre del objeto, el operador punto y el nombre del método. miObjeto.miMetodoDeInstancia(); En C++, se puede acceder de este mismo modo o utilizando una variable puntero que apunte al objeto miPunteroAlObjeto->miMetodoDeInstancia(); Los métodos de instancia tienen acceso tanto a las variables de instancia como a las variables de clase, tanto en Java como en C++. Cuando una función es incluida en una definición de clase C++, o un método e incluso en una definición de una clase Java, y se utiliza la palabra static, se obtiene un método estático o método de clase. Lo más significativo de los métodos de clase es que pueden ser invocados sin necesidad de que haya que instanciar ningún objeto de la clase. En Java se puede invocar un método de clase utilizando el nombre de la clase, el operador punto y el nombre del método. MiClase.miMetodoDeClase(); En C++, hay que utilizar el operador de resolución de ámbito para poder invocar a un método de clase: MiClase::miMetodoDeClase(); En Java, los métodos de clase operan solamente como variables de clase; no tienen acceso a variables de instancia de la clase, a no ser que se cree un nuevo objeto y se acceda a las variables de instancia a través de ese objeto. Si se observa el siguiente trozo de código de ejemplo: class Documento extends Pagina { static int version = 10; int numero_de_capitulos; static void annade_un_capitulo() { numero_de_capitulos++; // esto no funciona } static void modifica_version( int i ) { version++; // esto si funciona } } la modificación de la variable numero_de_capitulos no funciona porque se está violando una de las reglas de acceso al intentar acceder desde un método estático a una variable no estática. Todas las clases que se derivan, cuando se declaran estáticas, comparten la misma página de variables; es decir, todos los objetos que se generen comparten la misma zona de memoria. Los métodos estáticos se usan para acceder solamente a variables estáticas. class UnaClase { int var; UnaClase() { var = 5; } unMetodo() { var += 5; } } En el código anterior, si se llama al método unMetodo() a través de un puntero a función, no se podría acceder a var, porque al utilizar un puntero a función no se pasa implícitamente el puntero al propio objeto (this). Sin embargo, sí se podría acceder a var si fuese estática, porque siempre estaría en la misma posición de memoria para todos los objetos que se creasen de la clase UnaClase. En C++, se puede declarar un método en una clase y definirlo luego dentro de la clase (bajo ciertas condiciones) o definirlo fuera de la clase. A la hora de declararlo, es necesario indicar el tipo de argumentos que necesita, pero no se requiere indicar sus nombres (aunque pueda hacerse). A la hora de definir el método sí tiene que indicarse el nombre de los argumentos que necesita el método. En Java, todos los métodos deben estar declarados y definidos dentro de la clase, y hay que indicar el tipo y nombre de los argumentos o parámetros que acepta. Los argumentos son como variables locales declaradas en el cuerpo del método que están inicializadas al valor que se pasa como parámetro en la invocación del método. En Java, todos los argumentos de tipos primitivos deben pasarse por valor, mientras que los objetos deben pasarse por referencia. Cuando se pasa un objeto por referencia, se está pasando la dirección de memoria en la que se encuentra almacenado el objeto. Si se modifica una variable que haya sido pasada por valor, no se modificará la variable original que se haya utilizado para invocar al método, mientras que si se modifica una variable pasada por referencia, la variable original del método de llamada se verá afectada de los cambios que se produzcan en el método al que se le ha pasado como argumento. El ejemplo java515.java se ilustra el paso de parámetros de tipo primitivo y también el paso de objetos, por valor y por referencia, respectivamente. // Esta clase se usa para instanciar un objeto referencia class MiClase { int varInstancia = 100; } // Clase principal class java515 { // Función para ilustrar el paso de parámetros void pasoVariables( int varPrim,MiClase varRef ) { System.out.println( "--> Entrada en la funcion pasoVariables" ); System.out.println( "Valor de la variable primitiva: "+varPrim ); System.out.println( "Valor contenido en el objeto: "+ varRef.varInstancia ); System.out.println( "-> Modificamos los valores" ); varRef.varInstancia = 101; varPrim = 201; System.out.println( "--> Todavia en la funcion pasoVariables" ); System.out.println( "Valor de la variable primitiva: "+varPrim ); System.out.println( "Valor contenido en el objeto: "+ varRef.varInstancia ); } public static void main( String args[] ) { // Instanciamos un objeto para acceder a sus métodos java515 aObj = new java515(); // Instanciamos un objeto normal MiClase obj = new MiClase(); // Instanciamos una variable de tipo primitivo int varPrim = 200; System.out.println( "> Estamos en main()" ); System.out.println( "Valor de la variable primitiva: "+varPrim ); System.out.println( "Valor contenido en el objeto: "+ obj.varInstancia ); // Llamamos al método del objeto aObj.pasoVariables( varPrim,obj ); System.out.println( "> Volvemos a main()" ); System.out.println( "Valor de la variable primitiva, todavia : "+ varPrim ); System.out.println( "Valor contenido ahora en el objeto: "+ obj.varInstancia ); } } En C++, se puede pasar como parámetro un puntero que apunte a una función dentro de otra función, y utilizar este puntero en la segunda función para llamar a la primera. Esta capacidad no está directamente soportada en Java. Sin embargo, en algunos casos, se puede conseguir casi lo mismo encapsulando la primero función como un método de instancia de un objeto y luego pasar el objeto a otro método, donde el primer método se puede ejecutar a través del objeto. Tanto en Java como en C++, los métodos tienen acceso directo a las variables miembro de la clase. El nombre de un argumento puede tener el mismo nombre que una variable miembro de la clase. En este caso, la variable local que resulta del argumento del método, oculta a la variable miembro de la clase. Cuando se instancia un método se pasa siempre una referencia al propio objeto que ha llamado al método, es la referencia this. |
|