2007/01/12

Abstracción Funcional en Java

En Java, se entiende la abstracción funcional dentro del propio modelo de orientación a objetos, de forma que el hecho de modelar los problemas reales mediante el uso de sus propiedades de abstracción, polimorfismo, herencia y encapsulamiento, da como resultado una propia abstracción funcional en que, efectivamente, se pueden emplear clases y llamar a métodos sin necesidad de conocer cómo realizan las tareas o cómo se comportan dichas clases o métodos.

Puesto que un objeto se compone de una identidad, un estado y un comportamiento, nos interesa a la hora de explicar la abstracción funcional, principalmente su comportamiento. Este comportamiento viene dado por los métodos definidos en la clase del objeto y estos métodos no son más que secciones de código autocontenidas que pueden recibir parámetros de entada y ofrecer una salida, lo que de por sí es la piedra angular de la abstracción funcional. En Java, los métodos no han de ser prototipados, aunque si es el caso de una interfaz o clase abstracta, su cometido es precisamente realizar prototipados de funciones que han de implementar otras clases que hereden o implementen esa interfaz. Asimismo, Java soporta la sobrecarga de métodos (aunque no la de operadores), es decir, métodos con distintas implementaciones según los parámetros que reciben. Así, la definición de un método en Java consiste en indicar un conjunto de modificadores, el nombre del método y, entre paréntesis, el tipo y nombre de cada uno de los argumentos que recibe. El modificador void indica que el método se comporta como un procedimiento. El paso de los parámetros para tipos simples es por valor, mientras que para los objetos es por referencia.
No se pueden declarar métodos dentro de métodos, sólo pueden formar parte directa de una clase, aunque una clase sí que puede tener clases definidas en su propia definición.

Pero para entender por completo la abstracción funcional, debe comprenderse también la comunicación entre objetos, pues un objeto debe poder acceder a los métodos de otro objeto. Para ello, se utiliza una comunicación entre objetos mediante mensajes. Estos mensajes deben corresponderse con métodos definidos en la interfaz del objeto que recibe el mensaje.
Cuando se realiza una llamada a un método desde un objeto, éste queda a la espera de que el método finalice. Una vez finalizado, el objeto continúa con su ejecución. El objeto que realiza la invocación de un método de otro objeto sólo conoce la interfaz de dicho método y su funcionalidad asociada, pero no cómo la realiza.

Sin embargo, este mecanismo requiere que para poder invocar un método, exista previamente el objeto (para recibir el mensaje). Esto no es siempre necesario y a veces puede resultar inadecuado. Para dar mayor flexibilidad al lenguaje, en Java existe un modificador para métodos y atributos static. En el caso de los atributos, este modificador provoca que el valor de estos atributos sea almacenado a nivel de clase y no de objeto, es decir, el valor en un momento determinado es el mismo para todas las instancias de un objeto. En el caso de los métodos, este modificador implica que puede ser invocado sin necesidad de que exista un objeto de esta clase, es decir, el receptor del mensaje es la clase en sí, no el objeto. Esto se debe que una clase no solo modela cómo deben ser los objetos, sino que puede mantener información global sobre sí misma o sus instancias y puede, a su vez, ser receptora de mensajes. Los métodos estáticos son invocados empleando para referenciar el método el nombre de la clase en vez de un nombre de instancia (por ejemplo, es el caso del método que da salida por consola System.out.println).
Por lo tanto, es lógico pensar, como así ocurre, que un método estático sólo puede acceder a variables estáticas. Asimismo, un método estático sólo puede invocar a métodos que también sean estáticos.

Hasta ahora hemos hablado siempre de objetos que invocaban a métodos de otros objetos o clases mediante el envío de mensajes, pero? ¿y cómo comienza la ejecución?
Bien, cuando se inicia la ejecución de un programa, el sistema localiza y ejecuta el método llamado main. Este debe presentar los siguientes modificadores: public, static, void, de forma que quedaría: public static void main(String args[]){ }
El parámetro cadena args es el único argumento que puede aceptar el método principal y, además ha de ser siempre un String []. Este argumento representa los parámetros que se pasan al ejecutar la aplicación (por ejemplo, al lanzarlo en el prompt de una consola del sistema).
Una vez dentro de éste método principal, el comportamiento es el anteriormente explicado de invocaciones mediante mensajes.

Además de los mecanismos ya mencionados, Java permite el encadenamiento de mensajes, de forma que el resultado de una invocación se puede utilizar como objeto receptor de una segunda invocación.
Por ejemplo: String cad = cad1.concat(cad2).concat(cad3);
Esta línea de código implicaría que se mandase un mensaje al objeto cad1 para invocar al método concat con cad2 como argumento. El objeto cad1 devuelve una referencia a un nuevo objeto de tipo String con el resultado de la concatenación. A este objeto resultante se le envía un mensaje para invocar al método concat con el cad3 como argumento, de forma que se obtiene una referencia a un nuevo objeto resultante de tipo String que contiene el resultado de esta última concatenación y, por tanto, la concatenación completa, que se asigna a la variable cadena.
De forma similar, existe el mecanismo de composición de mensajes, mediante el cual, el resultado de una invocación se emplea como argumento de la invocación de otro método.
Por ejemplo: String cad = cad1.concat( cad2.concat(cad3) );
El resultado final es el mismo pero la secuencia de acciones es diferente: primeramente se envía un mensaje a cad2 para invocar al método concat pasando cad3 como argumento. Así, el resultado devuelto es una referencia a un nuevo objeto String que contiene el resultado de la concatenación de cad2 y cad3. Ahora, esta referencia es pasada como argumento de la invocación de concat enviada al objeto cad1, obteniendo como resultado una referencia al resultado de la concatenación final, que se asigna a la variable cadena.

La recursividad en Java existe y funciona de forma muy sencilla: se invoca el método siguiendo el mismo mecanismo de mensajes, guardando las variables locales del método en la pila, guardando copias locales de los parámetros en la pila y guardando la dirección de retorno también en la pila.

Es interesante, además, para el concepto de abstracción funcional, conocer el uso de los paquetes (packages) en Java. Los paquetes no son más que librerías que contienen clases y/o interfaces para ser empleadas. Son unidades de software que pueden ser distribuidas de forma independiente y, también, ser combinadas con otros paquetes para desarrollar nuevas aplicaciones. Además de clases e interfaces, los paquetes pueden contener otros paquetes, y todo tipo de archivos con recursos adicionales (como ficheros de texto o de imágenes)
El contenido de un paquete puede ser empleado de dos formas diferentes: o bien se importa mediante la sentencia import, o bien, calificar el nombre del recurso incluyendo el nombre completo del paquete en que se encuentra. Hay que tener en cuenta que sólo los elementos declarados como públicos pueden ser accedidos desde fuera del paquete.

Bibliografía: