Capítulo I: CONCEPTOS BÁSICOS DEL PARADIGMA ORIENTADO A OBJETOS Yolanda Moyao Martínez Ana Patricia Cervantes Márquez Capítulo I: Conceptos Básicos del Paradigma Orientado a Objetos I.1. Clase y Objeto Objeto Un objeto es una entidad tanto tangible como intangible, que se puede imaginar y que tiene un estado, un comportamiento y una identidad. (Joy2011)(Tho08). Por ejemplo, el cuadrado es un objeto tangible que tiene propiedades tales como longitud, color, etc. Además tiene cierto comportamiento tal que este puede ser dibujado, fraccionado, también se le puede calcular el área, calcular el perímetro, etc. Fig. 1.1. Objeto Tangible Por ejemplo, el seguro es un objeto intangible que tiene propiedades tales como tipo de cobertura, costo, vigencia, etc. Además tiene cierto comportamiento tal que este puede ser contratado, renovado, cancelado, modificado, etc. Fig. 1.2. Objeto Intangible Clase Una clase es un tipo de molde o plantilla que dicta lo que los objetos pueden o no hacer. Así una clase es un conjunto de objetos que comparten una estructura y un comportamiento. (Joy2011)(Tho08). Por ejemplo, figuras geométricas es una clase que define objetos tangibles que tienen propiedades tales como número de lados, área, perímetro, color, etc. Además los objetos que son definidos dentro de esta clase tienen cierto comportamiento tal como calcular área, calcular perímetro, colorearlo, etc. Fig. 1.3. Clase que define objetos tangibles Por ejemplo, servicios es una clase que define objetos intangibles que tienen propiedades tales como tipo de cobertura, costo, vigencia, etc. Además tienen cierto comportamiento tal que este puede ser contratado, renovado, cancelado, modificado, etc. Servicios +Tipo_Cobertura +Costo +Vigencia +Contratar() +Renmovar() +Cancelar() +Modificar() Fig. 1.4. Clase que define objetos intangibles Atributos Los atributos son los datos necesarios para describir los objetos creados a partir de alguna clase en particular (Joy2011). En la clase figuras geométricas los atributos necesarios para describir a los objetos son: número de lados, área, perímetro, color, etc. Fig. 1.5. Atributos del objeto Cuadro En la clase Servicios los atributos necesarios para describir a los objetos son: tipo de cobertura, costo, vigencia, etc. Fig. 1.6. Atributos del objeto Seguro_Médico Método Un método es una secuencia de instrucciones que una clase u objeto sigue para realizar una tarea, es decir, un método es un conjunto de operaciones que manipulan a los atributos del objeto. En la clase figuras geométricas los métodos que manipulan a los atributos de los objetos son: calcular área, calcular perímetro, colorear, etc. En la clase Servicios los métodos que manipulan a los atributos de los objetos son: contratar, renovar, cancelar, modificar, etc. Los métodos se clasifican en dos tipos (Tho08): Métodos de clase: Es un método que al ser ejecutado no es necesario que sea invocado a través de un objeto. Métodos de instancia: Es un método que para ser ejecutado requiere ser invocado a través de un objeto. Por ejemplo: En la clase Seguros el método Cancelar es un método de clase. Creación de un objeto Una vez que una clase se modela o se define, es posible crear o instanciar uno o más objetos que se identifican con dicha clase. Por ejemplo: el triángulo, el círculo, etc. son objetos que se identifican con la clase figuras geométricas. Fig. 1.7. Objeto Círculo Fig. 1.8. Objeto Triángulo Mensaje Un mensaje es una instrucción que se envía a un objeto, el cual se ejecutará al recibirlo; incluye el identificador que contiene la acción a realizar por el objeto junto con los datos que este necesita para efectuar su trabajo. (Joy2011) Por ejemplo: Si se requiere calcular el área del triángulo, solamente, el objeto triangulo tiene que enviar un mensaje solicitando al método calcular_área que efectué tal operación Triangulo.calcular_area(b,h ) Fig.1.9.Objeto triángulo envía mensaje Calcular_area I.2. Abstracción de Datos En términos simples, una abstracción es un proceso mental, mediante el cual se extraen los rasgos esenciales de algo para representarlos por medio de un lenguaje gráfico o escrito. (Mar02) Por ejemplo, en la vida cotidiana hacemos frecuentemente abstracciones cuando describimos lugares, personas, etc. En el caso de los datos, la abstracción se refiere a la metodología que permite diseñar estructuras de datos, representar sus características esenciales. La técnica de la abstracción de datos establece que al diseñar una nueva estructura de datos, ésta pasa a ser un Tipo de Dato Abstracto (TDA), que podrá implementarse en cualquier lenguaje y aplicarse en cualquier concepto. (Mar02) En realidad el concepto TAD ya existe en los lenguajes de programación bajo la forma de los tipos predefinidos (Fra02), como son: enteros, cadenas, etc. La verdadera utilidad de los TAD aparece en el diseño de nuevos tipos de datos. Los TDA guardan valores sobre los cuales se puede aplicar un conjunto dado de operaciones, siendo sobre esta idea que se apoya la programación orientada a objetos. I.3 TDA I.4 Encapsulamiento Encapsulación o encapsulamiento significa reunir en cierta estructura todos los elementos que a determinado nivel de abstracción, se pueden considerar de una misma entidad. Oculta lo que hace un objeto de lo que hacen otros objetos del mundo exterior. (Joy2011) Por ejemplo: en el objeto seguro médico el ocultamiento significa que el usuario del seguro cuando hace uso del mismo, no necesita conocer el procedimiento que se lleva a cabo entre la aseguradora y el hospital que brinda el servicio. I.5 Herencia La herencia es un mecanismo para modelar dos o más entidades que son diferentes pero que comparten muchas características comunes (Tho08). Manifiesta que dichas entidades tienden a organizase de manera jerárquica, donde esta jerarquía no se limita a un solo nivel. Clase Padre La clase Padre, superclase o clase base es aquella contiene los atributes comunes de las entidades (Tho08). Clase Hija Las clases Hijas, subclases o derivadas son todas las clases que heredan a la cases padre. Por ejemplo: Si tenemos como clase padre a la clase servicios podemos generar tres clases hijas tales como Servicios de Vivienda, Servicios Médicos y Servicios Para Auto las cuales heredan atributos y métodos comunes a ellas. Fig. 1.10. Herencia entre clases I.6 Polimorfismo El polimorfismo es la propiedad que le permite a una operación o función tener el mismo nombre en clases diferentes y actuar de modo distinto en cada una de ellas, es decir, implica la capacidad de una operación de ser interpretada solo por el propio objeto que la invoca. (Joy11) Por ejemplo: Si tenemos como clase padre a la clase servicios podemos generar tres clases hijas tales como Servicios de Vivienda, Servicios Médicos y Servicios Para Auto las cuales heredan atributos y métodos comunes a ellas. En la figura 1.10 podemos observar que las clase Seguro de Vivienda y Seguro de Auto utilizan un método Reparar el cual tiene el mismo nombre pero actúa de forma diferente ya que en la Vivienda cuando se habla de reparar nos referimos a la instalación eléctrica, instalación de gas, pintura, etc. Y en el caso de Seguro para Auto cuando hablamos de reparar nos referimos a alguna parte del mismo. BIBLIOGRAFÍA Y REFERENCIAS (Joy11) Luis, Joyanes. A. (2011). Programación en Java 6. Algoritmos, programación orientada a objetos e interfaz gráfica de usuario.(3ª. Edición). Mc Graw Hill. (Mar02)Roman, Martínez. (2002). Estructuras de datos. México: International Thomson Editores, S.A. (Tho08)C.,Thomas Wu., (2008).Introducción a la programación orientada a objetos. Promación en Java.(Primera Edición). Mc Graw Hill. (Fra02)Xavier,Franch Gutiérrez (2002).Estructuras de datos. Especificación, diseño e implementación.(4ª. edición). Alfaomega. Capítulo II: Abstracción del Mundo Real al Paradigma Orientado a Objetos María Luz A. Sánchez Galvéz Mario Anzures García Capítulo II: Abstracción del Mundo real al Paradigma Orientado a Objetos II.1. Principios básicos del modelado de objetos II.2. Modelar Clases y Objetos II.3 Modelar relaciones entre clases II.3.1 Dependencia II.3.2 Asociación II.3.3 Agregación y Composición II.3.4 Herencia e Interfaces BIBLIOGRAFÍA Y REFERENCIAS [AHO74] Aho Ullman, Hopcroft. “The Design and Analysis of Computer Algorithms”, Addison-Wesley, 61-65. 1974. [AKL92] Akl Selim G., “Diseño y Análisis de Algoritmos Paralelos”, Ra-Ma Serie Paradigma, Madrid. 1992. [AND96] Andreoli Jean-Marc, Pareschi Remo. “Integrated Computational Paradigms for flexible Client-Server Communication”. ACM Computing Surveys, Vol. 28, No.2, pp.297-299. 1996. [AND83] Andrews G. R., Schneider F. B. “Concepts and Notations for Concurrent Programming”. Computing Surveys, Vol. 15, No. 1, pp. 3-43. 1983. Capítulo III: LENGUAJE DE PROGRAMACIÓN ORIENTADO A OBJETOS Yalú Galicia Hernández Laura Cuayahuilt Romero Darnes Vilariño Ayala José Andrés Vázquez Flores Graciano Cruz Almanza Capítulo III: Lenguaje de Programación Orientado a Objetos III.1. Tipos de Datos primitivos En Java se tienen dos tipos de datos disponibles, los tipos de datos primitivos y los tipos de datos de referencia, en esta sección se describen los tipos de datos primitivos. Se les llama primitivos porque están integrados en el sistema y en realidad no son objetos, lo cual hace que su uso sea más eficiente. Un tipo de datos es el conjunto de valores que puede tomar una variable; así, el tipo de datos char representa la secuencia de caracteres Unicode y una variable del tipo char podrá tener uno de esos caracteres; los tipos de datos primitivos representar valores escalares o individuales, los cuales pueden ser char o los enteros Hay ocho tipos de datos primitivos soportados por Java. Los tipos de datos primitivos están predefinidos por el lenguaje y nombrados por una palabra clave. Veamos ahora en detalle acerca de los ocho tipos de datos primitivos. La tabla 3.1 muestra los tipos de datos primitivos, sus tamaños en bytes y el rango de valores. Tipo char byte short int long fload double boolean Tamaño en bytes 2 1 2 4 8 4 8 1 bit Rango de valores Mínimo y Máximo ´\0000´… ´\ffff´ -128 a 127 -32768 a 32767 -2147483648 a 2147483647 -9223372036854775808 … +9223372036854775807 3.4*(10-38) … 3.4*(1038) 1.7*(10-308) … 1.7*(10308) false , true Tabla 3.1. Tipos de datos primitivos en Java Enteros: int , byte, short, long. Probablemente el tipo de datos más familiar es el entero o int; adecuado para aplicaciones que trabajan con datos numéricos; en Java hay cuatro tipos de datos enteros byte, short, int y long. El tipo más utilizado, por semejanza de nombre, es int; sus valores se almacenan internamente en 4 bytes (o 32 bits) de memoria. Ejemplos de declaración de variables de tipo numérico: int valor; int valor = 99; int valor1, valor2; int num_parte = 1141, num_items = 45; long sim, jila = 999111444222; Java siempre realiza la aritmética de enteros de tipo int en 32 bits a menos que en la operación intervenga un entero long; por ello conviene utilizar variables de estos tipos cuando se realicen operaciones aritméticas; en el siguiente ejemplo se generan errores por el tipo de variables: short x; int a = 19, b = 5; x = a+b; Al devolver a+b, no se puede asignar a x un valor de tipo int porque es de tipo short; aunque en este caso es mejor declarar las variables de tipo int, el error también se puede resolver forzando una conversación de tipo de datos: x = (short) (a+b); aunque es mejor, en este caso, declararlas variables de tipo int. Las constantes enteras como -1211100, 2700 o 4250 siempre se consideran de tipo int; para que el compilador considere a una constante de tipo long se utiliza el sufijo l o L. Por ejemplo -2312367L. También hay que considerar que si el resultado de una operación sobrepasa el máximo valor entero, no se genera un error de ejecución, sino que se pierden los bits de mayor peso en la representación del resultado. En aplicaciones generales, las constantes enteras se pueden escribir en decimal o base 10, en octal o base ocho o en hexadecimal o base dieciséis. Una constante octal es cualquier número que comienza con un cero y contiene dígitos en el rango del 1 a 7. Una constante hexadecimal comienza con 0x y va seguida de los dígitos 0 a 9 o las letras A a F o bien de a a f. La tabla 3.2 muestra ejemplos de constantes enteras representadas en sus notaciones decimal, hexadecimal y octal. Base 10 decimal 8 10 16 65536 24 17 Base 16 hexadecimal 0x08 0x0A 0x10 0x10000 0x18 0x11 Base 8 octal 010 012 020 0200000 030 021 Tabla 3.2. Constantes enteras en tres bases diferentes Reales: float, double. Los tipos de datos punto flotante representan números reales que contienen un punto decimal, tal como 3.14159, o números muy grandes, tales como 1.85*1015. Java soporta dos formatos de punto flotante. El tipo float (simple precisión) requiere de 4 bytes de memoria y tiene una precisión de 7 dígitos, el tipo double (doble precisión) requiere de 8 bytes de memoria y tiene una precisión de 15 dígitos. Ejemplos de declaración de variables de tipo real (punto flotante): double d = 5.65; float x = -1.5F; Las constantes que representan números reales (punto flotante), Java las considera por defecto double; para que una constante sea considerada de tipo float se añade el sufijo F o f. También se puede expresar que una constante es de tipo double con el sufijo D o d. Caracteres: char. Un carácter es cualquier elemento de un conjunto de caracteres predefinidos o alfabeto. Java fue diseñado con el objetivo de poder ser utilizado en cualquier país, con independencia del tipo de alfabeto. Para poder reconocer cualquier tipo de carácter, los elementos de este tipo utilizan 16 bits, dos bytes, en vez de los 8 bits que utilizan la mayoría de los lenguajes de programación. De esta forma Java puede representar el estándar Unicode, que recoge más de 30000 caracteres distintos procedentes de las distintas lenguas escritas. La mayoría de las computadoras utilizan el conjunto de caracteres ASCII, que se almacenan en el byte de menor peso de un char; el valor inicial de los caracteres ASCII y también Unicode es ´\u0000´ y el último carácter ASCII, ´\u00FF´. Ejemplos: char letra = ´A´ ; char respuesta =´S´; Internamente, los caracteres se almacenan como números. La letra A, por ejemplo, se almacena internamente como el número 65, la letra B es 66, etc. Puesto que los caracteres se almacenan internamente como números, se pueden realizar operaciones aritméticas con datos tipo char. Por ejemplo, se puede convertir una letra minúscula a a una mayuscula A, restando 32 del código ASCII y convirtiendo el entero a char. Así, para realizar la conversión, restar 32 del tipo de datos char, como sigue: char carLetra = ´a´; carLetra = (char)(carLetra – 32); Esto convierte a (código ASCII 97) a A (código ASCII 65). De modo similar , añadiendo 32 convierte el carácter de letra mayúscula a minúscula: carLetra = (char) (carLetra + 32); Existen caracteres que tienen un propósito especial y no se pueden describir utilizando el método normal. Java proporciona secuencias de escape. Por ejemplo, el literal carácter de un apóstrofe se puede escribir como ´\´´ y el carácter nueva línea ´\n´. La tabla 3.3 muestra las diferentes secuencias de escape en Java. Código de Escape ´\n´ ´\r´ ´\t´ ´\b´ ´\f´ ´\\´ ´\´´ ´\”´ ´\000´ ´\uhhhh´ Significado Nueva línea Retorno de carro Tabulación Retroceso de espacio Avance de página Barra inclinada inversa Comilla simple Doble comilla Número octal Número hexadecimal Códigos ASCII Dec Hex 13 10 0D 0A 13 0D 9 9 8 8 12 0C 92 5C 39 27 34 22 Todos Todos Todos Todos Tabla 3.3. Caracteres de secuencia de escape (códigos) Booleananos: boolean. Los compiladores de Java incorporan el tipo de dato boolean, cuyos valores son verdadero (true) y falso (false). Las expresiones lógicas devuelven valores de este tipo true o false; se forman con operandos y operadores relacionales o lógicos, por ejemplo: boolean encontrado, indicador = false; encontrado = (x>2) && (x<10) ; // encontrado toma el valor ver//dadero si x está comprendido //entre 2 y 10. La mayoría de las expresiones lógicas aparecen es estructuras de control que sirven para determinar la secuencia en que se ejecutan las sentencias Java. Raramente se tiene la necesidad de leer valores boolean como dato de entrada o de visualizar valores boolean como resultados de programa. III.2. Declaración de clases Puesto que Java es un lenguaje orientado a objetos, un programa Java se compone solamente de objetos. Un objeto es la concreción de una clase, y que una clase equivale a la generalización de un tipo específico de objetos. La clase define los atributos del objeto, así como los métodos para manipularlos. Formalmente: Una clase es un tipo definido que describe los atributos y los métodos de los objetos que se crearán a partir de la misma. Los atributos definen el estado de un determinado objeto y los métodos son las operaciones que definen su comportamiento. Forman parte de estos métodos los constructores, que permiten iniciar un objeto, y los destructores, que permiten destruirlos. Los atributos y los métodos se denominan en general miembros de la clase. La definición de una clase consta de dos partes: el nombre de la clase precedido por la palabra reservada class, y el cuerpo de la clase encerrado entre llaves. Es decir: class Nombreclase{ cuerpo de la clase … } El cuerpo de la clase consta de modificadores de acceso (public, protected y private), atributos, mensajes y métodos. Un método implícitamente define un mensaje (el nombre del método es el mensaje). Los atributos constituyen la estructura interna de los objetos de una clase. class circulo{ private double x,y; private double radio; //… } Los métodos generalmente forman lo que se denomina interfaz o medio de acceso a la estructura interna de los objetos; ellos definen las operaciones que se pueden realizar con sus atributos. Desde el punto de vista de la Programación orientada a Objetos, el conjunto de todos los métodos se corresponde con el conjunto de mensajes a los que los objetos de una clase pueden responder. En Java un método es una definición incluida siempre dentro del cuerpo de una clase. Los métodos no se pueden anidar. El concepto de clase incluye la idea de ocultación de los datos, que consiste en que no se puede acceder a los datos directamente, sino que hay que hacerlo a través de métodos de la clase, estos métodos se denominan métodos de acceso. Para controlar el acceso a los miembros de una clase, java provee las palabras clave private(privado), protected(protegido) y public (público), aunque es posible omitirlas y el acceso es predeterminado. Un miembro declarado public (público) está accesible para cualquier otra clase o subclase que necesite utilizarlo. La interfaz pública de una clase o simplemente interfaz está formada por todos los miembros públicos de la misma, es importante recalcar que los atributos static de la clase generalmente son declarados públicos. Un miembro declarado private(privado) es accesible solamente por los métodos de su propia clase, es decir no puede ser accedido por los métodos de cualquier otra clase incluidas las subclases. Un miembro declarado protected(protegido) se comporta exactamente igual que uno privado para los métodos de cualquier otra clase, excepto para los métodos de las clases del mismo paquete o de sus subclases con independencia del paquete al que pertenezcan, para las que se comportan como un miembro público. A continuación se enumeran algunas características importantes de las clases: Todas las variables y funciones de Java deben pertenecer a una clase. No hay variables ni funciones globales Si una clase hereda de otra (extends), hereda todas sus variables y métodos. Java ofrece una jerarquía de clases estándar de la que pueden derivar las clases que los usuarios crean. Una clase sólo puede heredar de una única clase (en Java no hay herencia múltiple). Si al definir una clase no se especifica de qué clase deriva, por defecto la clase deriva de Object. La clase Object es la jerarquía de todas las clases en Java. En un fichero se pueden definir varias clases, pero en un fichero no puede haber más de una clase public. Este fichero se debe llamar como la clase public que contiene con extensión *.java. Si una clase contenida en un fichero no es public, no es necesario que el fichero se llame como la clase. Los métodos de una clase pueden referirse de modo global al objeto de esa clase que se aplica por medio de la referencia this. Las clases se pueden agrupar en package, poniendo al inicio del fichero (package Packagename;). Esta agrupación en packages está relacionada con la jerarquía de directorios y ficheros en las que se guardan las clases. El control de acceso a una clase determina la relación que tiene esa clase con otras clases de otros paquetes. Se pueden distinguir dos niveles de acceso: de paquete y público. Una clase con nivel de acceso de paquete sólo puede ser utilizada por las clases de un paquete( no está disponibles para otros paquetes, ni siquiera para los subpaquetes). Una clase pública puede ser utilizada por cualquier otra clase de otro paquete. Declarar un método final supone que la clase se ejecute con más eficiencia, porque el compilador puede colocar el código de bytes del método directamente en lugar del programa donde se invoque a dicho método, ya que se garantiza que el método no va a cambiar. Cuando un clase se declara final se impide que de esa clase se puedan derivar subclases y todos sus métodos se convierten automáticamente en final, esto no es muy conveniente ya que se sacrifica una de las características más importantes del Paradigma orientado a Objetos, que es la reutilización de código. Un objeto es un ejemplar concreto de una clase. Las clases son como los tipos variables, mientras que los objetos son como variables concretas de un tipo determinado. Nombreclase objeto1; Nombreclase objeto2; El operador new El operador new crea una instancia de una clase asignando la cantidad de memoria necesaria de acuerdo al tipo de objeto. El operador new se utiliza en conjunto con un constructor. El operador new regresa una referencia a un nuevo objeto. Acceso a variables y métodos Una vez que se ha creado un objeto, seguramente se querrá hacer algo con él. Tal vez se requiera obtener información de éste, se quiera cambiar su estado, o se necesite que realice alguna tarea. Los objetos tienen dos formas de hacer esto: Manipular sus variables directamente Para accesar a las variables de un objeto se utiliza el operador punto ( . ). La sintaxis es la siguiente: nombreObjeto.nombreVariable; Llamar a sus métodos Para llamar a los métodos de un objeto, se utiliza también el operador punto ( . ). La sintaxis es la siguiente: nombreObjeto.nombreMetodo([lista de argumentos opcionales]); Ejemplo /* Usuario2.java */ class Usuario2 { String nombre; int edad; String direccion; Usuario2( ) /* Equivale al contructor por omisión */ { nombre = null; edad = 0; direccion = null; } Usuario2(String nombre, int edad, String direccion) { this.nombre = nombre; this.edad = edad; this.direccion = direccion; } Usuario2(Usuario2 usr) { nombre = usr.getNombre(); edad = usr.getEdad(); direccion = usr.getDireccion(); } void setNombre(String n) { nombre = n; } String getNombre() { return nombre; } void setEdad(int e) { edad = e; } int getEdad() { return edad; } void setDireccion(String d) { direccion = d; } String getDireccion() { return direccion; } } Ejemplo /* ProgUsuario2.java */ class ProgUsuario2 { void imprimeUsuario(Usuario2 usr) { // usr.nombre equivale en este caso a usr.getNombre() System.out.println("\nNombre: " + usr.nombre ); System.out.println("Edad: " + usr.getEdad() ); System.out.println("Direccion: " + usr.getDireccion() +"\n"); } public static void main(String args[]) { ProgUsuario2 prog = new ProgUsuario2( ); Usuario2 usr1,usr2; /* Se declaran dos objetos de la clase Usuario2 */ /* Se utiliza el constructor por omisión */ usr1 = new Usuario2( ); prog.imprimeUsuario(usr1); /* Se utiliza el segundo constructor de Usuario2 */ usr2 = new Usuario2("Eduardo",24,"Mi direccion"); prog.imprimeUsuario(usr2); /* Se utiliza el tercer constructor de Usuario2 */ usr1 = new Usuario2(usr2); /*En este caso usr1.setDireccion("nuevoValor"); equivale a usr1.direccion = "nuevoValor"; */ usr1.setDireccion("Otra direccion"); prog.imprimeUsuario(usr1); prog.imprimeUsuario(usr2); } } Constructor En java una forma de asegurar que los objetos siempre contengan valores válidos es escribir un constructor. Un constructor es un método especial de una clase que es llamado automáticamente siempre que sea un objeto de la misma. En Java una forma de asegurar que los objetos contengan siempre valores válidos es escribir un constructor. Un constructor es un método especial de una clase que es llamado automáticamente siempre que se crea un objeto de la misma. Cuando se crea un objeto Java hace lo siguiente: Asigna memoria para el objeto por medio del operador new Inicia los atributos de ese objeto Llama al constructor de la clase El constructor tiene el mismo nombre que la clase a la que pertenece, no se hereda, no puede retornar un valor y no puede ser declarado final, static, abstract, synchronized o native. Constructores múltiples Cuando se declara una clase en Java, se pueden declarar uno o más constructores (constructores múltiples) opcionales que realizan la iniciación cuando se instancia un objeto de dicha clase. Para la clase Usuario del ejemplo anterior no se especificó ningún constructor, sin embargo, Java proporciona un constructor por omisión que inicia las variables del objeto a sus valores predeterminados. Ejemplo /* ProgUsuario.java */ class ProgUsuario { public static void main(String args[]) { Usuario usr1, usr2; /* Se declaran dos objetos de la clase Usuario */ boolean si_no; usr1 = new Usuario(); /* Se utiliza el constructor por omisión */ si_no = usr1 instanceof Usuario; if(si_no == true) System.out.println("\nEl objeto usr1 SI es instancia de Usuario."); else System.out.println("\nEl objeto usr1 NO es instancia de Usuario."); usr2 = usr1; /* usr1 y usr2 son el mismo objeto */ si_no = usr2 instanceof Usuario; if(si_no == true) System.out.println("\nEl objeto usr2 SI es instancia de Usuario."); else System.out.println("\nEl objeto usr2 NO es instancia de Usuario."); } } Destructor De la misma forma que existe un método que se ejecuta automáticamente cada vez que se construye un objeto, también existe un método que se invoca automáticamente cada vez que se destruye. Este método recibe el nombre de destructor y en el caso concreto de Java se corresponde con finalize. Cuando un objeto es destruido ocurren varias cosas: se llama al método finalize y después, el recolector de basura se encarga de eliminar el objeto, es decir, eliminar la memoria y todos los recursos que formen parte de éste objeto. Un objeto es destruido automáticamente cuando se eliminan todas las referencias al mismo. Una referencia a un objeto puede ser eliminada porque el flujo de ejecución salga fuera del ámbito donde ella está declarada, o porque explícitamente se le asigne el valor null. Atributos y Métodos static Un atributo static no es un atributo específico de un objeto, es un atributo de la clase, es un atributo del que sólo hay una copia y comparten todos los objetos de la clase. Un método declarado static carece de la referencia this por lo que no puede ser invocado para un objeto de su clase, sino que se invoca donde se necesite utilizar la operación para la que ha sido escrito, es por ello que un método static no puede acceder a un miembro no static de su clase. static valorRetorno nombreMetodo([lista argumentos opcionales]) { /* cuerpo del método */ } Para acceder a las variables o métodos de clase se utiliza el mismo operador punto ( . ). Aunque se puede acceder a las variables y métodos de clase a través de un objeto, está permitido y se recomienda utilizar mejor el nombre de la clase. /* Utilizar esto */ nombreClase.nombreVarClase; nombreClase.nombreMetodoClase(); /* en lugar de esto */ nombreObjeto.nombreVarClase; nombreObjeto.nombreMetodoClase(); Ejemplo /* Usuario3.java */ class Usuario3 { static char MAS = 'm'; static char FEM = 'f'; String nombre; int edad; String direccion; char sexo; Usuario3( ) { nombre = null; edad = 0; direccion = null; sexo = '\0'; } Usuario3(String nombre, int edad, String direccion,char sexo) { this.nombre = nombre; this.edad = edad; this.direccion = direccion; this.sexo = sexo; } Usuario3(Usuario3 usr) { nombre = usr.getNombre(); edad = usr.getEdad(); direccion = usr.getDireccion(); sexo = usr.getSexo(); } void setNombre(String n) { nombre = n; } String getNombre( ) { return nombre; } void setEdad(int e) { edad = e; } int getEdad() { return edad; } void setDireccion(String d) { direccion = d; } String getDireccion( ) { return direccion; } void setSexo(char s) { sexo = s; } char getSexo( ) { return sexo; } public String toString() { return nombre; } } /* ProgUsuario3.java */ class ProgUsuario3 { static int NUM_USUARIOS = 0; static java.util.Vector usuarios = new java.util.Vector(); String nombreObj = null; ProgUsuario3(String nombre) { this.nombreObj = nombre; } static int getNumUsuarios() { return NUM_USUARIOS; } static void imprimeUsuario(Usuario3 usr) { System.out.println("\nNombre: " + usr.nombre ); System.out.println("Edad: " + usr.getEdad() ); System.out.println("Sexo: " + usr.getSexo() ); System.out.println("Direccion: " + usr.getDireccion() ); } void addUsuario(Usuario3 usr) { usuarios.addElement(usr); System.out.print(usr.toString( this.toString() +","); NUM_USUARIOS ++; } )+ " void delUsuario(Usuario3 usr) { boolean b = usuarios.removeElement(usr); if( b == true ) { NUM_USUARIOS--; agregado por el "+ System.out.print(usr.toString( )+ " eliminado por el this.toString() +","); } else System.out.println("No se pudo eliminar al usuario."); } "+ public String toString() { return nombreObj; } public static void main(String args[]) { ProgUsuario3 obj1 = new ProgUsuario3("objeto1"); ProgUsuario3 obj2 = new ProgUsuario3("objeto2"); Usuario3 usr1,usr2,usr3,usr4; usr1 = new Usuario3( ); usr2 = new Usuario3("Usuario A",Usuario3.FEM); usr1 = new Usuario3(usr2); usr1.setNombre("Usuario A"); usr3 = new Usuario3("Usuario C",Usuario3.MAS); usr4 = new Usuario3("Usuario D",Usuario3.MAS); B",24,"La direccion C",35,"La direccion D",15,"La direccion obj1.addUsuario(usr1); System.out.println( "\t Total: " +ProgUsuario3.getNumUsuarios() ); obj2.addUsuario(usr2); System.out.println( "\t Total: " +obj1.getNumUsuarios() ); obj1.addUsuario(usr3); System.out.println( "\t Total: " +ProgUsuario3.NUM_USUARIOS ); obj2.addUsuario(usr4); System.out.println( "\t Total: " +getNumUsuarios() +"\n"); obj2.delUsuario(usr4); System.out.println( "\t Total: " +ProgUsuario3.getNumUsuarios() ); obj1.delUsuario(usr3); System.out.println( "\t Total: " +obj1.getNumUsuarios() ); obj2.delUsuario(usr2); System.out.println( "\t Total: " +ProgUsuario3.NUM_USUARIOS ); obj1.delUsuario(usr1); System.out.println( "\t Total: " +getNumUsuarios() +"\n"); } } III.3 Relaciones entre clases La asociación De los temas anteriores recordamos que una asociación relaciona instancias de dos clases. Existe una asociación entre dos clases cuando una instancia de una clase debe saber sobre otra instancia para llevar a cabo sus funciones En una asociación, dos instancias A y B relacionadas entre sí existen de forma independiente. No hay una relación fuerte. La creación o desaparición de una de ellas implica únicamente la creación o destrucción de la relación entre ellas y nunca la creación o destrucción del otro. Por ejemplo, un cliente puede tener varios pedidos de compra o ninguno. La relación de asociación expresa una relación (unidireccional o bidireccional) entre las instancias a partir de las clases conectadas. El sentido en que se recorre la asociación se denomina navegabilidad de la asociación. Cada extremo de la asociación se caracteriza por el rol o papel que juega en dicha relación el objeto situado en cada extremo. La cardinalidad o multiplicidad es el número mínimo y máximo de instancias que pueden relacionarse con la otra instancia del extremo opuesto de la relación. Por defecto es 1. Uno y sólo uno (por defecto) 0..1 Cero a uno. También (0,1) M..N Desde M hasta N (enteros naturales) 0..* Cero a muchos 1..* Uno a muchos (al menos uno) 1,5,9 Uno o cinco o nueve Agregación y Composición En una relación todo-parte una instancia forma parte de otra. En la vida real se dice que A está compuesto de B o que A tiene B. La diferencia entre asociación y relación todo-parte radica en la asimetría presente en toda relación todo-parte. En teoría se distingue entre dos tipos de relación todo-parte: a) la agregación es una asociación binaria que representa una relación todo-parte (pertenece a tiene un, es parte de). Por ejemplo, un centro comercial tiene clientes. b) la composición es una agregación fuerte en la que una instancia ‘parte’ está relacionada, como máximo, con una instancia ‘todo’ en un momento dado, de forma que cuando un objeto ‘todo’ es eliminado, también son eliminados sus objetos ‘parte’. Por ejemplo: un rectángulo tiene cuatro vértices, un centro comercial está organizado mediante un conjunto de secciones de venta… A nivel práctico se suele llamar agregación cuando la relación se plasma mediante referencias (lo que permite que un componente esté referenciado en más de un compuesto). Así, a nivel de implementación una agregación no se diferencia de una asociación binaria. Por ejemplo: un equipo y sus miembros. Por otro lado, se suele llamar composición cuando la relación se conforma en una inclusión por valor (lo que implica que un componente está como mucho en un compuesto, pero no impide que haya objetos componentes no relacionados con ningún compuesto). En este caso si se destruye el compuesto se destruyen sus componentes. Por ejemplo: un ser humano y sus miembros. Algunas relaciones pueden ser consideradas agregaciones o composiciones, en función del contexto en que se utilicen. La dependencia o relación de uso Una clase A usa una clase B cuando no contiene atributos de la clase B pero, o bien utiliza alguna instancia de la clase B como parámetro en alguno de sus métodos para realizar una operación, o bien accede a sus atributos (clases con métodos amigos). Ejemplos de Agregación o Composición Como se ha comentado anteriormente, la agregación o composición son mecanismos diferentes de la herencia que consiste en que uno o más atributos de una clase pertenecen a una o más clases previamente declaradas. Es decir, un objeto puede componerse de otros pertenecientes a otras clases. Por ejemplo, la clase Persona se compone de dos variables de instancia, una de la clase String y otra de la clase Fecha: public class Persona { String nombre; Fecha fechaNacimiento; public void asignaDatos(String nombre, Fecha f) { this.nombre = nombre; fechaNacimiento = f; } public String toString() { return nombre + " nacido el dia " + fechaNacimiento.toString(); } } Se dice que la clase Persona es una agregación de las clases String y Fecha. La clase PruebaPersona muestra un ejemplo de uso de la clase Persona: public class PruebaPersona { public static void main (String [] args ) { Persona p = new Persona(); Fecha n = new Fecha(11,2,2002); p.asignaDatos("Miguel Angel Garcia", n); System.out.println(p.toString()); } } La asociación de clases puede emplearse como técnica que implementa la denominada anidación o descomposición de código. En el siguiente ejemplo, la clase Fecha se compone de otras tres clases: Dia, Mes y Anho. Estas tres clases se encapsulan como clases internas dentro de la clase Fecha. public class Fecha { private Dia dd; private Mes mm; private Anho aa; public Fecha(int d, int m, int a) { this.dd = new Dia(d); this.mm = new Mes(m); this.aa = new Anho(a); } public void siguienteDia() { dd.siguiente(); } private class Dia { private int d; // 1 <= d <= mes.dias() public Dia(int d){ this.d = d; } public void siguiente() { d = d + 1; verificar(); } private void verificar() { if (d > mm.dias()) { d = 1; mm.siguiente(); } } } private class Mes { private int m; private final int[] diasDelMes = {0,31,28,31,30,31,30,31,31,30,31,30,31}; /* 1 2 3 4 5 6 7 8 9 10 11 12) */ public Mes (int m) { this.m = m; } public int dias() { int result = diasDelMes[m]; if (m==2 && aa.esBisiesto()) { result = result+1; } return result; } public void siguiente() { m = m + 1; verificar(); } private void verificar() { if (m>12) { m=1; aa.siguiente(); } } } private class Anho { private int a; public Anho(int a) { this.a = a; } public void siguiente() { a=a+1; } public boolean esBisiesto() { return ((a % 4 == 0) && (a % 100 !=0) || (a % 400 == 0)); } } } Las clases Dia, Mes y Anho son muy simples y se distribuyen las funcionalidades de una forma fácilmente reconocible. Los identificadores facilitan la legibilidad del código lo que ayuda a que la descomposición extrema sea la base de una comprehensión, desarrollo y verificación independiente e incremental de cada una de las cuatro clases, así como la de sus métodos. III.4 Declaración de herencia e interfaces III.5 Polimorfismo El polimorfismo es una característica importante en la programación orientada a objetos, después de la herencia. Esta característica, inexistente en la programación estructurada, permite al programador continuar con el uso del importante concepto que es la “abstracción”. Antes de presentar la definición de polimorfismo, es importante comentar algunos conceptos básicos, que si bien son independientes del tema, son necesarios para su comprensión [Cru13][4]. Concepto: Idea que concibe o forma el entendimiento. Especificar: Explicar, declarar con individualidad de algo. Abstraer: Separar por medio de una operación intelectual las cualida des de un objeto para considerarlas aisladamente o para considerar el mismo objeto en su pura esencia o noción. Generalizar: Abstraer lo que es común y esencial a muchas cosas, para formar un concepto general que las comprenda todas. Clase Abstracta: Clase que no será instanciada, pero cuyas clases desciendentes poseen instancias. Permiten agrupar, también llamado factorizar, los atributos y los métodos. Método Abstracto: Método sin cuerpo definido. Además de los conceptos previos, es conveniente mencionar que dependiendo del autor se utilizan diferentes referencias a las clases relacionadas en la herencia y por consecuencia en el polimorfismo. Los conceptos utilizados son: Clase, Subclase; Superclase, Clase Derivada; Clase Padre, Clase Hija; Clase Superior, Clase Inferior; etc. La definición de polimorfismo puede ser descrita con enfoques diferentes, es decir, desde el punto de vista de los Métodos, de los Objetos o de las Llamadas. A continuación se presentan tres diferentes definiciones, todas ellas válidas de Polimorfismo: “Capacidad que tienen algunos métodos de presentar “múltiples formas”, en el sentido de que una llamada a un mismo método puede ofrecer comportamientos distintos en función del contexto en el que se ejecute” [1]. “El Polimorfismo permite que diferentes objetos respondan de modo diferente al mismo mensaje. Y adquiere su máxima potencia cuando se utiliza en unión de herencia” [Joy98]. “Característica que permite referirse a objetos de clases diferentes mediante la misma llamada y realizar “la misma operación” de diferentes formas, según sea el objeto que se referencia en ese momento” [Cru13]. Estas definiciones, suelen presentarse en POO y generan confusión a buena parte de los estudiantes. Debemos enfatizar que estas definiciones son válidas y muy usadas en el medio, pero para programadores principiantes es necesario hacer un desglose de los pasos y los elementos involucrados en el concepto de polimorfismo. Por esta razón, en estas notas presentamos los siguientes aspectos referentes al concepto de polimorfismo, con el objetivo de que el estudiante comprenda en toda su magnitud este concepto. El polimorfismo involucra inicialmente a dos clases relacionadas por herencia. En cada una de las clases, existe definido, un método con el mismo nombre. Los dos métodos tienen el mismo nombre, pero cuerpo diferente. Existen dos formas de usar el polimorfismo, llamados: Sobreescritura y Clases Abstractas. La llamada a uno de los dos métodos depende del objeto que se referencia en ese momento y por tal motivo la respuesta es distinta. La herencia extiende el polimorfismo a subclases de subclases. III.5.1 Clases Abstractas Esta forma de usar el polimorfismo consiste en definir métodos con el mismo nombre en clases relacionadas por Herencia, con la característica de que en la superclase se define un método abstracto con cuerpo vacío. En Java la superclase misma debe definirse como clase abstracta. Las clases abstractas permiten definir atributos y métodos que existen en todas las subclases derivadas de la superclase. La superclase debe definirse abstracta ya que incluye al menos un método abstracto. Remarquemos que un método abstracto tiene definido su cuerpo vacío y que será redefinido en las subclases. El argumento o justificación para usar clases abstractas y no Sobreescritura radica en que en este momento del diseño no se cuenta con una definición para dicho método abstracto. A continuación se muestran ejemplos desarrollados en Java que son descritos detalladamente. Ejemplo de polimorfismo (mediante clases abstractas) del método abstracto CalculaPeri de la clase abstracta Figura, del método CalculaPeri de la clase Cuadrado y del método CalculaPeri de la clase Rectangulo (ejemplo modificado de [Joy98]). En el siguiente ejemplo se muestra la clase abstracta Figura que empieza en la línea 2 y termina en la línea 18. Nótese que en la línea 17 se definió el método abstracto CalculaPeri. Posteriormente se define la clase Cuadrado derivada de la clase abstracta Figura que inicia en la línea 19 y termina en la línea 24, en esta clase se redefine el método CalculaPeri para un cuadrado (línea 21), lo cual constituye una redefinición del método abstracto de la línea 17. Además, se define la clase Rectangulo derivada de la clase abstracta Figura que inicia en la línea 25 y termina en la línea 34, en esta clase se redefine nuevamente el método CalculaPeri para un rectángulo (línea 30), lo cual constituye una redefinición del método abstracto de la línea 17. De la línea 35 a la línea 47 se define la clase FiguraPolimorfismo. En el método main se crean dos objetos llamados C y R, donde C es una instancia de la clase Cuadrado y R es una instancia de la clase Rectangulo, esto se ve en la líneas 37 y 38 respectivamente. Y finalmente se llama al método CalculaPeri para calcular el perímetro correspondiente de cada uno de los objetos, en las líneas 40 y 44. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package figurapolimorfismo; abstract class Figura { protected int ancho; protected int perimetro; public Figura() { ancho = 0; perimetro = 0; } public void GuardaAncho(int x) { ancho = x; } public int SacaPerimetro() { return perimetro; } abstract void CalculaPeri(); } class Cuadrado extends Figura { public void CalculaPeri() { perimetro = ancho*4; } } class Rectangulo extends Figura { private int alto; public void GuardaAlto( int x) { alto = x; } public void CalculaPeri() { perimetro = (ancho*2) + (alto*2); } } public class FiguraPolimorfismo { public static void main(String[] args) { Cuadrado C = new Cuadrado(); Rectangulo R = new Rectangulo(); C.GuardaAncho(3); C.CalculaPeri(); System.out.println("El perimetro del cuadrado es:"+C.SacaPerimetro()); 42 R.GuardaAncho(3); 43 44 45 R.GuardaAlto(4); R.CalculaPeri(); System.out.println("El perimetro del rectangu lo es:"+R.SacaPerimetro()); 46 } 47 } A continuación se muestra el resultado de la ejecución del código anterior. run: El perimetro del cuadrado es:12 El perimetro del rectangulo es:14 BUILD SUCCESSFUL (total time: 0 seconds) Ejemplo de polimorfismo (mediante clases abstractas) de los métodos abstracto CalculaSalario e ImprimeRecibo de la clase abstracta Persona, de los métodos CalculaSalario e ImprimeRecibo de la clase Academico y de los métodos CalculaSalario e ImprimeRecibo de la clase Administrativo (ejemplo modificado de [3]). En el siguiente ejemplo se muestra la clase abstracta Persona que empieza en la línea 2 y termina en la línea 12. Nótese que en la línea 10 se definió el método abstracto CalculaSalario y en la línea 11 el método abstracto ImprimeRecibo. Posteriormente se define la clase Academico derivada de la clase abstracta Persona que inicia en la línea 13 y termina en la línea 24, en esta clase se redefinen los métodos CalculaSalario e ImprimeRecibo (líneas 16 y 19) con su código correspondiente. Además, se define la clase Administrativo derivada de la clase abstracta Persona que inicia en la línea 25 y termina en la línea 36, en esta clase se redefinen nuevamente los métodos CalculaSalario e ImprimeRecibo (líneas 28 y 31). De la línea 37 a la línea 52 se define la clase PersonaPolimorfismo. En el método main se crean dos objetos llamados p y a, donde p es una instancia de la clase Academico y a es una instancia de la clase Administrativo, esto se ve en la líneas 39 y 40 respectivamente. Finalmente se llama al método CalculaSalario e ImprimeRecibo en las líneas 44 y 45 para el objeto p y se llama al método CalculaSalario e ImprimeRecibo en las líneas 49 y 50 para el objeto a. 1 package personapolimorfismo; 2 abstract class Persona { 3 protected String Nombre; 4 protected int SalXdia; 5 protected int Salario; 6 public Persona() { Nombre=""; SalXdia=0; } 7 public void GuardaNombre(String x) {Nombre=x; } 8 public void GuardaSalario(int x) {SalXdia = x;} 9 public String SacaNombre() { return(Nombre);} 10 abstract void CalculaSalario();//Depende de la //persona 11 abstract void ImprimeRecibo(); 12 } 13 class Academico extends Persona { 14 private int Becas; 15 public void GuardaBecas(int x) { Becas = x; } 16 public void CalculaSalario() { 17 Salario = (15 * SalXdia) + (Becas*SalXdia); 18 } 19 public void ImprimeRecibo() { 20 System.out.println("RECIBO BUAP PARA PROFESOR"); 21 System.out.println("Nombre: " + Nombre + " Becas: " + Becas); 22 System.out.println("Salario: " + Salario); 23 } 24 } 25 class Administrativo extends Persona { 26 private int Canasta; 27 public void GuardaCanasta(int x){Canasta = x;} 28 public void CalculaSalario() { 29 Salario = (15 * SalXdia) + Canasta; 30 } 31 public void ImprimeRecibo() { 32 System.out.println("RECIBO BUAP PARA ADMINISTRATIVO"); 33 System.out.println("Nombre: " + Nombre + " Canasta:" + Canasta); 34 System.out.println("Salario: " + Salario); 35 } 36 } 37 public class PersonaPolimorfismo { 38 public static void main(String[] args) { 39 Academico p = new Academico(); 40 Administrativo a = new Administrativo(); 41 p.GuardaNombre("Salvador Lopez"); 42 p.GuardaSalario(500); 43 p.GuardaBecas(5); 44 p.CalculaSalario(); 45 p.ImprimeRecibo(); 46 a.GuardaNombre("Rocio Saavedra"); 47 a.GuardaSalario(300); 48 a.GuardaCanasta(100); 49 a.CalculaSalario(); 50 a.ImprimeRecibo(); 51 } 52 } A continuación se muestra el resultado de la ejecución del código anterior. run: RECIBO BUAP PARA PROFESOR Nombre: Salvador Lopez Becas: 5 Salario: 10000 RECIBO BUAP PARA ADMINISTRATIVO Nombre: Rocio Saavedra Canasta:100 Salario: 4600 BUILD SUCCESSFUL (total time: 0 seconds) III.5.2 Sobrecarga La sobrecarga de métodos es la característica que permite definir dos o más métodos, con el mismo nombre, dentro de una misma clase. La diferencia entre los métodos radica en el número de parámetros y/o en el tipo de dato de los parámetros. Los constructores métodos ejecutados en la creación del objeto- también pueden ser sobrecargados. La sobrecarga no es una propiedad específica de los lenguajes orientados a objetos. Lenguajes tales como C y PASCAL soportan operaciones sobrecargadas [Joy98]. Tomando esto como base consideramos que la sobrecarga de métodos es una característica que debe estudiarse antes del concepto de Polimorfismo, que se verá en la siguiente sección. A continuación se muestran ejemplos desarrollados en Java que son descritos detalladamente. Ejemplo de sobrecarga de constructores en la clase Esfera (ejemplo modificado de [1]) En el siguiente ejemplo se muestra la clase Esfera que empieza en la línea 2 y termina en la línea 9. Nótese que en la línea 4 se definió el método constructor Esfera sin parámetros y en la línea 5 se define otro método constructor con el parámetro r. La existencia de dos métodos con el mismo nombre y diferente número de parámetros, es un ejemplo de sobrecarga. Formalmente se dice que se sobrecargó el método Esfera. De la línea 10 a la línea 17 se define la clase EsferaSobrecarga. En el método main se crean dos objetos llamados miesfera1 y miesfera2 usando diferente constructor y finalmente se llama al método Escribe para cada uno de los objetos. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package esferasobrecarga; class Esfera { double radio; Esfera( ) { radio = 0.0; } Esfera( double r ) { radio = r; } public void Escribe() { System.out.println("El Radio es: " + radio); } } public class EsferaSobrecarga { public static void main(String[] args) { Esfera miesfera1 = new Esfera( ); Esfera miesfera2 = new Esfera( 3.0 ); miesfera1.Escribe(); miesfera2.Escribe(); } 17 } A continuación se muestra el resultado de la ejecución del código anterior. run: El Radio es: 0.0 El Radio es: 3.0 BUILD SUCCESSFUL (total time: 0 seconds) III.5.3 Sobreescritura Esta forma de usar el polimorfismo consiste en definir métodos con el mismo nombre en clases relacionadas por Herencia. En ambas clases existe una definición de estos métodos con cuerpo no vacío. Normalmente el código del método existente en la subclase es de mayor tamaño que el existente en la superclase. El objetivo de asignarles el mismo nombre, es debido a que realizarán “la misma operación”, donde la misma operación es desde el punto de vista abstracto. Por ejemplo, los métodos que suman vectores de una, dos, tres,…, n dimensiones; deben llamarse todos “suma”. A continuación se muestran ejemplos desarrollados en Java que son descritos detalladamente. Ejemplo de sobreescritura del método EscribeInfo de la clase Esfera y del método EscribeInfo de la clase Planeta (ejemplo modificado de [1]). En el siguiente ejemplo se muestra la clase Esfera que empieza en la línea 2 y termina en la línea 7. Nótese que en la línea 5 se definió el método EscribeInfo sin parámetros. Posteriormente se define la clase Planeta derivada de la clase Esfera que inicia en la línea 8 y termina en la línea 14, en esta clase se redefine el método EscribeInfo sin parámetros (línea 12), lo cual constituye una sobreescritura del método EscribeInfo de la línea 5. Formalmente se dice que se sobreescribió el método EscribeInfo. De la línea 15 a la línea 25 se define la clase EsferaPolimorfismo. En el método main se crean dos objetos llamados E y P, donde E es una instancia de la clase Esfera y P es una instancia de la clase Planeta, esto se ve en la líneas 17 y 18 respectivamente. Y finalmente se llama al método EscribeInfo para cada uno de los objetos en las líneas 22 y 23. 1 package esferapolimorfismo; 2 class Esfera { 3 int radio; 4 public void GuardaRadio(int x) { radio = x;} 5 6 public void EscribeInfo() { System.out.println("El radio es: "+radio); }; 7 } 8 class Planeta extends Esfera 9 { 10 int lunas; 11 public void GuardaLunas(int x) { lunas =x;} 12 public void EscribeInfo() 13 { System.out.println("El radio es: "+radio+" y "+lunas+" lunas");} 14 } 15 public class EsferaPolimorfismo { 16 public static void main(String[] args) { 17 Esfera E = new Esfera(); 18 Planeta P = new Planeta(); 19 E.GuardaRadio(100); 20 P.GuardaRadio(500); 21 P.GuardaLunas(2); 22 E.EscribeInfo(); 23 P.EscribeInfo(); 24 } 25 } A continuación se muestra el resultado de la ejecución del código anterior. run: El radio es: 100 El radio es: 500 y 2 lunas BUILD SUCCESSFUL (total time: 0 seconds) Ejemplo de sobreescritura de los métodos Vender y EscribeInfo de la clase Producto y de los métodos Vender y EscribeInfo de la clase ProductoaMayoreo. En el siguiente ejemplo se muestra la clase Producto que empieza en la línea 2 y termina en la línea 20. Nótese que en la línea 12 se definió el método Vender y en la línea 16 el método EscribeInfo. Posteriormente se define la clase ProductoaMayoreo derivada de la clase Producto que inicia en la línea 21 y termina en la línea 33, en esta clase se redefinen los métodos Vender y EscribeInfo (líneas 25 y 29), lo cual constituye una sobreescritura de dichos métodos. De la línea 34 a la línea 43 se define la clase ProductoPolimorfismo. En el método main se crean dos objetos llamados Prod1 y Prod2, donde Prod1 es una instancia de la clase Producto y Prod2 es una instancia de la clase ProductoaMayoreo, esto se ve en la líneas 36 y 37 respectivamente. Y finalmente se llama al método Vender para cada uno de los objetos en las líneas 38 y 39; y se llama al método EscribeInfo para cada uno de los objetos en las líneas 40 y 41. 1 package productopolimorfismo; 2 class Producto 3 { 4 public int cantidad; 5 public double precio; 6 public double pago; 7 public Producto() 8 { 9 cantidad = 5; 10 precio = 10.50; 11 } 12 void Vender() 13 { 14 pago = cantidad*precio; 15 } 16 void EscribeInfo() 17 { 18 System.out.println("Su pago total es "+pago); 19 } 20 } 21 class ProductoaMayoreo extends Producto 22 { 23 public String NombreEmpresa; 24 public boolean mayoreo; 25 void Vender() 26 { 27 pago = cantidad*precio - cantidad*precio*0.2; 28 } 29 void EscribeInfo() 30 { 31 System.out.println("Pago total con descuento: "+pago); 32 } 33 } 34 public class ProductoPolimorfismo { 35 public static void main(String[] args) { 36 Producto Prod1 = new Producto(); 37 ProductoaMayoreo Prod2 = new ProductoaMa yoreo(); 38 Prod1.Vender(); 39 Prod2.Vender(); 40 Prod1.EscribeInfo(); 41 Prod2.EscribeInfo(); 42 } 43 } A continuación se muestra el resultado de la ejecución del código anterior. run: Su pago total es 52.5 Pago total con descuento: 42.0 BUILD SUCCESSFUL (total time: 0 seconds) COMENTARIO FINAL. En este capítulo se define el concepto de polimorfismo, así como una cantidad importante de ejemplos que permiten entender el concepto a nivel básico. Sin embargo, este concepto tiene un impacto significativo y un alcance que no es posible mostrar en este documento y que será comprendido a medida que el programador madure al utilizarlo y pueda vislumbrar el alcance de dicho concepto. BIBLIOGRAFÍA Y REFERENCIAS [Llo ] Llobet Azpitarte Rafael, Alonso Jordá Pedro, Devesa Llinares Jaume, Miedes De Elías Emilí, Ruiz Fuertes María Idoia, Torres Goterris Francisco. “Introducción a la Programación Orientada a Objetos con Java”. Departamento de Sistemas Informáticos y Computación Universidad Politécnica de Valencia. [Joy98] Joyanes Aguilar Luis.(1998) “Programación orientada a objetos”, Mc. Graw Hill [Cru13] Cruz Almanza Graciano. “Notas 2013 de Programación II”. Facultad de Ciencias de la Computación, Universidad Autónoma de Puebla. [ . ] RAE Capítulo IV: LENGUAJE DE PROGRAMACIÓN ORIENTADO A OBJETOS, ELEMENTOS AVANZADOS Beatriz Beltrán Martínez Carmen Cerón Garnica Meliza Contreras González Mario Rossainz López Miguel Rodriguez Hernández Capítulo IV: Lenguaje de Programación Orientado a Objetos Elementos Avanzados IV.1. Tratamiento de Excepciones En todo desarrollo de software, existen en algún momento condiciones que puede llevar a contratiempos de programación, ya sea por agentes externos (usuarios) o internos (sistemas operativos, agotamiento de recursos) al sistema; esto a pesar de que el software desarrollado sea eficiente y de calidad. Es por eso que para este tipo de situaciones JAVA ofrece las excepciones. Una excepción es un error o una condición anormal que se ha producido durante la ejecución de un programa. Es decir, es un evento que ocurre durante la ejecución de un programa y que interrumpe el flujo normal de sus instrucciones JAVA, además mediante las excepciones permite al programador intentar recuperarse ya sea continuando con la ejecución o bien detenerla [MAR11]. Identificar Excepciones en JAVA: Las excepciones en general pueden ser de 2 tipos: Errores de Programación: Aquellos errores en donde el programador puede evitarlo porque es un error al codificar el programa. Errores de Datos: Aquellos errores que el programador puede evitar, o simplemente no puede hacerlo, ya que son problemas en la interacción programa-usuario. IV.1.1 Condiciones de error Al escribir programas de manera correcta es una tarea compleja, y es por eso que surge la necesidad de manejar los errores con eficiencia. Este manejo requiere que se aseguré que los programas cuando utilizan objeto o métodos de manera incorrecta puede el programa manipular el error de manera conjunta y no de manera aislada. Además, se tiene que tomar en cuenta que JAVA tiene diferentes tipos de excepciones: excepciones de entrada/salida, las excepciones en tiempo de ejecución y las de su propia creación. Durante el tiempo de compilación se detectan únicamente los errores de sintaxis. Y el manejo de excepciones de JAVA permite el manipular los errores que ocurren en tiempo de ejecución, se pueden mencionar las excepciones aritméticas (división entre cero), excepciones de puntero (acceso a punteros NULL) y excepciones de indexación (acceso por encima o debajo de los límites de un vector) entre otras. Algunas excepciones son fatales y causan el fin de la ejecución del programa, cuando ocurre esto, lo más recomendable es el envío de un mensaje al usuario indicando el problema que se provocó. Pero existen otras excepciones, por ejemplo cuando no se encuentra un archivo, en donde el programa puede dar al usuario la oportunidad de corregir el error. Para el manejo excepciones dentro de JAVA y ayudando a la portabilidad, se ofrecen mecanismos para el manejo de excepciones. En esta línea se trabaja con la detección y manejo de errores, gestión de recursos y especificación de excepciones. La captura (catch) o localización de errores es un problema dentro de la programación, puesto que también hay que pensar, no solo en el error, sino en donde es que puede surgir y como se manejará. En el momento que se tiene un error, ahora lo que se debe tomar en cuenta es que se debe hacer: terminar con la ejecución del programa o ignorar el error esperando que no se tengan consecuencias. Ante un error, la solución que ofrece JAVA, es la de llamar a mecanismos del lenguaje que soportan manejo de errores, aunque en general, para el programador resulta complicado detectar todos los errores. Un programa lanza (throws) una excepción en el momento que se detecta el error y es cuando JAVA busca el código que maneja o captura el problema y responder de una manera adecuada. En caso de que no la encuentra, entonces el error se propaga hasta ser capturada por la rutina que la lanzó, generalizándose para regresar el control al sistema. En este sentido, de la propagación, se tiene que entender que existe una jerarquía de las excepciones, como se muestra a continuación: IV.1.2 Manejo de excepciones en Java El concepto de excepción indica que hay una irregularidad dentro de la ejecución del software, esto implica que dentro de la ejecución existe un problema a nivel de ejecución, que no tiene que confundirse con excepciones de hardware. Una excepción, entonces es lanzada cuando existe un problema en algún segmento del programa, por ejemplo, cuando se quiera acceder a una localidad de un arreglo que no está dentro del rango definido, esto lleva a una violación entre el llamado y el llamador. En el momento que surge dicha excepción, no desaparece aunque el programador decida ignorarla, entonces se debe poder reconocer y manejar, sino se propagará hasta alcanzar el máximo nivel y terminará la aplicación sin ninguna otra opción. Es por esto que el mecanismo de manejo de excepciones de Java, permite: 1. Detección de errores y posibilidad de recuperación. 2. Limpieza de errores no manejados. 3. Evitar la propagación de errores. IV.1.3 Mecanismo de manejo de excepciones El mecanismo proporcionado por Java, lleva a considerar cinco palabras reservadas: try, throw, throws, catch y finally. Considerando que: try es un bloque donde se quiere detectar errores. catch es el manejador que captura las excepciones del bloque try. throw es una expresión para levantar (raise) excepciones. throws indica las excepciones que puede levantar un método. finally es opcional, y se sitúa después de los catch de un try. Los pasos a seguir son: 1. Establecer un conjunto de operaciones para anticipar errores, dentro de un bloque try. 2. Cuando una rutina encuentra un error, lanza la excepción y el lanzamiento (throwing) es el efecto de levantar la excepción. 3. Para propósitos de limpieza o recuperación, anticipando el error y capturando (catch) la excepción El mecanismo, se completa: El bloque finally, si es que se especifica, es ejecutado después del try. El tratar excepciones y detectarlas conlleva a una diferencia, el tratar la excepción realiza una comunicación lanzando una excepción, pero si se deja que se lance la excepción con un error (detectarla) es no capturada (uncaught) y el programa termina por omisión, sin el manejo del error. La estructura de una excepción se muestra a continuación: … try { // código del bloque try } catch (excepción) { // código del bloque catch } … catch (excepción) { // código del bloque catch } finally { // código opcional de finally } … La palabra reservada try sirve para definir el bloque de código donde es posible que exista una excepción, es donde existen llamados a métodos que generan o definieron excepciones. La palabra reservada catch, es donde se define el bloque que maneja la excepción, el parámetro que lleva representa el tipo de excepción que capturará y que podrá manejar, por cada catch que se coloque, se puede manejar una excepción diferente. Y finally, el cual es opcional, si se utiliza se coloca al final del último catch definido y la finalidad es liberar recursos que se utilizaron en el bloque try, además este bloque siempre se ejecuta, exista o no una excepción. Cabe hacer mención que el bloque try puede estar anidado, cada try contendrá sus propios bloques catch. El proceso que se sigue en una estructura que se trata una excepción es: 1. Si una excepción se produce en alguna de las sentencias dentro del bloque try, entonces se da un salto al primer manejador catch cuyo parámetro coincida con el tipo de excepción que fue lanzada. 2. Cuando las sentencias en el manejador catch se ejecutaron, se termina el bloque try y la ejecución prosigue en la sentencia siguiente, nunca se produce un salto hacia atrás, donde ocurrió la excepción. 3. Si no hay manejadores para tratar con una excepción, se aborta el bloque try y la excepción es relanzada. 4. Si utiliza el manejador opción del bloque finally, este se debe escribir al final del último catch y se lance una excepción o no, se ejecuta las sentencias que se encuentren dentro de este bloque. IV.1.4 Nuevas clases de excepciones Las clases de excepciones definidas en Java, pueden ampliarse a las aplicaciones, definiendo excepciones específicas a errores de alguna aplicación en particular, dichas excepciones derivan de la clase Exception, de manera directa o indirecta. Por ejemplo: 1 public class MiExcepcion extends Exception 2 { 3 … 4 } O bien, ya que ArrayIndexOutOfBoundsException hereda de RunTime que a su vez, hereda de Exception. 1 public class ArrayIndexOutOfBoundsException 2 { 3 … 4 } ArregloExcepcion extends En este caso, la excepción que se está definiendo debe ser una clase que contenga como mínimo un constructor con un argumento de cadena en el que se puede dar información sobre la excepción generada, en este caso se puede utilizar super, si se requiere invocar al constructor de la clase base. Así, por ejemplo, la definición de los ejemplos anteriores quedaría: 1 public class MiExcepcion extends Exception 2 { 3 public MiExcepcion (String error) 4 { 5 super (error); 6 System.out.println (“Constructor de la clase MiExcepcion \n”); 7 } 8 } 9 public class ArregloExcepcion extends ArrayIndexOutOfBoundsException 10 { 11 private int c; 12 13 public ArregloExcepcion (String mensaje, int a) 14 { 15 super(mensaje); 16 c = a; 17 } 18 } Estas excepciones, ya definidas pueden ser lanzadas en cualquier aplicación que así lo requieran, por ejemplo: 1 2 3 4 5 6 7 8 9 … private final MAX=10; private int n; private int v[]; n = entrada.nextInt(); if (n > MAX) throw new ArregloExcepcion (“Fuera del rango ”,n); … Incluso en este sentido, se puede generar una jerarquía de excepciones, esto en caso de que las excepciones a definir así lo requieran. IV.1.5 Crear Excepciones y Lanzar en Java El programador no sólo puede utilizar las excepciones que le provee el lenguaje Java, también permite generar y lanzar sus propias excepciones mediante la sentencia trhow que se ajusten al nivel del programa. Una excepción (clase) se guarda con extensión .java : La sintaxis general para crear una (clase) Excepciones es: <...>class <Nombre_Nueva_Excepción>extends Exception { ··· } Sintaxis general para aplicar la excepción en la Clase que utiliza la nueva excepción <...> class <Nombre_Clase> extends <Nombre_Clase_ Nueva_Excepción { ··· } La sintaxis para la declaración en la cabecera del método de las excepciones que se lanzarán: <...> Nombre_Método (Argumentos)throws <Nombre Excepción1>,..., <Nombre Excepciónn> { ··· } Crear el objeto de la nueva excepción en el cuerpo del método constructor Sintaxis general: <Nombre Excepción> <Nombre Variable> = new <Nombre Excepciónn> Lanzamiento de la excepción en el cuerpo del método constructor Sintaxis general: < throw new <Nombre Variable> ; Lanzamiento de la excepción en el cuerpo de un método Sintaxis general: <throw new <Nombre Excepción> (“Mensaje a mostrar”) Ejemplo 1: Generar una nueva Excepción en el Método Constructor Generar una excepción en una clase para limitar el número máximo de instancias que se pueden crear de ella. La clase se llama GenearObjetos tiene un atributo Número (static int) y atributo: Nombre (String). El atributo Numero se decremeta cada vez que se instancia la clase. La clase solo debe permitir tres instancias, por lo que si esta llega al número máximo que es de 3 las siguientes llamadas generarán una excepción llamada Maximo_Instancias con el mensaje “No se puede crear más de tres instancias”. Posteriormente Gestionar la excepción en la solución requerida. La clase Paso1. Creamos la excepción por herencia con el nombre de Maximo_Instancias 1 2 3 5 6 7 public class Maximo_Instancias extends Exception { public Maximo_Instancias() {} super(message); } } Paso2. Creamos la Clase GenearObjetos con sus atributos y lanzaremos la excepción ya definida. 1 public classGenerar Objetos extends Maximo_Instancias 2{ 3 4 static int numero=3; 5 String Nombre; 6 7 //Constructor 8 public GenerarObjetos() throws Maximo_Instancias{ 9 if (numero==0) 10 { 11 throw new Maximo_Instancias(" No se puede crear más de tres instancias"); 12 } 13 else 14 numero--; 15 } 16 // Constructor con un argumento nombre 17 18 public GenerarObjetos(String Nom) throws Maximo_Instancias{ 19 Nombre=Nom; 20 if (numero==0) 22 { 23 throw new Maximo_Instancias (" No se puede crear mas de tres instancias"); 24 } 25 else 26 numero--; 27 28} } Paso 3. Posteriormente se Gestiona la Excepción creada al instanciar la clase. 1 import java.io.*; 2 import java.util.*; 3 public class EjecutaGenerarObjetos 4 { 5 public static void main (String [] args ) 6 { 7 ArrayList a1=new ArrayList(); 8 9 for (int i=0;i<5;i++) 10 { 11 System.out.println("Se crea un nuevo objeto"+ i); 12 try 13 { 14 //Creamos objeto con nombre 15 a1.add(new GenerarObjetos ("Objeto"+ i)); 16 } catch (Exception e) 17 { 18 System.out.println("Usted no puede crear más objetos porque: "+e.getMessage()); 19 } 20 finally 21 { 22 System.out.println("Gracias por usar este programa"); 23 }//fin finally 24 }// fin for 25 }//fin main 26 }//fin clase Paso 4. Visualización de la Ejecución de la Nueva Excepción 4.2 Corrida de la Generación de la Excepción Ejemplo 2: Generar y Lanzar Excepción en un Método de la Clase Estudiaremos una situación en la que el usuario introduce su edad y se compara si es mayor o igual a 18 años, si el valor es menor es decir es un dato no permitido, el programa lanza un excepción, que vamos a llamar ExcepcionEdad. En caso contrario seguirá la ejecución del programa. Creamos la excepción por herencia con el nombre de ExcepcionEdad 1 public class ExcepcionEdad extends Exception 2 { 3 public ExcepcionEdad (String message) 4 { 5 super(message); 6 } 7 } Lanzaremos la excepción ya creada: 1 public class Votar { 2 3 String nombre; 4 String genero; 5 int edad; 6 7 public Votar(String nombre, String genero, int edad){ 8 this.nombre=nombre; 9 this.genero=genero; 10 this.edad=edad; 11 } En el método validadEdad vamos a lanzar la excepción si se produce el error 12 public void validarEdad() throws ExcepcionEdad{ 13 if(edad<18){ 14 throw new ExcepcionEdad("Es menor de edad"); 15 } 16 else{ 17 System.out.println("Usted si puede votar"); 18 } 19 } 20 } // De la clase Posteriormente vamos a visualizar la ejecución de la Excepción creada. 1 public class EjecutaException { 2 3 public static void main(String[] args) { 4 Votar v1= new Votar("Pepe","Hombre", 15); 5 try{ 6 v1.validarEdad(); 7 } 8 catch(Exception e){ 9 System.out.println("Usted no puede votar porque: "+e.getMessage()); 10 } 11 finally{ 12 System.out.println("Gracias por usar este programa"); } 13 } 14 } // De la clase En resumen para utilizar excepciones requiere: 1. Crear la excepción 2. Llamar la excepción para lanzarla en el momento que se produzca el error 3. Gestionar la excepción para controlar el error, con las sentencias: try{} y catch {} Haz Ahora y Programa 1. Crear una excepción para validar números que guardaran en un arreglo y que se encuentre en un rango permitido de 1 a 20 y que solo se han números enteros, cualquier otro número no se permitirá y deberás informar al usuario. Posteriormente realiza el promedio de los datos del arreglo. 2. Crea una excepción para validar que un objeto llamado alumno se crea con los atributos necesarios como son: Matriculo, Nombre y Promedio y en caso contario avisar que atributo falta. 3. Crea una Excepción para validar números pares al momento de la lectura 4. Crea una Excepción para validar números impares al momento de la lectura 5. Crea Una Excepción para validar las operaciones de un cuenta bancaria, como son: 5.1 Retiro donde no se pueden retirar cantidades negativas y tampoco si el saldo es menor a la cantidad retirada. 5.2 Depósito: validar que no inserte cantidades negativas o iguales a cero. IV.2. Interface Gráficas de usuario Hasta este momento tu experiencia en la programación se ha limitado a proyectos donde solo interviene la consola (la típica pantalla negra donde solo puedes manipular texto). Sin embargo en la mayoría de las aplicaciones que utilizas en tu vida diaria no se emplea la consola, sino pantallas donde puedes realizar cualquier actividad que desencadena otros procesos, por ejemplo cuando ingresas a la página de tu correo electrónico aparece una pantalla donde debes ingresar tu nombre de usuario y contraseña, para que de ahí al presionar la opción iniciar sesión te muestra otra pantalla donde puedes enviar, eliminar tus correos, actualizar tus contactos. A las pantallas con las que interactúas se les llama Interfaces Gráficas de Usuario (GUI’s) que son el conjunto de componentes gráficos que posibilitan la interacción entre el usuario y la aplicación [Szna10]. Recuerdas el trabajo que te costo realizar tu primer menú para realizar operaciones básicas de una calculadora, tuviste que realizar un ciclo hasta que el usuario decidiera no realizar más operaciones (con la típica opción que empleaba s(sí) o n(no)), solicitabas los dos números y luego la operación a realizar con un condicional múltiple y si te equivocabas a la hora de digitar la operación (+,-,*,/) te mandaba a la opción por defecto y tenias en el peor de los casos, equivocarte a la hora de indicar en lugar de números, caracteres y así no te quedaba otra opción más que volver a proporcionar los números y la operación. Como te darás cuenta el usuario de tu propia aplicación fuiste tú, padeciste estas desventajas. Por lo que la solución a la incomodidad de ejecutar tu aplicación radicaba en que la interfaz para el usuario no resultaba atractiva y fácil de usar. Si observas la Figura 4.2.1 te darás cuenta que parece una típica calculadora que viene por defecto en el sistema operativo que empleas, tiene la ventaja que el usuario ya no puede equivocarse a la hora de introducir los números solicitados, además las operaciones que antes tenías que introducir para realizar el proceso están representadas de forma más sencilla y lo mejor tiene un apartado con la opción igual, lo que implica que puedes realizar tantas operaciones como desees hasta que decidas dar clic en la conocida x para cerrar la aplicación. Figura 4.2.1 Ejemplo de interfaz gráfica Así esta interfaz tiene los componentes más básicos (ver Figura 4.2.2) sin el botón cerrar no podríamos detener la ejecución de la aplicación, en la caja de texto aparecerán los dígitos que componen a cada uno de los números que se irán digitando conforme se pulse el botón que representa el dígito elegido, si el número es decimal se permite colocar un punto de separación de la parte entera y decimal. Una vez que se tiene el primer número, a continuación se elige uno de los botones de la extrema derecha que representan las operaciones aritméticas básicas, después de pulsar el botón de su preferencia el usuario debe digitar el otro número que se mostrará completo en la caja de texto, para posteriormente pulsar el botón con el símbolo igual cuya acción inmediata será mostrar el cálculo resultante en la caja de texto, y así se pueden realizar operaciones indefinidamente. Por lo tanto para realizar cualquier aplicación que vincule interfaces gráficas no basta sólo con realizar un diseño atractivo de la aplicación, sino también determinar qué funcionalidades realizar cuando suceda una acción específica sobre un componente, a estas acciones se les llama eventos [Joy11], por cada evento hay quee realizar su proceso de manipulación por lo que se definirán los elementos básicos para esto. Un objeto GUI donde el evento se desencadena se conoce como fuente del evento, por tanto la fuente del evento genera eventos [Wu08], por ejemplo en la calculadora cuando se presiona el botón del botón donde aparece el número 1 ocasionará que en la caja de texto(display) se muestre el número, por tanto cuando un evento se genera, el sistema notifica a los objetos receptores los eventos relevantes, en este caso a la caja de texto, por tanto esta cumple con el rol de ser un objeto receptor del evento que ejecutará una respuesta a los eventos generados. Además de estos objetos, debe asociarse a la fuente del evento una interface oyente que debe corresponder con el tipo de evento generado[Wu08], por ejemplo, si presionamos un botón se generará un evento de acción por lo que el sistema buscarán oyentes de acción registrados, si se pulsa el botón cerrar de la ventana se buscarán oyentes de ventana, si no existe el oyente el evento se ignora. Los oyentes son interfaces cuya única misión es atender eventos y poseerán métodos exclusivos sin implementar para el evento generado, esperando que sea el usuario el que le indique que instrucciones seguir en el caso de que el oyente haya detectado el evento. Por tanto para desarrollar una aplicación gráfica en Java se requiere contar con los siguientes elementos: 1. Diseñar la interfaz gráfica con todos los componentes. 2. Por cada componente agregar el oyente correspondiente o uno sólo si se maneja un solo tipo de eveto. 3. Definir las clases internas que realizarán el objetivo de la aplicación e intercalarlas con las fuentes, receptores de evento y los oyentes. IV.2.1 Componentes Anteriormente para implementar una interfaz gráfica en Java se empleaban las clases contenidas en el paquete de AWT[Wu08] que se consideran clase de peso pesado por lo que en los últimos años se ha optado por el paquete de clases de Swing, consideradas clases de peso ligero, la ventaja de Swing sobre AWT es que sus clases estan implementadas completamente en Java mientras que AWT utiliza las directivas gráficas del sistema operativo por lo que dependiendo de éste se visualizará el control de una o de otra forma, en cambio en Swing muestra el mismo tipo de interfaces, se recomienda también que no se mezclen las componentes awt y swing porque visualmente resulta inconsistente. Cabe mencionar que para el manejo de eventos hasta ahora se requiere utilizar las interfaces y métodos provistos por AWT para que la interfaz tenga funcionalidad y no sea estática. Los componentes en Swing se diferencian respecto AWT en que a sus componentes se les coloca el prefijo J por ejemplo un control Button en AWT es el equivalente al control JButton de Swing. Los componentes de las interfaces gráficas en Swing se clasifican de la siguiente forma[Wu08] y se observa un ejemplo en la figura: Component: que proviene de la clase Object, ejemplos de ellos tenemos la clase JButton, JRadioButton, JCheckBox, JLabel, JList, JcomboBox (que contiene una lista y una caja de texto integrada), JprogressBar, JScrollBar entre otros. Container: es subclase de la clase Component y tiene el objetivo de almacenar cuantos componentes y contenedores requiera el usuario, de esta clase provienen las clases hijas JPanel, Window, JFrame, JDialog con las que se realizan la mayoría de las aplicaciones donde regularmente se toma un objeto de la clase Frame que viene de ventana y se le agregan cuantos objetos Panel se requieran para agregar posteriormente componentes en cada Panel como botones, cajas de texto, barra de menús. TextComponent: son todos los componentes que permiten la manipulación de texto, los componentes JTextArea y JTextField, JpasswordField, JpopMenu, heredan las características de esta clase. MenuComponent: son todos los componentes relacionados con la administración de menús como los componentes JMenuBar, JMenuItem, JMenu, JPopupMenu, JCheckBoxMenuItem. Figura 4.2.2 Ejemplo de interfaz gráfica IV.2.2 Tipos de eventos y métodos de oyentes Para cada componente se cuentan con distintos eventos asociados a un oyente en específico, los más significativos se muestra en la siguiente tabla [Joy11]: Tabla 4.1 Eventos con oyentes Evento Componente Acción Oyente Métodos de ActionEvent JButton Hacer clic en el botón ActionListener actionPerformed() JRadioButton Hacer clic en el botón de radio JMenuItem Seleccionar un elemento del menu JTextField Terminar de editar un texto pulsando enter JList Hacer doble clic sobre el element de una lista JList Selecciona o suelta un elemento de una lista ItemListener itemStateChanged() JCheckBox Selecciona o deja de seleccionar un elemento TextEvent JTextComponent Cambia el texto TextListener textValueChanged() KeyEvent JTextField Se activa cuando se presiona una tecla dentro del campo de texto KeyListener keyPressed() keyReleased() keyTyped() Window Pulsa o suelta una tecla Window Pulsa o suelta un botón del ratón, entra o sale de un component por medio del mouse o mueve o arrastra el ratón MouseListener mouseClicked() mouseEntered() mouseExited() mousePressed() mouseReleased() Window Cuando el mouse se mueva a una coordenada específica MouseMotionList ener mouseDragged() mouseMoved() Window Obtiene o pierde el foco FocusListener focusGained() focusLost() JTextField Obtiene o pierde el foco el componente Window Actua sobre una ventana abre o cierra o se reestablece WindowListener windowActivated() windowDeactivated() windowClosed() windowClosing() windowIconified() windowDeiconified() windowOpened() ItemEvent MouseEvent FocusEvent WindowEvent Oyente Ahora teniendo todos los elementos se verá como implementar la interfaz gráfica en Swing siguiendo el esqueleto siguiente: 1 import javax.swing*; 2 import java.awt.*; 3 import java.awt.event.*; 4 public class nombreApp extends JFrame{ 5 public nombreApp(){ 6 // establecer el contenedor 7 // establecer el administrador de diseño 8 /* crear cada componente y añadirlo al contenedor, cabe mencionar que incluso se puede agregar un contenedor que a la vez se le agreguen más componentes o contenedores. */ 9 // asociar al menos un oyente por cada componente o uno para componentes del mismo tipo manejando la funcionalidad a aplicar con el método getSource() del objeto evento involucrado. 10 // establecer tamaño y visibilidad de la ventana 11 } 12 /* implementar cada oyente mediante una clase de la siguiente forma:*/ 13 class Oyente implements nombreListener{ 14 // implementar métodos de nombreListener 15 /* al ser una interface recuerde que si no requiere un método debe colocar su implementación como {} */ 16 } 17 public static void main(String args[]){ 18 nombreApp app=new nombreApp(); 19 app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) 20 } 21 } Respecto al código mostrado las líneas 1-3 son las bibliotecas de objetos que se requieren para los eventos y componentes gráficos, la línea 4 es un ejemplo de cómo definir una interfaz gráfica de tipo JFrame que es el tipo de contenedor más empleado en el modo gráfico. En la línea 5 se define el constructor de la clase generada, se sugiere que sea en el constructor donde se creen los componentes y sus oyentes, así como la configuración del contenedor (JFrame). En la línea 6 se indica que se establezca el contenedor, en el caso de Swing se hace con la instrucción: 6 Container contenedor = getContentPane() En el caso de otros contenedores como JPanel, primero debe ser creado el objeto para agregar los componentes y luego agregarse al contenedor o a la ventana que se requiera. En la línea 7 se indica que se emplee un administrador de diseño(layout) que se utilizan para acomodar bajo una cierta distribución los componentes como se describe a continuación [Szna10]: Absoluto: se utiliza cuando se trabaja con herramientas visuales para el diseño del GUI. Relativo: definen reglas y los componentes se acomodan automáticamente dentro del container, y los más usados son: o FlowLayout: distribuye los componentes uno al lado de otro en la parte superior del container, por defecto provee una alineación centrada, pero también puede alinearlos hacia la izquierda o hacia la derecha. o BorderLayout: divide el espacio del container en 5 regiones: NORTH, SOUTH, EAST, WEST y CENTER, admite un único componente por región. o GridLayout: divide el espacio del container en una rejilla de n filas por m columnas donde todas las celdas son de igual tamaño. Para definir el layout se usa el método setLayout() cuyo parámetro es un objeto anónimo de los siguientes layout, el elegido por tu preferencia, en el caso de los constructores se puede consultar el API de Java para revisar las versiones y sus argumentos: 7 contenedor.setLayout( new FlowLayout() ); 7 contenedor.setLayout(new BorderLayout()); 7 contenedor.setLayout(new GridLayout(3,3)); En la línea 8 se indica que se agreguen los componentes al contenedor, en este caso el método pertenece a la clase Container, en este caso tenemos ejemplos de cómo agregar un JButton, una Jlabel 8 botonResta= new JButton( ”-” ); botonResta.setSize(20,50); contenedor.add( botonResta ); resultado = new JLabel( "el resultado es " ); contenedor.add( resultado ); En la línea 9 se establece la asociación del oyente con el componente veamos un ejemplo: 9 botonResta.addActionListener( new OyenteBoton()); En la línea 10 se establece el tamaño y visibilidad de la ventana con las siguientes instrucciones: 10 setSize( 275, 170 ); setVisible( true ); o alternativamente puede emplearse en lugar de setSize() el método pack() para que la ventana se ajuste al tamaño de los componentes contenidos en ella. De la línea 13 a la 16 se muestra el esqueleto de la implementación de los oyentes, en este caso se muestran ejemplos de un ActionListener y un ItemListener: 13 private class OyenteBoton implements ActionListener { public void actionPerformed( ActionEvent evento ) { if ( evento.getSource() == BotonSuma) /* instrucciones para realizar la suma de dos números a partir de verificar que el componente de tipo JButton llamado BotonSuma haya generado el evento, en caso contrario se buscará el evento que lo desencadeno */ else if ( evento.getSource() == BotonResta) /* instrucciones*/ } } 16 } 13 private class ManejadorLista implements ItemListener { public void itemStateChanged( ItemEvent evento ) { /* se colocan las instrucciones una vez que se detecto que se pulso el elemento de un JList o un JCheckbox o un JRadioButton */ } 16 } De la línea 17 a 20 se declara el método main cuyas únicas instrucciones consisten en crear un nuevo objeto de la clase que hereda de JFrame línea 18, en la línea 19 se habilita el método en la ventana para que al pulsar el botón cerrar se termine la ejecución si se omite la instrucción la ejecución no terminará. La línea 21 termina la definición de la clase. Ejercicios Propuestos 1. Diseñar una interfaz gráfica que simule un chat de manera que el usuario interactúa con un objeto robot, cada vez que el usuario ingresa información el robot le contesta con una frase aleatoria respecto al tema que pregunta. Si el usuario decide terminar el chat, el robot antes de que termine la ejecución de la aplicación mostrará un cuadro de diálogo donde le diga “adios”+ el nombre del usuario. 2. Diseñar una interfaz gráfica que simule una lista de canciones en reproducción, cuando el usuario da clic en un elemento de la lista mostrará la información respecto a la canción, si el usuario da doble clic sobre la lista la canción desaparecerá de la interfaz. 3. Diseñar una interfaz gráfica que simule una tiendita de dulces(crear la clase dulce, se necesitara un arreglo de dulces), el usuario podrá dar clic en la imagen y vera la descripción y cantidad de dulces disponibles, si decide comprar un dulce en el almacén se debe reportar un decremento de la disponibilidad del mismo, si el dulce se acaba entonces debe aparecer en una etiqueta el mensaje “comprar dulce” + nombre del dulce, al final si el usuario se acaba los dulces en la etiqueta deben aparecer todos los dulces a comprar. 4. Diseñar una interfaz gráfica que simule una consulta del clima de manera que se tenga almacenado en una estructura tipo lista o en un flujo, información aleatoria de cómo se comportará el clima en el periodo de un mes, cada vez que el usuario ejecute la aplicación debe mostrarse en un textArea las condiciones del día actual, mostrar en una etiqueta por medio de una imagen si el día estará soleado, nublado, lluvioso entre otros. Considerar que el usuario podrá consultar también la condición del clima en días posteriores o anteriores. 5. Diseñar una interfaz gráfica que simule una hamburguesería considerando componentes de tipo menu para seleccionar los paquetes y las opciones de compra, considerar checkBox para la selección de uno o varios ingredientes y radioButton para la selección del método de pago: efectivo, tarjeta de débito o de crédito. Considerar que por cada paquete o producto debe visualizarse la imagen del mismo. IV.3 Flujos de Entrada /Salida Uno de los paquetes más importantes de JAVA es el paquete io que contiene el sistema de E/S básico del lenguaje incluyendo la E/S con archivos. Los programas Java realizan la entrada/salida a través de flujos (streams). Un flujo es una abstracción que produce o consume información y esta relacionado con un dispositivo físico a través del sistema de entrada y salida de Java. Todos los flujos se comportan de la misma manera, incluso aunque estén relacionados con distintos dispositivos físicos. Por esta razón se pueden aplicar las mismas clases y métodos de E/S a cualquier tipo de dispositivo. Un flujo de entrada puede abstraer distintos tipos de entrada, ya sea desde un disco hasta un teclado o una conexión de red. De la misma forma un destino de salida puede ser una consola, un archivo de disco o una conexión de red. IV.3.1. Clases de Flujos Java implementa los flujos dentro de una jerarquía de clases definida en el paquete io.java. En la parte superior de la jerarquía hay dos clases abstractas InputStream y OutputStream. Java tiene algunas subclases concretas de cada una de ellas para gestionar las diferencias que existen entre los distintos dispositivos, como archivos de disco, buferes de memoria, etc. Las clases abstractas InputStream y OutputStream definen algunos métodos que las otras clases implementan como son el método read() y write() que respectivamente leen y escriben bytes de datos. IV.3.2. Flujos Predefinidos Todos los programas Java importan el paquete java.lang. Este paquete define una clase llamada System que encapsula aspectos del entorno de ejecución. Además contiene tres variables con flujos predefinidos llamadas in, out y err. Estos campos estan declarados como public y static en System por lo cual se pueden utilizar en cualquier parte del programa sin tener una referencia a un objeto System específico. System.out se refiere al flujo de salida estándar (por default es la consola). System.in hace referencia a la entrada estándar (que por defecto es el teclado). System.err se refiere al flujo de error estándar (que por defecto también es la consola). IV.3.3 Entrada por consola En Java la entrada por consola se efectúa leyendo bytes de un flujo de entrada. Estos bytes se pueden leer en varios objetos como pueden ser caracteres y cadenas. Read(): El método de entrada de más bajo nivel es read() y tiene distintas formas. El formato más común que se utiliza es: int read() throws IOException Cada vez que se llama a read() lee un único byte del flujo de entrada y lo devuelve como un valor entero. El método devuelve –1 cuando encuentra el final del flujo de entrada. Puede generar una excepcíon del tipo IOException. El siguiente programa muestra el funcionamiento de read() donde lee caracteres de la entrada estándar hasta que el usuario pulsa la letra “q”. // Ejemplo del método read() import java.io.*; class UseRead { public static void main(String args[]) throws IOException { char c; System.out.println(“Introduzca caracteres; ‘q’ para salir...”); //lee caracteres do { c= (char) System.in.read(); System.out.println(c); }while (c!=’q’); } } Un ejemplo de ejecución es el siguiente: Introduzca caracteres, ‘q’ para salir... 123abcq 1 2 3 a b c q System.in es por defecto un flujo con buffer. Esto significa que realmente no se pasa ninguna entrada al programa hasta que se pulsa <enter>. Ahora bien, los flujos de entrada y salida estándares pueden ser redirigidos. El siguiente programa muestra cualquier archivo de texto en pantalla, suponiendo que la entrada estandar ha sido redirigida al archivo deseado cuando comienza el programa. Esto hace que System.in utilice dicho archivo como entrada. /* Muestra un archivo de texto. Para utilizar este programa es necesario redirigir la entrada al archivo que se quiere mostrar. Por ejemplo, para mostrar un archivo llamada prueba.txt se utiliza la siguiente orden: java ShowFile < prueba.txt */ import java.io.*; class ShowFile { public static void main(String args[]) throws IOException { int i; // lee caacteres hasta llegar al final del archivo //(EOF) do { i= System.in.read(); System.out.print((char)i); }while (i!=-1); } } IV.3.3.1 Lectura de una cadena Java proporciona un método llamado readLine() que automáticamente lee una secuencia de caracteres de un flujo de entrada y devuelve un objeto del tipo String. Este método forma parte de la clase DataInputStream que proporciona métodos para leer todos los tipos simples de Java, además de lineas de texto. La forma general de readLine es la siguiente: Final String readLine() throws IOException Este método devuelve un objeto String. Antes de utilizar el método readLine() es necesario obtener un objeto DataInputStream asociado al flujo de entrada. Para hacer esto se utiliza el constructor: DataInputStream( InputStream flujo_de_entrada) Donde flujo_de_entrada es el flujo que se asocia a la instancia de DataInputStream que se va a crear. Para leer la entrada por consola se puede utilizar como parámetro System.in. El siguiente programa lee e imprime líneas de texto hasta que se introduzca la palabra “fin”. // Ejemplo del método readLine() import java.io; class ReadLines { public static void main(String args[]) throws IOException { String str; System.out.println(“INTRODUZCA LINEAS DE TEXTO”); System.out.println(“Introduzca fin para finalizar...”); do { str= inData.readLine(); System.out.println(str); }while(!str.equals(“fin”)); } } El siguiente ejemplo crea un pequeño editor de texto. Crea una matriz de objetos String y después lee líneas de texto almacenándolas en la matriz. El programa finaliza cuando haya leído 100 líneas o cuando lea la palabra “fin”. // Un pequeño editor import java.io.*; class TinyEdit { public static void main(String args[]) throws IOException { String str[]= new String[100]; System.out.println(“Introduzca lineas de texto”); System.out.println(“Introduzca ‘fin’ para finalizar”); for (int i=0; i<100; i++) { str[i]= inData.readLine(); if (str[i].equals(“fin”)) break; } System.out.println(“Este fue el texto que introduciste...”); for (int i=0; i<100; i++) { if (str[i].equals(“fin”)) break; System.out.println(str[i]); } } } IV.3.4 Salida por consola La salida por consola se realiza con la ayuda de los métodos print() y println() que ya se han utilizado anteriormente. Estos métodos están definidos en la clase PrintStream, que es el tipo del objeto referenciado por System.out. Además, PrintStream es un flujo de salida derivado de OutputStream y también implementa el método de bajo nivel write(). La forma más simple de usar éste método definida en PrintStream es: void write(int valor_byte) Este método escribe el valor especificado por valor_byte. El siguiente programa utiliza el método write() para imprimir el carácter “A” seguido de un carácter de línea nueva “\n”. // Ejemplo de System.out.write() class WriteDemo { public static void main(String args[]) { int b; b=’A’; System.out.write(b); System.out.write(“\n”); } } Normalmente no se suele utilizar el método write() para presentar información en la consola. IV.3.5 Flujos de archivo Las clases FileInputStream y FileOutputStream crean flujos asociados a archivos. Para abrir un archivo, es necesario crear un objeto de una de estas clases, especificando el nombre del archivo como argumento del constructor. FileInputStream(String archivo) throws FileNotFoundException FileOutputStream(String archivo) throws IOException Aquí, archivo especifica el nombre del archivo que se desea abrir. Cuando se crea un flujo de entrada, si el archivo no existe se genera una excepción FileNotFoundException. Para los flujos de salida, si no se puede crear el archivo se genera una excepción IOException. Cuando se abre un archivo de salida se destruye cualquier archivo existente que tuviese el mismo nombre. Cuando se ha terminado de trabajar con un archivo, es necesario cerrarlo utilizando el método close(). Este método esta definido tanto en FileInputStream como en FileOutputStream. void close() throws IOException Para leer un archivo se puede utilizar el método read() con la siguiente sintaxis definida en FileInputStream int read() throws IOException Cada vez que se llama a este método lee un único byte del archivo y lo devuelve como un valor entero. Cuando encuentra el final del archivo devuelve –1. El siguiente programa utiliza el método read() para leer y mostrar el contenido de un archivo de texto cuyo nombre se pasa como argumento en línea de comandos. /* Muestra un archivo de texto. Para utilizar este programa, especifique el nombre del archivo que desea ver, por ejemplo, para ver un archivo llamado PRUEBA.TXT se utiliza la siguiente orden: java ShowFile PRUEBA.TXT */ import java.io.*; class ShowFile { public static void main(String args[]) throws IOException { int i; FileInputStream fin; try { fin= new FileInputStream(args[0]); }catch(FileNotFoundException e) { System.out.println(“Archivo no encontrado...”); return; }catch(ArrayIndexOutOfBoundsException e) { System.out.println(“Formato: ShowFile archivo”); return; } // lee caracteres hasta llegar al // final del archivo (EOF) do { i= fin.read(); if (i != -1) System.out.println((char)i); }while (i != -1); fin.close(); } } Para escribir en un archivo se puede utilizar el método write() definido en FileOutputStream void write(int valorByte) throws IOException El siguiente programa utiliza el método write() para copiar un archivo de texto. /* Copia un archivo de texto... Para utilizar este programa, especifique el nombre de los archivos origen y destino. Por ejemplo, para copiar un archivo llamado ORIGEN.TXT, en un archivo llamado DESTINO.TXT, se utiliza la siguiente orden: java CopyFile ORIGEN.TXT DESTINO.TXT */ import java.io.*; class CopyFile { public static void main(String args[]) throws IOException { int i; FileInputStream fin; FileOutputStream fout; try{ fin= new FileInputStream(args[0]); fout= new FileOutputStream(args[1]); }catch(FileNotFoundException e) { System.out.println(“Archivo no encontrado”); return; }catch(IOException e){ System.out.println(“Error al abrir el archivo de salida”); return; }catch(ArrayIndexOutOfBoundsException e){ System.out.println(“Formato: CopyFile Origen Destino”); } // Copia el archivo try { do { i= fin.read(); if (i != -1) fout.write(i); }while(i != -1); }catch(IOException e) { System.out.println(“Error...”); } fin.close(); fout.close(); } } IV.3.6 File La clase File trabaja directamente con los archivos y no sobre flujos. Es decir, la clase File no especifica cómo se recupera o almacena la información la información en los archivos; sólo describe las propiedades de un objeto archivo. Un objeto File se utiliza para obtener o modificar la información asociada con un archivo de disco, como los permisos, hora, fecha y directorio y para navegar por la jerarquía de subdirectorios. Como File no trabaja con flujos, no es una subclase de InputStream o OutputStream. Un directorio en Java se trata igual que un archivo con una propiedad adicional: una lista de nombres de archivo que se pueden examinar utilizando el método list(). Los objetos File se pueden crear utilizando uno de los siguientes tres constructores disponibles: File(String directorio) File(String directorio, String nom_arch) File(File obj_Dir, String nom_arch) El parámetro directorio es el nombre del directorio, nom_arch es el nombre del archivo y obj_Dir es un objeto File que especifica un directorio. El siguiente ejemplo crea tres archivos: f1, f2, f3. El primer objeto File se construye utilizando un directorio como único argumento. El segundo se crea utilizando dos argumentos: el directorio y el nombre del archivo. El tercero se crea utilizando el directorio asignado a f1 y un nombre de archivo; f3 hace referencia al mismo archivo que f2. File f1= new File(“/”); File f2= new File(“/”,”autoexec.bat”); File f3= new File(f1,”autoexec.bat”); La clase File define muchos métodos para determinar las propiedades de un objeto File: El método getName() devuelve el nombre de un archivo. El método getParent() devuelve el nombre del directorio padre. El método exist() devuelve true si el archivo existe y false en caso contrario. Sin embargo hay muchos métodos dentro de File con los que podemos examinar las propiedades del objeto, pero no se proporcionan las funciones correspondientes para cambiar sus atributos. El siguiente ejemplo muestra alguno de los métodos de File. // Ejemplo de la clase File import java.io.File; class FileDemo { static void p(String s) { System.out.println(s); } public static void main(String args[]) { File f1= new File(“/java/COPYRIGTH”); p(“Archivo: “+f1.getName()); p(“Directorio: “+f1.getPath()); p(“Directorio Absoluto: “ +f1.getAbsolutePath()); p(“Padre: “+f1.getParent()); p(f1.exists()? “existe” : “no existe”); p(f1.canWrite() ? “se puede escribir” : “no se puede escribir”); p(f1.canRead() ? “se puede leer” : “no se puede leer”); p((f1.isDirectory() ? “ ” : “no”)+ ”es un directorio”); p(f1.isFile() ? “es un archivo normal” : “podría ser un enlace con nombre”); p( “última modificacion: ” + f1.lastModified()); p(“tamaño del archivo: “+ f1.length() + “Bytes”); } } La ejecución de este programa sería algo parecido a: Archivo: COPYRIGHT Directorio: /java/COPYRIGHT Directorio Absoluto: /java/COPYRIGHT Padre: /java Existe Se puede escribir Se puede leer No es un directorio Es un archivo normal Ultima modificación: 812465204000 Tamaño del archivo: 695 Bytes La clase File también incluye métodos específicos cuyo uso esta restringido al trabajo con archivos convencionales, esto es, no pueden ser llamados sobre directorios. Algunos de estos son: renameTo(): boolean renameTo(File nuevo_nombre) donde el nombre del archivo especificado por nuevo_nombre es el nuevo nombre del archivo. Devolverá true si se realiza con éxito y false en caso contrario. delete(): boolean delete() Cuyo propósito es eliminar el archivo de disco representado por el trayecto del objeto File. Este método solo funciona sobre objetos que son archivos simples. Devuelve true si elimina el archivo y false en caso contrario. IV.3.7 Directorios Un directorio es un File que contiene una lista de otros archivos y directorios. Cuando se crea un objeto File y es un directorio, el método isDirectory() devolverá true. En este caso, se puede llamar al método list() sobre ese objeto para extraer la lista de los otros archivos y directorios que contiene. Una de las formas de uso del método list() es: String[ ] list() La lista de archivos se devuelve en una matriz de objetos del tipo String. El siguiente programa muestra como examinar el contenido de un directorio utilizando el método list(). Si se llama al método list() sobre un objeto File que no sea un directorio se provoca una excepción del tipo: NullPointerException (excepción de apuntador nulo) en tiempo de ejecución. // Uso de directorios import java.io.File; class DirList { public static void main(String args[]) { String dirname= “/java”; File f1=new File(dirname); if (f1.isDirectory()) { System.out.println(“Directorio de “+dirname); String s[]=f1.list(); for (int i=0; i< s.length; i++) { File f= new File(dirname+”/”+s[i]); if (f.isDirectory()) { System.out.println(s[i]+ “ es un directorio”); } else { System.out.println(s[i]+ “es un archivo”); } } } else { System.out.println(dirname+ “ no es un directorio”); } } } IV.3.7.1 FilenameFilter A menudo se desea limitar el número de archivos devuelto por el método list() para que se incluyan únicamente aquellos archivos cuyo nombre cumpla con cierto patrón o filtro. Para hacer esto se utiliza la segunda forma de uso del método list(): String list(FilenameFilter objFF) En esta forma, el parámetro objFF es un objeto de una clase que implementa la interfaz FilenameFilter. Esta interfaz define un único método accept() al que se llama una vez por cada archivo de la lista. Su forma general es: Boolean accept(File directorio, String nom_archivo) Este método devuelve true para los archivos del directorio que deberían ser incluidos en la lista. Es decir, aquellos que coincidan con el argumento nom_archivo y false para aquellos que deberían ser excluidos. La siguiente clase mostrada a continuación como ejemplo, implementa la interfaz FilenameFilter. Esta clase restringe la visibilidad de los nombres de archivo devueltos por el método list(). La restricción se aplica a los archivos cuyos nombres terminan con la extensión que se pasa como parámetro en su constructor. import java.io.*; public class OnlyExt implements FilenameFilter { String ext; public OnlyExt(String ext) { this.ext=”.”+ext; } public boolean accept(File dir, String name) { return name.endsWith(ext);} } A continuación se muestra el programa que presenta el directorio, de manera que liste sólo los archivos que tienen extensión .html. // directorio de archivos .html import java.io.*; class DirListOnly { public static void main(String args[]) { String dirname=”/java”; File f1= new File(dirname); FilenameFilter only= new OnlyExt(“html”); String s[]= f1.list(only); for (int i=0; i<s.length; i++) { System.out.println(s[i]);} } } File tiene otros métodos específicos para trabajar con directories. El método mkdir() crea un directorio, devolviendo true si tiene éxito la creación y false en caso contrario. Si se quiere crear un directorio cuando el path no existe, hay que utilizar el método mkdirs() el cual no sólo creará un directorio sino que además creará todos los padres de éste. Se pueden crear directorios utilizando un objeto File, pero sin embargo no se pueden eliminar. 4.4.8. Las clases Stream Anteriormente se dio una introducción a la E/S de Java basada en flujos la cual se desarrolla a partir de dos clases abstractas: InputStream y OutputStream. Estas clases definen una funcionalidad básica común a todas las clases de flujo. InputStream: Es una clase abstracta que define el modelo de Java para el flujo de entrada. Todos sus métodos lanzarán una excepción IOException si se producen errores. Los métodos de esta clase son: METODO DESCRIPCION int read() Devuelve un entero como representación del siguiente byte disponible en la entrada int read(byte bufer[]) Intenta leer hasta bufer.length bytes situándolos en bufer y devuelve el número real de bytes leidos con éxito Intenta leer hasta len bytes situándolos int read(byte bufer[], int off,en bufer comenzando en bufer[off] y int len) devuelve el número de bytes leidos con éxito int skip(long num) Omite num bytes de la entrada y devuelve el número de bytes que se han omitido. int available() Devuelve el número de bytes disponibles actualmente para su lectura void close() Cierra el origen de entrada void mark(int num) Coloca una marca en el punto actual del flujo de entrada que seguirá siendo válida hasta que se lean num bytes. void reset() Devuelve el apuntador de entrada a la marca establecida nuevamente. boolean markSupported() Devuelve true si se admiten los métodos mark() o reset() en este flujo. Tabla 4.1. Métodos de la clase InputStream OutputStream Es una clase abstracta que define el flujo de salida. Todos los métodos de esta clase devuelven un valor void y lanzan una IOException en caso de error.La siguiente tabla muestra los métodos definidos por esta clase: METODO DESCRIPCION void write(int b) Escribe un único byte en un flujo de salida. void write(byte bufer[]) Escribe una matriz completa de bytes en un flujo de salida. void write(byte bufer[], int off, int len) Escribe len bytes de la matriz bufer, comenzando a partir de bufer[off]. void flush() Inicializa el estado de la salida de manera que se limpian todos los buferes. void close() Cierra el flujo de salida. Tabla 4.2. Métodos de la clase OutputStream FileInputStream: Esta clase crea un InputStream que se puede utilizar para leer el contenido de un archivo. Sus constructores más comunes son: FileInputStream(String directorio) FileInputStream(File objArch) Estos constructores lanzan una FileNotFoundException en caso de error. El parámetro directorio es el nombre completo del directorio de un archivo y objArch es un objeto File que describe el archivo. Por ejemplo: FileInputStream f0; f0=new FileInputStream(“/autoexec.bat”); File f= new File(“/autoexec.bat”); FileInputStream f1= new FileInputStream(f); FileInputStream sobrescribe seis de los métodos de la clase abstracta InputStream. Los métodos mark() y reset() no sesobrescriben y cualquier intento de utilizar estos métodos en un FileInputStream generará una IOException. El siguiente ejemplo muestra cómo leer un único byte, una matriz de bytes y una parte de una matriz de bytes. También muestra como saber cuántos bytes quedan utilizando available() y como omitir los bytes no deseados utilizando el método skip(). El programa lee su propio archivo origen que debe estar en el directorio actual. // Ejemplo de FileInputStream import java.io.*; class FileInputStreamDemo { public static void main(String args[]) throws Exception { int size; InputStream f; f=new FileInputStream(“FileInputStreamDemo.java”), System.out.println(“Bytes totales disponibles: “ +(size=f.available())); int n=size/40; System.out.println(“Los primeros “+n+” bytes del archivo se leen en una operación read()”); for (int i=0; i<n; i++) { System.out.print((char)f.read());} System.out.println(“\n Total disponible todavía: “+ f.available()); System.out.println(“leyendo los siguientes: “+n+ “bytes con una llamada a read(b[])”); byte b[]= new byte[n]; if (f.read(b) != n) { System.err.println(“No se puede leer “+n+ ” bytes”); } System.out.println(new String(b,0,0,n)); System.out.println(“\n total disponible: “+ (size=f.available())); System.out.println(“Omite la mitad de los bytes restantes Con skip()”); f.skip(size/2); System.out.println(“\n total disponible: “+ f.available()); System.out.println(“leyendo “+n/2+” bytes del final de la matriz”); if (f.read(b,n/2,n/2) != n/2) { System.err.println(“No se pueden leer “+n/2+ ” bytes”); } System.out.println(new String(b,0,0,b.length)); System.out.println(\n total disponible: “ +f.available()); f.close();}} FileOutputStream Crea un OutputStream que se puede utilizar para escribir en un archivo. Sus constructores más comunes son: FileOutputStream(String directorio) FileOutputStream(File objArch) Los dos constructores pueden lanzar una IOException o una SecurityException en caso de error. El parámetro directorio es el nombre completo del directorio de un archivo y objArch es un objeto File que describe el archivo. Si se intenta abrir un archivo de sólo lectura como un FileOutputStream se lanzará una IOException. El siguiente ejemplo crea un bufer de bytes creando en primer lugar una cadena y después usa el método getBytes() para obtener la matriz de bytes equivalente. Después se crean tres archivos. file1.txt que contiene los bytes pares, file2.txt que contiene el conjunto completo de bytes, y file3.txt que contiene la última cuarta parte del buffer. //ejemplo de FileOutputStream import java.io.*; class FileOutputStreamDemo { public static void main(String args[]) throws Exception { String source=”Ahora es el momento de que los hombres buenos\n”+” vengan a ayudar a su país\n”+”y paguen sus impuestos”; byte buf[]= new byte[source.length()]; source.getBytes(0,buf.length,buf,0); OutputStream f0; f0= new FileOutputStream(“file.txt”); for (int i=0; i<buf.length;i+=2) {f0.write(buf[i]);} f0.close(); OutputStream f1; f1= new FileOutputStream(“file2.txt”); f1.write(buf); f1.close(); OutputStream f2; f2= new FileOutputStream(“file3.txt”); f2.write(buf,buf.length-buf.length/4, buf.length/4); f2.close(); } } IV.3.9. La clase RandomAccessFile Esta clase encapsula un archivo de acceso aleatorio y no es derivada de InputStream ni de OutputStream, sino que implementa las interfaces DataInput y DataOutput que definen los métodos de entrada y salida básicos. También permite peticiones de posicionamiento, es decir, se puede situar el puntero dentro del archivo. Esta clase tiene dos constructores: RandomAccessFile(File objArch, String acceso) RandomAccessFile(String nomArch, String acceso) En el primer constructor, objArch especifica el nombre del archivo que se desea abrir como objeto File. En el segundo constructor, el nombre del archivo se pasa en nomArch. En ambos casos, acceso determina el tipo de acceso al archivo que se permite. Si es “r”, entonces sólo se puede leer el archivo y no se puede escribir en él. Si es “rw”, entonces el archivo se abre en modo lectura-escritura. El método seek() se utiliza para establecer la posición actual del puntero dentro del archivo. void seek(long posicion) Aquí, posición especifica la nueva posición en bytes, del puntero desde el inicio del archivo. Después de llamar a seek(), la siguiente operación de lectura o escritura se realizará en la nueva posición dentro del archivo. RandomAccessFile implementa los métodos de entrada y salida estándar que se pueden utilizar para leer y escribir en archivos de acceso aleatorio. IV.3.10. Seriación de objetos La seriación en Java se utiliza para escribir y leer objetos a y desde un archivo. Es decir, es la operación mediante la cual se envía una serie de objetos a un archivo para hacerlos persistentes. El proceso contrario de recuperar el estado de la serie de objetos del archivo para reconstruirlos en memoria recibe el nombre de “deseriación”. El paquete java.io proporciona las clases ObjectOutputStream y ObjectInputStream para realizar operaciones de seriación y deseriación de forma automática. Para hacer la seriación de los objetos de una clase se debe implementar la interfaz Serializable, la cual es una interfaz vacía que no tiene métodos y que tiene como propósito identificar aquellas clases cuyos objetos pueden ser seriados. Por ejemplo, el siguiente código muestra la definición de una clase cuyos objetos pueden ser seriados. import java.io.*; public class Persona implements Serializable { // Cuerpo de la clase… } IV.3.10.1. Escritura de objetos en un archivo Utilizaremos un objeto de tipo ObjectOutputStream para enviar un flujo de datos de tipos primitivos y objetos a un flujo OutputStream, concretamente a un objeto FileOutputStream puesto que lo que se quiere es almacenar objetos en un archivo. Posteriormente esos objetos podrán ser reconstruidos a través de un objeto de tipo ObjectInputStream. Para escribir un objeto dentro de un objeto ObjectOutputStream, utilizaremos el método writeObject de dicha clase. Éste método lanzará una excepción de tipo NotSerializableException si se intenta escribir un objeto de una clase que no implementa la interfaz Serializable. El siguiente código construye un ObjetoOutputStream sobre un FileOutputStream y lo utiliza para almacenar una cadena y un objetoPersona en un archivo llamado datos. FileOutputStream fos=new FileOutputStream(“datos”); ObjectOutputStream oos=new ObjectOutputStream(fos); oos.writeUTD(“Archivo datos”); oos.write(new Persona(nombre, dirección, teléfono)); oos.close(); Como ejemplo, se muestra una clase llamada Directorio que permita almacenar objetos de tipo Persona en un archivo de nombre “directorio.dat”. Programa Persona.java import java.io.*; public class Persona implements Serializable { String nombre; int edad; long telefono; public Persona(String nombre, int edad, long telefono) { this.nombre=nombre; this.edad=edad; this.telefono=telefono; } public String toString() { return "Nombre: "+nombre+" Edad: "+edad+ " anios Telefono: "+telefono; } } Programa Directorio.java import java.io.*; import java.util.*; public class Directorio { public static void creaArchDir(String nomarch) throws IOException { Scanner inDatos=new Scanner(System.in); String resp=""; String nombre; int edad; long tel; FileOutputStream out; out=new FileOutputStream(nomarch); ObjectOutputStream f; f=new ObjectOutputStream(out); do{ System.out.print("\nNombre: "); nombre=inDatos.next(); System.out.print("\nEdad: "); edad=inDatos.nextInt(); System.out.print("\nTelefono: "); tel=inDatos.nextLong(); f.writeObject(new Persona(nombre, edad,tel)); System.out.print("\nContinuar (S/N: "); resp=inDatos.next(); }while (resp.equals("S")); } public static void main(String args[]) { try{ creaArchDir("directorio.dat"); }catch(IOException e){ ; } } } IV.3.10.2. Lectura de objetos desde un archivo Un objeto de la clase ObjectInputStream permite recuperar datos de tipos primitivos y objetos desde un objeto InputStream. De forma concreta, cuando se trate de datos de tipos primitivos y objetos almacenados en un archivo, utilizaremos un objeto FileInputStream. Para leer un objeto desde un flujo ObjectInputStream se deberá utilizar el método readObject. Si se almacenaron objetos y datos de tipos primitivos, éstos deberán ser recuperados en el mismo orden. El siguiente código crea la aplicación MuestraDir que lee los objetos del archivo y muestra su información en pantalla. Programa MuestraDir.java import java.io.*; public class MuestraDir { public static void muestraArchDir(String nomarch)throws IOException { Persona p; String resp=""; String nombre; int edad; long tel; FileInputStream in; in=new FileInputStream(nomarch); ObjectInputStream f; f=new ObjectInputStream(in); try{ do{ p=(Persona)f.readObject(); System.out.println(p); }while(true); }catch(EOFException e){ ; }catch(ClassNotFoundException e){;} } public static void main(String args[]) { try{ muestraArchDir("directorio.dat"); }catch(IOException e){;} } } IV.3.11. Ejercicios Propuestos 1. Escriba un programa denominado CopiarArchivo que copie el contenido de un archivo en otro. El programa deberá ser invocado de la manera siguiente: java CopiarArchivo <archivo_fuente> <archivo_destino> en el <archivo_destino> se almacenará la copia del contenido del <archivo_fuente> pero con la variante de que ésta será escrita en mayúsculas. Los archivos deberán ser de texto. 2. Hacer un programa que escriba una secuencia de números primos en un archivo de acceso directo o binario. El programa deberá preguntar al usuario cuantos números primos desea generar, a continuación los generará y los ira almacenando en un archivo binario. Para poder visualizar el contenido de ése archivo deberá elaborar un programa aparte que lea el archivo binario generado y lo muestre en pantalla. 3. Implementar un programa llamado grep que permita buscar palabras en uno o más archivos de texto. Como resultado se visualizará en pantalla, por cada uno de los archivos, su nombre, el número de línea y el contenido de la misma para cada una de las líneas del archivo que contenga la palabra buscada. El programa podrá pedir los nombres de los archivos a procesar, así como la palabra a buscar en ellos o bien se podrán dar en línea de comandos cuando se ejecute el programa. 4. Escribe un frame que le pida al usuario dos números enteros e imprima la suma, resta, multiplicación y división de los mismos. El frame deberá contar con opciones para que el usuario guarde los resultados de estas operaciones en un archivo ya sea de texto o binario. 5. Escriba un programa que muestre en pantalla e imprima en un archivo de texto la cantidad de caracteres, palabras y líneas del archivo de texto que se le pase para su análisis. Incluya un método que verifique que los archivos de lectura y de escritura son diferentes. Las entradas de los nombres de los archivos serán dados por el usuario utilizando los cuadros de diálogo de la clase JOptionPane. BIBLIOGRAFÍA Y REFERENCIAS [Amm98] Ammeraal L. (1998). Computer Graphics for JAVA Programmers. England. John Wiley & Son. [Bra97] Brassard G., Bratley P. (1999) Fundamentos de Algoritmia. Madrid. Prentice Hall. [Ceb11] Ceballos F.J. (2011). Java 2, Curso de Programación. Madrid. Alfaomega – RaMa. [Fla99] Flanagan D. (1999). JAVA in a NutShell. UK., O´Reilly – Mc Graw Hill. [Hub04] Hubbard J. R. (2004). Programming with Java. Theory and Problems. Schaum´s Outline Series. USA. McGrawHill. [Joy96] Joyanes A. L. (1996). Programación Orientada a Objetos. Madrid. Mc Graw Hill. MAR11] Martínez, L. J. (2011). Programación en Java 6. México: McGrawHill [Liw98] Liwu L. (1998). JAVA. Data Structures and Programming. Germany. SpringerVerlag Berlin Heidelberg NewYork. [Rob99] Roberts S., Heller P., Ernest M. (1999) Complete JAVATM 2. Certification Study Guide. USA. Sybex Inc. [Sch97] Schildt H., Naughton P. (1997). JAVA, Manual de Referencia. Madrid. Osborne Mc Graw Hill. [Joy11] Joyanes Aguilar L., Zahonero Martínez I. (2011). Programación en Java Algoritmos, programación orientada a objetos e interfaz gráfica de usuario. México. Mc Graw Hill. [Szna10] Sznajdleder P. (2010). Java a fondo Estudio del Lenguaje y desarrollo de aplicaciones. México. Alfaomega. [Wu08] Wu T. C. (2008). Programación en Java. Introducción a la programación orientada a objetos. México. Mc Graw Hill. Capítulo V: Tópicos Avanzados de la Programación Mireya Tovar Vidal Pedro Bello López Capítulo V: Tópicos Avanzados de la Programación V.1. Recursividad Los programas que hemos visto hasta ahora están estructurados generalmente como métodos que se llaman entre sí, de una manera disciplinada y jerárquica. Sin embargo, para algunos problemas es conveniente hacer que un método se llame a sí mismo. Dicho método se conoce como método recursivo; este método se puede llamar en forma directa o indirecta a través de otro método. La recursividad es un tema importante, que puede tratarse de manera extensa en los cursos de ciencias computacionales de nivel superior [Dei08]. Se dice que un método es recursivo cuando es posible que se llame a sí mismo o a otro método que lo llame a él. Esta técnica es especialmente útil cuando una tarea se tiene que repetir muchas veces siguiendo el mismo proceso, tal como recorrer una lista de datos, o recurrir a la técnica de “divide y vencerás” en la que al tratar, por ejemplo, de ordenar un conjunto de números, se llama a ordenar una mitad y luego otra mitad, cada una de estas mitades llamaría a ordenar las mitades de las mitades y así sucesivamente hasta que quedara uno o dos números. Al llamar un método a sí mismo, la ejecución de este primero se pospone hasta terminar de ejecutar el método llamado, y así sucesivamente, con lo que se produce una cadena de ejecuciones pendientes que completan la ejecución empezando por el último en ser llamado y terminando por el primero que se ha llamado [Mol11]. Un método recursivo f() se invoca a sí mismo de forma directa o indirecta; en recursión directa el código del método f() contiene una secuencia que lo evoca, mientras que en recursión indirecta el método f() invoca a g() el cual a su vez a p() y así sucesivamente hasta que se llama de nuevo al método f() [Joy11] . Un método recursivo consta básicamente de dos partes. Caso base: también llamado caso trivial o elemental, representa el fin de la recursión y es necesario para evitar que se cicle el método. Caso recursivo o general: es un llamado recursivo a si mismo generalmente modificando su parámetro de entrada para resolver una parte más pequeña del problema. Veamos un ejemplo simple, cuya solución es un llamado recursivo así mismo. Ejemplo1. Calcular el factorial de un número natural, donde // Método para calcular el factorial de un numero 1 public int Factorial(n) 2 { 3 if(n==0 ││ n==1) return 1; // Caso base 4 else return n*Factorial(n-1);//Caso recursivo 5 } En el Ejemplo 1 el caso base (fin de la recursión) está dado por 0! y 1!, que en ambos casos el factorial es uno y el caso general (caso recursivo) viene dado por el llamado al mismo método recursivo con n-1 como argumento, de esta forma se va disminuyendo el argumento hasta llegar a uno o cero. Supongamos que queremos obtener el factorial de 4, entonces: Factorial(4) = 4*Factorial(3), el Factorial(3) = 3*Factorial(2), el Factorial(2) = 2*Factorial(1), y en este momento termina la recursión debido a que el Factorial(1) ya no realiza otro llamado recursivo sino que retorna el valor de 1, este valor es devuelto al nivel anterior y es multiplicado por el valor de 2 y así sucesivamente hasta llegar a Factorial(4) = 4*6=24 . V.1.1. Tipos de recursividad La recursividad puede ser de diferentes tipos de acuerdo al número o forma de llamados del caso recursivo, como se muestra a continuación. V.1.1.1 Recursividad simple Es aquella donde en la definición del método solo aparece una llamada recursiva. El ejemplo 1 es un caso de recursividad simple debido a que dentro del cuerpo del método solo existe un llamado al mismo método, en la figura 5.1 se muestra la prueba de ejecución para calcular el factorial de 5, las líneas con dirección hacia abajo indican los llamados recursivos y las líneas hacia arriba indican los valores que son devueltos en cada llamada al terminar el proceso recursivo y llegar al caso base. Es importante notar que la recursión avanza haciendo llamados recursivos hasta llegar a la condición del caso base y posteriormente retorna los valores subiendo nivel por nivel hasta llegar al primer llamado del método, sin embargo existen ocasiones que no hay un “return”, pero aun así el proceso de recursión se realiza del mimo modo, veamos el ejemplo 2 de recursividad simple. Ejemplo 2. Método que escribe una secuencia de n números 1 public void escribe(int n) 2 { 3 if(n==0) System.out.print(n+" "); // caso base 4 else { 5 System.out.print(n+" "); 6 escribe(n-1); // caso recursivo 7 } 8 } En el código del ejemplo 2 al ejecutarse con un llamado de escribe(5), se escriben los números 5,4,3,2,1,0 debido a que antes de llamar nuevamente a la recursión mandamos a escribir el valor de n. Pero si cambiamos el letrero del bloque del else después del llamado recursivo, entonces la secuencia que se escribe es: 0,1,2,3,4,5, esto debido a que la recursión termina cuando n = 0 y en ese momento escribe un 0 y retorna al llamado anterior cuyo valor es 1 y demás hasta llegar al valor original que es 5. Fig. 5.1. Prueba de ejecución del factorial de 5 V.1.1.2. Recursividad múltiple Este tipo de recursividad se da cuando en el bloque del método recursivo se realizan más de un llamado al mismo método, el ejemplo 3 se muestra este tipo de recursividad. Ejemplo 3. Calcular el valor n de la sucesión de Fibonacci 0, 1, 1, 2, 3, 5, 8, 13, 21, .., definida de forma recursiva como: // 1 2 3 4 5 7 código del ejemplo 3 public int Fibonacci(int n) { if (n==0) return 0; // caso base else if(n==1)return 1; // caso base else return Fibonacci(n-1)+Fibonacci(n-2); // caso recursivo } En el código del ejemplo 3 se puede observar que en el cuerpo del método Fibonacci se llama dos veces de forma recursiva al método. En la figura 5.2 se muestra la prueba de ejecución para Fibonacci(4), donde se define como la suma de los dos números anteriores. En este caso al llegar al final de la recursión y subir al nivel anterior se realiza la suma de los valores retornados. Fig. 5.2. Prueba de ejecución del Fibonacci de 4 V.1.1.3. Recursividad anidada Este tipo de recursividad seda cuándo en algunos de los argumentos de la llamada recursiva hay una nueva llamada al mismo método recursivo. Ver ejemplo 4. Ejemplo 4. La función de Ackerman es uno de los algoritmos recursivos que crece muy rápido, y se define de la siguiente manera: // 1 2 3 4 5 6 código del ejemplo 4. Ackerman public int Ack(int n, int m) { if(n==0) return m+1; else if(m==0) return Ack(n-1,1); else return Ack(n-1,Ack(n,m-1)); } Los números generados por la función de Ackerman crecen muy rápido debido a que en es una recursión con múltiples llamados recursivos y además dentro de los argumentos del método también existe un llamado recursivo. Ver figura 5.3. Fig. 5.3. Prueba de ejecución del Ackerman En la siguiente ejecución se muestra lo complejo del proceso de Ackerman al llamar al método de Ackerman con los valores 2,2. //Ejecución de Ackerman con n=2 y m=2 Ack(2,2)= Ack(1,Ack(2,1)) = Ack(1,Ack(1,Ack(2,0))) = Ack(1,Ack(1,Ack(1,1))) = Ack(1,Ack(1,Ack(0,Ack(1,0)))) = Ack(1,Ack(1,Ack(0,Ack(0,1)))) = Ack(1,Ack(1,Ack(0,2))) = Ack(1,Ack(1,3)) = Ack(1,Ack(0,Ack(1,2))) = Ack(1,Ack(0,Ack(0,Ack(1,1)))) = Ack(1,Ack(0,Ack(0,Ack(0,Ack(1,0))))) = Ack(1,Ack(0,Ack(0,Ack(0,Ack(0,1))))) = Ack(1,Ack(0,Ack(0,Ack(0,2)))) = Ack(1,Ack(0,Ack(0,3))) = Ack(1,Ack(0,4)) = Ack(1,5) = Ack(0,Ack(1,4)) = Ack(0,Ack(0,Ack(1,3))) = Ack(0,Ack(0,Ack(0,Ack(1,2)))) = Ack(0,Ack(0,Ack(0,Ack(0,Ack(1,1))))) = Ack(0,Ack(0,Ack(0,Ack(0,Ack(0,Ack(1,0)))))) = Ack(0,Ack(0,Ack(0,Ack(0,Ack(0,Ack(0,1)))))) = Ack(0,Ack(0,Ack(0,Ack(0,Ack(0,2))))) = Ack(0,Ack(0,Ack(0,Ack(0,3)))) = Ack(0,Ack(0,Ack(0,4))) = Ack(0,Ack(0,5)) = Ack(0,6) = 7 V.1.1.4. Recursividad cruzada o indirecta En este tipo de recursividad un método provoca un llamado a si mismo de forma indirecta, a través de otros métodos, en el ejemplo 5 se muestra el método par() que llama al método impar() y este a su vez realiza una llamada al método par(), con el fin de determinar si un número es par o impar. Ejemplo 5. Elaborar un programa para determinar si un número es par o impar. // código del ejemplo 5. Método par o impar 1 public int Par(int nump) 2 { 3 if(nump==0) return 1; 4 else return impar(nump-1); 6 } 7 8 public int Impar(int numi) 2 { 3 if(numi==0) return 1; 4 else return par(numi-1); 6 } A continuación en la figura 5.4 se muestra la ejecución de la recursión indirecta con los métodos par e impar, en el inciso a) se llama al método Par(6) con el argumento cuyo valor es 6 y se después del fin de la recursión se retorna un 1, lo que significa que el argumento es par, en el inciso b) se llama al método Impar(6) y se retorna un 0 lo que significa que el 6 no es Impar, en el inciso c) se llama al método Par(5) y se retorna un 0 lo que significa que el 5 no es par y en el inciso d) se llama al método Impar(5) y se retorna un 1 lo que significa que el 5 si es impar. Fig.5.4. Recursión indirecta V.1.2. Ventajas y desventajas Cualquier problema que puede resolverse en forma recursiva, también puede resolverse en forma iterativa (no recursiva). Generalmente se prefiere una metodología recursiva a una iterativa cuando la primera refleja el problema con más naturalidad, y se produce un programa más fácil de comprender y depurar [Har04]. La principal ventaja de la recursividad frente a los algoritmos iterativos es que se da lugar a algoritmos simples y compactos. Otra ventaja es que una solución iterativa podría no ser aparente para el problema en cuestión [Ang09]. La principal desventaja es que la recursividad resulta más lenta y consume más recursos al ejecutarse, por lo que, no se recomienda en los casos en los que se requiera un buen rendimiento. La razón por la que los métodos recursivos consumen más recursos que los iterativos es por la forma de resolverse las llamadas recursivas. Al ejecutarse una llamada de un método recursivo se almacena en la pila: Los argumentos del método Las variables locales del método La dirección de retorno, es decir, el punto del programa que debe ejecutarse una vez que termine la llamada actual. Ejercicios propuestos 1. 2. 3. 4. 5. Construir un método recursivo que determine si un número n es primo o no. Escribir un método recursivo que determine si una palabra es palíndroma o no. Construir un método recursivo que calcule el MCD de Euclides. Escriba un método recursivo llamado potencia(x,y) que calcule la potencia y de x. Construir un método que muestre la representación binaria de cualquier número natural. 6. Escribir un método recursivo que, dados dos vectores de números naturales del mismo tamaño, calcule el resultado de sumar las componentes pares y restar las componentes impares. Por ejemplo, considera los vectores V1=(5,2,4,3) y V2=(0,2,1,7) el resultado del cálculo sería: -(5+0)+(2+2)-(4+1)+(3+7)= -5 +4-5+10= 4 7. Escribir un método recursivo que devuelva el total de números primos entre dos números naturales a y b. 8. Escribir un método recursivo que, dados dos vectores de números naturales del mismo tamaño, calcule el producto escalar de los dos vectores. 9. Escribir un método recursivo que calcule la norma de un vector. 10. Escribir un método recursivo que determine si los elementos de un vector V=(v1, v2, .., vn) forman una sucesión estrictamente creciente. El método devuelve “cierto” (1) si los elementos del vector satisfacen la relación v1 < v2 < … < vn y “falso” (0) de lo contrario. BIBLIOGRAFÍA Y REFERENCIAS [Dei08] Deitel P, J. D. (2008). Java como programar, Septima edición. México: Pearson, Educación. [Mol11] F.Javier, M. T. (2011). Java 7 Manual Imprescindible. Madrid: Anaya Multimedia. [Har04] Educación. Harvey M. Deitel, P. J. (2004). Cómo programar en Java. Pearson [Joy11] Joyanes Aguilar Luis, Z. M. (2011). programación en JAVA 6 Algoritmos, programación orientada a objetos e interfaz gráfica de usuario. México: Mc Graw Hill. [Jes09] Riera, J. B. (2009). Manual de Algorítmica: Recursividad, complejidad y diseño de algoritmos. Editorial UOC. [Ang09] Libros. Yera, A. C. (2009). Programar desde un punto de vista científico. Visión