Agradecimientos Gracias… A Dios por la vida misma, por haberme dado la capacidad de pensar y porque me ha permitido llegar sano a este momento. A mis padres por haberme dado la vida, porque gran parte de lo que soy se lo debo a ellos y porque todas sus enseñanzas han sido la base para llegar hasta aquí. A todos mis hermanos porque muchos de sus actos me han servido como ejemplo para terminar este proceso, por su apoyo incondicional y porque siempre han creído en mi. A mi asesora por su sabiduría, asesoría y paciencia durante todo el proceso. A mis revisores por su disposición, porque su valioso aporte ayudo a realizar un trabajo mejor y mas completo. A mis compañeros y amigos que han creído en mi. Contenido tematico INDICE CONTENIDO DE FIGURAS ................................................................................................. 5 INTRODUCCION ................................................................................................................ 6 UNIDAD I. ARREGLOS UNIDIMENSIONALES Y MULTIDIMENSIONALES ......................... 7 1.1 Arreglos Unidimensionales (Listas) .................................................................................... 8 1.1.1 Conceptos básicos..................................................................................................... 8 1.1.2 Operaciones............................................................................................................ 11 1.1.3 Aplicaciones............................................................................................................ 15 1.2 Arreglos Bidimensionales ................................................................................................ 17 1.2.1 Conceptos básicos.................................................................................................. 17 1.2.2 Operaciones............................................................................................................ 19 1.2.3 Aplicaciones............................................................................................................ 26 1.3 Arreglos multidimensionales ........................................................................................... 29 1.3.1 Conceptos básicos................................................................................................... 29 1.3.2 Aplicaciones............................................................................................................ 30 1.4 Ejercicios resueltos......................................................................................................... 32 1.5 Ejercicios propuestos...................................................................................................... 34 UNIDAD II. MÉTODOS Y MENSAJES ............................................................................... 36 2.1 Atributos Const y Static .................................................................................................. 37 2.2 Concepto de método ...................................................................................................... 37 2.3 Declaración de Métodos ................................................................................................. 38 2.4 Llamadas a Métodos ...................................................................................................... 39 2.5 Tipos de Métodos........................................................................................................... 40 2.5.1 Métodos Estáticos o de Clase ................................................................................... 40 2.5.2 Métodos Normales .................................................................................................. 43 2.6 Referencia this .............................................................................................................. 44 2.7 Forma de Pasar Argumentos........................................................................................... 45 2.8 Devolver un valor desde un método ................................................................................ 48 2.9 Estructura del Código ..................................................................................................... 51 2.10 Ejercicios Resueltos:..................................................................................................... 51 2.11 Ejercicios propuestos:................................................................................................... 54 UNIDAD III. CONSTRUCTOR, DESTRUCTOR (RECOLECTOR DE BASURA)...................... 56 3.1 Concepto de método constructor .................................................................................... 57 3.2 Recolector de basura (garbage collector)......................................................................... 57 3.3 Declaración de métodos constructores ............................................................................ 59 3.4 Aplicaciones de constructores y destructores (Recolectores de basura) ............................. 60 3.5 Tipos de Métodos Constructores y Destructores (Recolectores de Basura) ......................... 63 3.6 Ejercicios propuestos...................................................................................................... 69 UNIDAD IV. SOBRECARGA .............................................................................................. 70 4.1 Conversión de Tipos....................................................................................................... 71 4.2 Sobrecarga de Métodos .................................................................................................. 76 4.3 Sobrecarga de Operadores ............................................................................................ 83 4.4. Ejercicios resueltos........................................................................................................ 84 4.5 Ejercicios Propuestos...................................................................................................... 87 UNIDAD V. HERENCIA..................................................................................................... 88 5.1 Introducción a la Herencia.............................................................................................. 89 5.2 Herencia Simple............................................................................................................. 90 5.3 Herencia Múltiple ........................................................................................................... 94 5.4 Clase Base y Clase Derivada .......................................................................................... 94 5.4.1 Definición de la Clase Base ..................................................................................... 94 5.4.2 Definición de Clase Derivada.................................................................................... 95 Programación orientada a objetos en Java Contenido tematico 5.5 Parte Protegida.............................................................................................................. 96 5.5.1 Propósito de la Parte Protegida ................................................................................ 96 5.6 Redefinición de los miembros de las clases derivadas....................................................... 97 5.7 Clases Virtuales y Visibilidad ........................................................................................... 98 5.8 Constructores en Clases Derivadas.................................................................................. 98 5.9 Aplicaciones................................................................................................................... 99 5.10 Ejercicios propuestos...................................................................................................110 UNIDAD VI. POLIMORFISMO Y REUTILIZACION ......................................................... 112 6.1 Concepto de polimorfismo .............................................................................................113 6.2 Clases abstractas ..........................................................................................................116 6.2.1 Definición ..............................................................................................................116 6.2.2 Redefinición...........................................................................................................118 6.3 Definición de una Interfaz .............................................................................................120 6.4 Implementación de la Definición de una Interfaz.............................................................121 6.5 Reutilización de la definición de una Interfaz ..................................................................123 6.6 Definición y Creación de Paquetes..................................................................................129 6.7 Reutilización de las clases de un Paquete / Librería .........................................................132 6.8 Clases Genéricas (Plantillas) .........................................................................................134 6.9 Ejercicios Resueltos.......................................................................................................134 6.10 Ejercicios Propuestos...................................................................................................139 UNIDAD VII. EXCEPCIONES.......................................................................................... 140 7.1 Definición .....................................................................................................................141 7.1.1 ¿Qué son las Excepciones? .....................................................................................141 7.1.2 Clases de Excepciones Predefinidas por el Lenguaje .................................................142 7.1.3 Propagación de Excepciones ...................................................................................146 7.2 Gestión de Excepciones .................................................................................................147 7.2.1 Manejo de Excepciones ..........................................................................................148 7.2.1 Lanzamiento de Excepciones ..................................................................................152 7.3 Excepciones Definidas por el Usuario .............................................................................155 7.3.1 Clase Base de las Excepciones ................................................................................155 7.3.2. Creación de una Clase Derivada del Tipo Excepción.................................................157 7.3.3 Manejo de una Excepción Definida por el Usuario.....................................................159 7.4 Ejercicios resueltos........................................................................................................160 7.5 Ejercicios propuestos: ...................................................................................................163 UNIDAD 8.FLUJOS Y ARCHIVOS ................................................................................... 164 8.1 Definición de archivos de texto y Archivos Binarios .........................................................165 8.2 Operaciones Básicas en Archivos de Texto y Binario........................................................166 8.2.1 Crear.....................................................................................................................166 8.2.2 Abrir......................................................................................................................166 8.2.3 Cerrar....................................................................................................................167 8.2.4 Lectura y Escritura .................................................................................................167 8.2.5 Recorrer ................................................................................................................177 8.3 Aplicaciones..................................................................................................................178 8.4 Ejercicios propuestos.....................................................................................................183 CONCLUSIONES ............................................................................................................ 184 ANEXO I. INTRODUCCIÓN AL LENGUAJE JAVA ............................................................ 185 1.1 Introducción .................................................................................................................186 1.2 Características de Java ..................................................................................................186 1.2.1 Simple ...................................................................................................................186 1.2.2 Orientado a Objetos ...............................................................................................187 1.2.3 Distribuido .............................................................................................................187 1.2.4 Robusto.................................................................................................................187 1.2.5 Seguro ..................................................................................................................187 Programación orientada a objetos en Java 3 Contenido tematico 1.2.6 Portable.................................................................................................................187 1.2.7 Arquitectura Neutral ...............................................................................................187 1.2.8 Rendimiento medio ................................................................................................188 1.2.9 Multithread ............................................................................................................188 1.3 Java frente a los demás lenguajes..................................................................................188 1.4 Como Compilar y Ejecutar Programas en Java ................................................................190 1.4.1 Javac.....................................................................................................................190 1.4.2 Java ......................................................................................................................190 1.4.3 Appletviewer..........................................................................................................190 1.5 Estructuras de Datos Básicas en Java .............................................................................191 1.5.1 Tipos de Datos.......................................................................................................191 1.5.2 Enteros..................................................................................................................191 1.5.3 Tipos de Datos Enteros en Java ..............................................................................192 1.5.4 Tipos de Datos Reales en Java................................................................................192 1.5.5 Caracteres .............................................................................................................192 1.5.6 Boolean .................................................................................................................193 1.5.7 Variables ...............................................................................................................193 1.6 Entornos de Desarrollo de Java......................................................................................194 1.6.1 Bluej .....................................................................................................................194 1.6.2 Jcreator .................................................................................................................194 1.6.3 Jbuilder .................................................................................................................195 1.6.4 Netbeans ...............................................................................................................195 1.6.5 Eclipse...................................................................................................................195 ANEXO II. EL ENTORNO DE DESARROLLO ECLIPSE ..................................................... 197 2.1. ¿Que es Eclipse? ..........................................................................................................197 2.2. Trabajando con Eclipse ................................................................................................197 2.2.1. Creación de un proyecto........................................................................................197 2.2.2. Creando clases......................................................................................................198 2.2.3. Ejecutando el programa ........................................................................................199 2.2.4. Depuración de programas......................................................................................199 2.2.5. Otras herramientas interesantes. ...........................................................................200 BIBLIOGRAFIA .............................................................................................................. 207 Programación orientada a objetos en Java 4 Indice de figuras CONTENIDO DE FIGURAS Figura 1: Arreglo de 12 elementos ............................................................................................ 11 Figura 2: Ordenamiento de los elementos de un arreglo usando el método de la burbuja ............ 12 Figura 3: Proceso completo del ordenamiento del arreglo .......................................................... 13 Figura 4: Ilustración de los arreglos bidimensionales.................................................................. 17 Figura 5: Ilustración de arreglos bidimensionales (2). ................................................................ 18 Figura 6: Un arreglo de tres dimensiones (4 x 5 x 3).................................................................. 29 Figura 7: Las 36 posibles sumas de los dados............................................................................ 35 Figura 8: El recolector de basura de Java funciona determinando los objetos que son alcanzables.58 Figura 9: Clase bicicleta ........................................................................................................... 90 Figura 10: Ilustración del polimorfismo. ...................................................................................113 Figura 11: Polimorfismo y reutilización .....................................................................................114 Figura 12 Ilustración de la herencia .........................................................................................115 Figura 13 Jerarquía de las clases de excepciones......................................................................142 Figura 14: Propagación de excepciones....................................................................................146 Figura 15 Manejo de Excepciones. ...........................................................................................148 Figura 16: Clase base de la clase Throwable. ...........................................................................156 Figura 17: Primera pantalla de Eclipse .....................................................................................200 Figura 18: Selección del tipo de proyecto en Eclipse .................................................................201 Figura 19: Datos sobre el proyecto en Eclipse ..........................................................................201 Figura 20: Creación de una clase en Eclipse .............................................................................202 Figura 21: Creación de un fichero vació en Eclipse....................................................................202 Figura 22: La perspectiva Java con las vistas mostrando una clase en Eclipse ............................203 Figura 23: Configuración para ejecutar la aplicación: proyecto y clase con el main en Eclipse......203 Figura 24: Argumentos para enviar al programa y argumentos para enviar a la Maquina Virtual en Eclipse ...................................................................................................................................204 Figura 25: Consola con el resultado de la ejecución en Eclipse. .................................................204 Figura 26: Barra de Herramientas de Eclipse. ...........................................................................205 Figura 27: La perspectiva debug en Eclipse. .............................................................................205 Figura 28: La perspectiva debug en acción en Elipse.................................................................206 Figura 29: Asistente de código (pulsando Ctrl + espacio) en Eclipse. .........................................206 Programación orientada a objetos en Java Introducción INTRODUCCION Actualmente una de las áreas más candentes en la industria y en el ámbito académico es la orientación a objetos. La orientación a objetos promete mejoras de amplio alcance en la forma de diseño, desarrollo y mantenimiento del software ofreciendo una solución a largo plazo a los problemas y preocupaciones que han existido desde el comienzo en el desarrollo de software: la falta de portabilidad del código y reusabilidad, código que es difícil de modificar, ciclos de desarrollo largos y técnicas de codificación no intuitivas. Un lenguaje orientado a objetos ataca estos problemas. Tiene tres características básicas: debe estar basado en objetos, basado en clases y capaz de tener herencia de clases. Muchos lenguajes cumplen uno o dos de estos puntos; muchos menos cumplen los tres. La barrera más difícil de sortear es usualmente la herencia. En un principio eran muy pocos los lenguajes de programación que atacaban los tres problemas anteriores. Como una solución a eso, nace Java. Java es un lenguaje de programación con el que se puede realizar cualquier tipo de programa. En la actualidad es un lenguaje muy extendido y cada vez cobra más importancia tanto en el ámbito de Internet como en la informática en general. Está desarrollado por la compañía Sun Microsystems con gran dedicación y siempre enfocado a cubrir las necesidades tecnológicas más punteras. Una de las principales características por las que Java se ha hecho muy famoso es que es un lenguaje independiente de la plataforma. Eso quiere decir que si hacemos un programa en Java podrá funcionar en cualquier ordenador del mercado. Es una ventaja significativa para los desarrolladores de software. Actualmente Java se utiliza en un amplio abanico de posibilidades y casi cualquier cosa que se puede hacer en cualquier lenguaje se puede hacer también en Java y muchas veces con grandes ventajas. El presente material es un libro de texto que sirve como herramienta importante de apoyo para la materia de Programación orientada a objetos (POO) basada en el lenguaje de programación java. Dicho material se compone de ocho unidades, más dos anexos; una de introducción al lenguaje de programación java y la otra de Eclipse uno de los entornos de desarrollo mas usados en dicho lenguaje. Además cada una de las unidades contiene un apartado de ejercicios resueltos y ejercicios propuestos para que el lector pueda practicar los conocimientos adquiridos. Cada uno de los temas de las unidades se desarrolló en base a dos o más autores para que el lector pueda comparar y elegir el enfoque que considere más apropiado de acuerdo a su opinión personal. Programación orientada a objetos en Java 6 Unidad I. Arreglos unidimensionales y multidimensionales UNIDAD I. ARREGLOS UNIDIMENSIONALES Y MULTIDIMENSIONALES Conceptos básicos, operaciones y aplicaciones con arreglos Programación orientada a objetos en Java 7 Unidad I. Arreglos unidimensionales y multidimensionales Unidad 1. Arreglo Unidimensional Y Multidimensional 1.1 Arreglos Unidimensionales (Listas1) 1.1.1 Conceptos básicos Definición de un arreglo unidimensional (vectores) En java, un arreglo unidimensional es un grupo de variables (llamadas elementos o componentes) que contienen valores del mismo tipo. Los arreglos son objetos, por lo que se consideran como tipos de referencia. Los elementos de un arreglo en java pueden ser tipos primitivos2 o de referencias (incluyendo arreglos). Para hacer referencia a un elemento específico en un arreglo se debe especificar el nombre de la referencia al arreglo y el número de la posición del elemento en el arreglo. El número de posición del elemento se conoce formalmente como el índice o subíndice del elemento en el arreglo. Todos los elementos de un arreglo deben ser del mismo tipo. Declaración y creación de arreglos Unidimensionales Para crear un arreglo, antes hay que crear una variable de arreglo del tipo deseado. La forma general de un arreglo unidimensional es: Tipo nombre-de-variable[ ]; Los objetos arreglo ocupan espacio en memoria. Todos los objetos en java deben de crearse con la palabra clave new 3. El programador especifica el tipo de cada elemento y el número de elementos que se requieren para el arreglo. La siguiente declaración crea 12 elementos para el arreglo de enteros de nombre c: int c[] = new int[12]; Esta tarea también puede realizarse en dos pasos, como se muestra a continuación: int c[]; //declara la variable arreglo c = new int [12]; //crea el arreglo Al crear un arreglo cada uno de sus elementos recibe un valor predeterminado de cero para los elementos numéricos de tipos primitivos, falso para los elementos boléanos y nulo para las referencias (cualquier tipo no primitivo). Vectores Tipo primitivo son las variables propias del lenguaje, p. e. char, int, boolean, y float. 3 El operador new se utiliza en Java para la creación de objetos. 1 2 Programación orientada a objetos en Java 8 Unidad I. Arreglos unidimensionales y multidimensionales En la tabla 1.1.1, se muestra los diferentes valores de inicialización para cada uno de los tipos de variable: Tipo de elemento byte Valor inicial 0 int 0 float 0.0f char ‘\u0000’ referencia a objeto nulo short 0 long 0L double 0.0d boolean falso Tabla 1.1 Diferentes valores de inicialización para cada tipo de variable Si se quiere inicializar un arreglo de valores diferente a los que se muestran en la tabla anterior, se puede combinar, la declaración, construcción e inicialización en un sólo paso. La siguiente línea de código inicializa un arreglo de 5 flotantes: float[] diametros = {1.1f, 2.2f, 3.3f, 4.4f, 5.5f}; Se pueden crear varios arreglos en una sola declaración. La siguiente declaración de un arreglo String reserva 100 elementos para b y 27 para x: String b[] = new String [100], x[] = new String [27]; Al declararse un arreglo, su tipo y los corchetes pueden combinarse al principio de las declaraciones para indicar que todos los identificadores en la declaración son referencias a arreglos. Por ejemplo: double[] arreglo1, arreglo2; En la línea anterior se declara el arreglo1 y arreglo2 como referencias a arreglos de valores double. Como se vio anteriormente, la declaración y creación del arreglo pueden combinarse en la declaración. La siguiente declaración reserva 10 elementos para el arreglo1 y 20 para el arreglo2 de tipo double: double[] arreglo1 = new double[10], arreglo2 = new double[20]; Programación orientada a objetos en Java 9 Unidad I. Arreglos unidimensionales y multidimensionales Un programa puede declarar arreglos de cualquier tipo. Todo elemento de un arreglo de tipo primitivo es una variable del tipo declarado del arreglo. Por ejemplo, cada uno de los elementos de un arreglo int es una variable int. En un arreglo que sea de tipo de referencia, cada elemento del arreglo es una referencia a un objeto del tipo declarado de ese arreglo. Cada uno de los elementos de un arreglo String es una referencia a un objeto String. En la figura 1 se muestra una representación lógica de un arreglo de enteros llamado c, este arreglo contiene 12 elementos (es decir variables). Un programa puede hacer referencia a cualquiera de estos elementos mediante una expresión de acceso a un arreglo que incluye el nombre del arreglo, seguido por el índice del elemento especifico encerrado entre corchetes ([]). El primer elemento en cualquier arreglo tiene el índice cero (lo que se denomina como elemento cero) 4. Por lo tanto, el primer elemento del arreglo c es c[0], el segundo elemento es c[1], el séptimo elemento del arreglo es c[6] y, en general, el i-esimo elemento del arreglo c es[c-i]. Los nombres de los arreglos siguen las mismas convenciones, que los demás nombres de las variables. Un índice debe ser un entero positivo o una expresión entera que pueda promoverse a un int. Si un programa utiliza una expresión como índice, el programa evalúa la expresión para determinar el índice. Por ejemplo supóngase que la variable a es 5 y que b es 6, entonces la instrucción: C [ a + b ] += 2; suma 2 al elemento c[11] del arreglo. Obsérvese que el nombre del arreglo con subíndice es una expresión de acceso al arreglo. Dichas expresiones pueden utilizarse en el lado izquierdo de una asignación, para colocar un nuevo valor en un elemento del arreglo. 4 En Java todos los Arreglos siempre empiezan con en el subíndice 0. Programación orientada a objetos en Java 10 Unidad I. Arreglos unidimensionales y multidimensionales Figura 1: Arreglo de 12 elementos 1.1.2 Operaciones Ejemplo: Imprimir Elementos del Arreglo Para imprimir, se recorre el arreglo con un ciclo for desde la posición 0 hasta a.length5 - 1. Además, se imprime la palabra "Arreglo" y luego se subraya. Por lo tanto con el ciclo, se imprime cada elemento almacenado en a[i]. Código 1.2.1. Imprime los valores de un arreglo de 10 elementos. Ordenar Elementos del Arreglo 5 Esta propiedad contiene el tamaño del arreglo. Programación orientada a objetos en Java 11 Unidad I. Arreglos unidimensionales y multidimensionales Para ordenar el arreglo, se usará el algoritmo de la burbuja 6 que consiste en ir comparando el elemento i con i + 1 del arreglo, para ordenar en forma ascendente. Si se encuentra que a[i] es mayor que a[i+1], se intercambian dichos elementos. Código 1.1.2.2. Ordena los elementos de un arreglo y después los imprime ya ordenados Por ejemplo, al ejecutar el ciclo for(i = 0 ...) se encuentra que hay que cambiar de posición los elementos "Peru" y "Bolivia". Para realizar este cambio, se lleva a cabo las tres instrucciones que muestra en la figura 2. Figura 2: Ordenamiento de los elementos de un arreglo usando el método de la burbuja Ahora bien, además se tiene que aplicar el ciclo for, tantas veces como elementos tenga el arreglo, ya que en el peor caso los elementos podrían estar ordenados en forma descendente. Es para eso que se implementa el ciclo for con el índice k. En la figura 3, se muestra el estado del arreglo, y los valores de los índices i y k. 6 También conocido como bubblesort Programación orientada a objetos en Java 12 Unidad I. Arreglos unidimensionales y multidimensionales Figura 3: Proceso completo del ordenamiento del arreglo Comparación de Strings7 Para comparar Strings (lexicográficamente) no se puede usar el operador de igualdad 8 ==. Para comparar dos strings se ocupa el método int compareTo(String str) de la clase String de la siguiente manera : Funcionamiento de la función compare Sean a y b objetos de tipo String. La instrucción: a.compareTo(b) 1) retorna un valor menor que cero si a es menor que b 2) retorna un valor mayor que cero si a es mayor que b 3) retorna 0 (cero) si b es igual que a En el siguiente ejemplo, se recorre el arreglo, comparando el String pasado como parámetro con cada a[i]. Inicializa una variable p en -1, asumiendo que el String no se va 7 8 Cadenas El operador = = sólo se utiliza para la comparación de números Programación orientada a objetos en Java 13 Unidad I. Arreglos unidimensionales y multidimensionales a encontrar. Si durante el ciclo se encuentra, se asigna a p la posición correspondiente (i) y se ejecuta la instrucción break9 para no seguir buscando. Código 1.1.2.3. Función que busca un carácter en una cadena, retorna -1 si no la encuentra o la posición si la encuentra. Contar los elementos del Arreglo El método char charAt(int indice) de la clase String, retorna el caracter ( de tipo char) i-ésimo de un string. String temp = "DEA" temp.charAt(0) --> 'D' temp.charAt(1) --> 'E' temp.charAt(2) --> 'A' temp.length( ) --> 3 (número de caracteres) Para recorrer la cadena carácter por carácter se usa: for(i = 0; i < temp.length( ) ; i++ ) Realizando la comparación (temp.charAt(i) == c) se va verificando si se cumple la condición. Si esto ocurre, el contador cont se incrementa en 1. Ahora, como hay que recorrer todo el arreglo en busca del carácter, se recorre el arreglo con un ciclo for(k = 0...), y dentro de este ciclo, se asigna a temp, el valor de a[k]. 9 La sentencia break se utiliza para finalizar un ciclo. Programación orientada a objetos en Java 14 Unidad I. Arreglos unidimensionales y multidimensionales Código 1.1.2.4. Función que cuenta las veces que un carácter se encuentra en una cadena. 1.1.3 Aplicaciones Ejemplo 1 Ejemplo de una declaración de un arreglo unidimensional. En un arreglo llamado “arreglo” se almacena cantidad de días que tienen los doce meses del año. Código 1.1.3.1.declaración e inicialización de un arreglo. Cuando se ejecuta el programa, imprime el número de días de abril. Ya se menciono que los índices de arreglo en Java comienzan con cero, de manera que el número de días del mes de abril es month_days[3] = 30. La declaración de una variable arreglo se puede combinar con la reserva de memoria para el propio arreglo, como se ve aquí. Programación orientada a objetos en Java 15 Unidad I. Arreglos unidimensionales y multidimensionales int dias_mes[] = new int[12]; Esta es la forma de declarar arreglos en Java. Los arreglos también pueden inicializarse cuando se declaran. Un inicializador de arreglo es una lista de expresiones entre llaves y separadas por comas, que separan los valores de los elementos del arreglo. Automáticamente se creará un arreglo suficientemente grande para contener el número de elementos que se haya especificado en el inicializador del arreglo. No es necesario utilizar new. Ejemplo 2. Versión mejorada del programa anterior: Código 1.1.3.2. Versión mejorada del programa anterior. Al ejecutar este programa, se listara misma salida que la que se genero la versión anterior. Java comprueba estrictamente que no se almacenan o se reverencien valores que están fuera del rango del arreglo de forma accidental10. El sistema de interpretación de Java, revisa el valor de cada índice dentro de dias_mes para asegurarse de que están comprendidos entre 0 y 11. Si se intenta acceder a los elementos que están fuera de rango del arreglo (números negativos o número mayores al rango del arreglo) se provocará un error en el momento de la ejecución. Ejemplo 3. Aquí se tiene otro ejemplo que utiliza un arreglo unidimensional, y donde se calcula la media de un conjunto de números. Código 1.1.3.3. Calcula e imprime la media de cinco valores. 10 Es una de las características por las que Java sobresale con respecto a otros lenguajes de programación. Programación orientada a objetos en Java 16 Unidad I. Arreglos unidimensionales y multidimensionales Al ejecutarse el programa anterior arrojara el valor promedio de los cinco números contenidos en el arreglo números. 1.2 Arreglos Bidimensionales 1.2.1 Conceptos básicos Definición de Arreglos Bidimensionales Se define como un conjunto de datos del mismo tipo organizados en dos o más columnas y uno o más renglones, es decir es una matriz o una tabla. Los arreglos con dos dimensiones se utilizan a menudo para representar tablas de valores, que constan de información ordenada en filas y columnas. Para identificar un elemento especifico de una tabla, se deben especificar los índices. Por convención, el primer índice indica la fila del elemento y el segundo, su columna. En Java los arreglos bidimensionales son arreglos de arreglos. Como era de esperarse, se parecen y actúan como los arreglos multidimensionales. Cuando un arreglo tiene dos dimensiones, hay mas en lo que se tiene que pensar. Considérese esta declaración e inicialización: int[][] mints = new int[3][4] Es natural asumir que mints contiene 12 enteros e imaginarlos organizados dentro de columnas y renglones, como se muestran en la tabla 1.2.1. 1 91 2001 2 92 2002 3 93 2003 4 94 2004 Tabla 1.2.1. Manera incorrecta de pensar acerca de arreglos multidimensionales. Realmente la tabla, esta desencaminada, mints es realmente un arreglo con tres elementos. Cada elemento es una referencia a un arreglo conteniendo 4 enteros, como se muestra en la figura 4. Figura 4: Ilustración de los arreglos bidimensionales. Programación orientada a objetos en Java 17 Unidad I. Arreglos unidimensionales y multidimensionales Los arreglos subordinados en una bidimensión no tienen que ser todos de la misma longitud. Es posible crear un arreglo como el que se muestra en la figura 5. Figura 5: Ilustración de arreglos bidimensionales (2). La figura 5 muestra un arreglo donde hay elementos de un arreglo de 3 enteros, un arreglo de 4 enteros y un arreglo de 2 enteros. Tal arreglo puede ser creado con la declaracíon: int[][] mints = { {1, 2, 3}, {91,92,93,94}, {2001, 2002} }; int[] reemplazo = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; myInts[1] = reemplazo; Declaración y Creación de Arreglos Bidimensionales Un arreglo bidimensional llamado b con dos filas y dos columnas, podría declararse e inicializarse con inicializadores de arreglos anidados, como se muestra a continuación: int b[][] = { {1 , 2}, {3 , 4}}; Los valores inicializadores se agrupan por filas entre corchetes. Por lo tanto, 1 y 2 inicializan b[0][0] y b[0][1]; 3 y 4 inicializan b[1][0] y b[1][1]. El compilador determina el número de filas contando el número de inicializadores de arreglos anidados11 en el inicializador del arreglo. El compilador determina el número de columnas en una fila contando los valores inicializadores en el inicializador del arreglo para esa fila. En java los arreglos bidimensionales se mantienen como arreglos de arreglos unidimensionales. Por lo tanto el arreglo b en la declaración anterior en realidad esta compuesto de tres arreglos unidimensionales separados. El arreglo b en si es un arreglo unidimensional que contiene dos elementos. Cada elemento es una referencia a un arreglo unidimensional de variables int. 11 Representados por conjuntos de llaves dentro de las llaves externas. Programación orientada a objetos en Java 18 Unidad I. Arreglos unidimensionales y multidimensionales Arreglos bidimensionales con filas de distintas longitudes La forma en que java representa los arreglos Bidimensionales los hace bastante flexibles. De hecho, las longitudes de las filas en el arreglo b no tienen que ser iguales. Por ejemplo: int b[][] = { { 1, 2 }, { 3, 4, 5 } }; En la línea anterior se crea el arreglo entero b con dos elementos (los cuales se determinan según el número de inicializadores anidados) que representan las filas del arreglo bidimensional. Cada elemento de b es una referencia a un arreglo unidimensional de variables int. El arreglo int de la fila 0 es una arreglo unidimensional con dos elementos (1 y 2), y el arreglo int de la fila 1 es un arreglo unidimensional con tres elementos (3, 4 y 5). Creación de arreglos bidimensionales mediante expresiones de creación de arreglos Un arreglo bidimensional con el mismo número de columnas en cada fila puede crearse mediante una expresión de creación de arreglos. Por ejemplo, en las siguientes líneas se declararán el arreglo b y se asignarán una referencia a un arreglo de tres por cuatro: Int b[][]; B = new int [2] [ ]; b [ 0 ] = new int [ 5 ]; b [ 1] = new int [ 3 ]; //crea 2 filas //crear 5 columnas para la fila 0 // crear 3 columnas para la fila 1 Estas instrucciones crean un arreglo bidimensional con dos filas. La fila 0 tiene 5 columnas y la fila 1 tiene 3 columnas. 1.2.2 Operaciones Mostrar un vector Ejemplo Para mostrar un vector en pantalla, se hace mediante un ciclo que se ejecutará n veces, donde n es el numero de elementos que tiene el vector. El ejemplo que sigue, se declara un vector String de 10 elementos. El programa contiene dos ciclos, en el primero se inicializa el vector con los valores de cadena 0, cadena 1, cadena 2 y así sucesivamente hasta llegar a cadena 9. en el segundo ciclo se manda a pantalla los 10 valores que contiene el vector. Programación orientada a objetos en Java 19 Unidad I. Arreglos unidimensionales y multidimensionales Código 1.2.2.1. Inicializa e imprime los elementos de un arreglo. Mostrar una matríz Mostrar una matriz en la pantalla de texto es difícil, ya que Java no dispone de una función que sitúe el cursor de texto en una posición de la pantalla, como lo hace la función gotoxy disponible en los lenguajes C/C++. La única alternativa que queda es mostrar los elementos de una fila unos a continuación de los otros separados por un tabulador, después otra fila y así hasta mostrar todos los elementos de la matriz. En el siguiente ejemplo se muestra en pantalla una matriz de 2 x 3; primero se inicializa la matriz en la línea int [][]a ={{1,2,3},{10,20,30}};. Se separan los elementos de una fila mediante el carácter tabulador '\t', Cuando se acaba una fila se inserta un retorno de carro '\n' y se continua con la siguiente fila, y así sucesivamente. Código 1.2.2.3. Inicializa e imprime los elementos de un arreglo bidimensional de 3 x 3. Programación orientada a objetos en Java 20 Unidad I. Arreglos unidimensionales y multidimensionales Se muestra en pantalla la matriz mediante la línea: System.out.print("\t" +a[i][j]); Copia de una matriz dada Para realizar una copia se ha de implementar la interface 12 Cloneable, y redefinir la función miembro clone de la clase base Object. El primer paso es la llamada a la función clone de la clase base Object. Código 1.2.2.4 Función que realiza la copia de una matriz dada usando la función “clone” de la clase matriz. Para obtener una copia a de una matriz d se escribe. Matriz a = (Matriz)d.clone(); La promoción (casting) es necesaria ya que clone devuelve una referencia a un objeto de la clase base Object. Traza de una matriz Se denomina traza de una matriz cuadrada a la suma de los elementos de su diagonal principal. 12 En el capitulo VI se explica de manera detallada el uso de una interface. Programación orientada a objetos en Java 21 Unidad I. Arreglos unidimensionales y multidimensionales Código 1.2.2.5. Imprime los elementos de la diagonal de un arreglo bidimensional de 2 x 2. Para obtener la traza de la matriz a de la sección anterior se escribe Traza=obj1.Traza(); Ejemplo Suma de dos matrices cuadradas Cuando se suman dos matrices de las mismas dimensiones Se obtiene otra matriz c en la que sus elementos cij son la suma de los correspondientes elementos de las matrices a y b, es decir C[i][j]=a[i][j]+b[i][j] Para sumar dos matrices, se define una función miembro estática denominada suma. Dentro de la función, se crea una matriz temporal resultado, con la misma dimensión de las matrices que intervienen en la operación, y se guardan en sus elementos el resultado de la suma de las matrices a y b. Finalmente, la función suma devuelve la matriz resultado. Programación orientada a objetos en Java 22 Unidad I. Arreglos unidimensionales y multidimensionales Código 1.2.2.6. Función que recibe de parámetros dos matrices bidimensionales y hace la suma de sus elementos. La función que suma dos matrices se llama de la siguiente manera: double[][] a1={{1, 2, 3},{4,5,6},{7,8,9}}; Matriz a=new Matriz(a1); double[][] b1={{1, 0, -1},{2,1,3},{-1, 0, 2}}; Matriz b=new Matriz(b1); Matriz re=Matriz.suma(a, b); System.out.println("matriz "+re); Producto de dos matrices La regla para multiplicar dos matrices es bastante más complicada que para sumar dos matrices de las mismas dimensiones. En general, se pueden multiplicar dos matrices de dimensiones m x n y n x q, dando como resultado una matriz de dimensiones m x q. Los elementos c[i][j] se obtienen multiplicando los elementos a[i][k] de la fila i por los elementos a[k][j] de la columna j, y sumando los resultados. La codificación se realiza empleando un triple ciclo for, guardando en los elementos de la matriz local resultado la suma de los productos de la fórmula anterior. Programación orientada a objetos en Java 23 Unidad I. Arreglos unidimensionales y multidimensionales Código 1.2.2.7. Función que recibe de parámetros dos matrices bidimensionales y arroja el producto de sus elementos. Otras variantes de la operación producto son: El producto de un escalar (número real) por una matriz que da como resultado otra matriz cuyos elementos están todos multiplicados por dicho escalar. Se define también la operación conmutativa. Código 1.2.2.8. Función que realiza el producto de dos matrices utilizando el productor escalar. Al multiplicar una matriz cuadrada de dimensión n, por un vector columna de la misma dimensión se obtiene otro vector columna. Cada elemento del vector resultante se obtiene multiplicando los elementos de una fila de la matriz por los correspondientes elementos del vector columna y se suman los resultados. La codificación de esta función producto es la siguiente: Programación orientada a objetos en Java 24 Unidad I. Arreglos unidimensionales y multidimensionales Al multiplicar un vector fila por una matriz cuadrada de la misma dimensión se obtiene otro vector fila. El código es semejante al de la función producto definida previamente. Matriz Traspuesta Una matriz traspuesta de otra matriz es aquella que tiene los mismos elementos pero dispuestos en forma distinta. Las columnas de la matriz original se transforman en filas de la matriz traspuesta. La definición de la función estática traspuesta no reviste dificultad alguna. Código 1.2.2.8. Función que realiza la matriz traspuesta de una matriz dada. Programación orientada a objetos en Java 25 Unidad I. Arreglos unidimensionales y multidimensionales Para hallar la matriz traspuesta de la matriz a se escribe double[][] a1={{1, 2, 3},{4,5,6},{7,8,9}}; Matriz a=new Matriz(a1); Matriz tras=Matriz.traspuesta(a); System.out.println("matriz traspuesta"+tras); 1.2.3 Aplicaciones Ejemplo 1 El siguiente programa se crea un arreglo bidimensional de 4 x 5 ,se enumera cada uno de los elementos del arreglo de izquierda a derecha y de arriba abajo, y se muestran estos valores: Código 1.2.3.1. Inicialización e impresión de un arreglo bidimensional de 4 x 5. Este programa generará la siguiente salida: 01234 56789 10 11 12 13 14 15 16 17 18 19 Programación orientada a objetos en Java 26 Unidad I. Arreglos unidimensionales y multidimensionales Cuando se reserva memoria para un arreglo bidimensional13, se puede únicamente especificar la memoria para la primera dimensión, la del extremo izquierdo. El espacio para las dimensiones restantes puede reservarse de manera separada. Ejemplo 2: El siguiente código reserva memoria para la primera dimensión de dosD cuando se declara. El espacio para la segunda dimensión se reserva manualmente. Int dosD[][] = new int[4][]; dosD[0] = new int[5]; dosD[1] = new int[5]; dosD[2] = new int[5]; dosD[3] = new int[5]; En este caso no resulta ventajoso reserva espacio para la segunda dimensión de forma individual, pero en otros casos, puede que sí lo sea. Por ejemplo, cuando se reserva memoria para dos dimensiones de forma separada, no es necesario reservar el mismo número de elementos para cada dimensión. Como se ha dicho, dado que los arreglos bidimensionales son arreglos de arreglos, la longitud de cada arreglo se puede establecer de forma independiente. Como ejemplo, véase el siguiente programa, que crea un arreglo bidimensional en que los tamaños de la segunda dimensión no son iguales14: Código 1.2.3.2. Reserva memoria para la primera dimensión de dosD cuando se declara. El espacio para la segunda dimensión se reserva manualmente. 13 14 Siempre se reserva usando el operador new. Arreglos irregulares. Programación orientada a objetos en Java 27 Unidad I. Arreglos unidimensionales y multidimensionales Este programa genera la siguiente salida: 0 12 345 6789 El arreglo creado con este programa tiene en el siguiente aspecto; [0][0] [1[0] [2[0] [3[0] [1[1] [2[1] [3[1] [2][2] [3][2] [3][3] El uso de arreglos bidimensionales irregulares puede no ser apropiado para muchas aplicaciones porque funciona de manera contraria a lo que la gente espera encontrarse en un arreglo bidimensional. Con todo, este tipo de arreglos puede utilizare de forma efectiva en algunas situaciones. Por ejemplo, si se necesita un arreglo bidimensional muy grande de escasos elementos (es decir, en la que no se vayan a utilizarse todos los elementos), un arreglo irregular puede ser la solución ideal. Es posible inicializar arreglos bidimensionales, para hacerlo, simplemente hay que introducir entre llaves el inicializador de cada dimensión. El siguiente programa crea un arreglo en la que cada elemento contiene el producto de la fila y los índices de la columna. Obsérvese también que pueden usarse tanto expresiones como valores literales en los inicializadores del arreglo. Código 1.2.3.3. Inicialización e impresión de un arreglo bidimensional irregular Cuando se ejecute este programa se obtendrá la siguiente salida: Programación orientada a objetos en Java 28 Unidad I. Arreglos unidimensionales y multidimensionales 0.0 0.0 0.0 0.0 0.0 1.0 2.0 3.0 0.0 2.0 4.0 6.0 0.0 3.0 6.0 9.0 Como puede apreciarse, cada fila en el arreglo se inicializa según se ha especificado en las listas de inicialización. 1.3 Arreglos multidimensionales 1.3.1 Conceptos básicos Java proporciona la posibilidad de almacenar varias dimensiones, aunque raramente los datos del mundo real requieren más de dos o tres dimensiones. El medio mas fácil de dibujar un arreglo de tres dimensiones es dibujar un cubo, tal como se muestra en la figura 6. Figura 6: Un arreglo de tres dimensiones (4 x 5 x 3). Un arreglo tridimensional se puede considerar como un conjunto de arreglos bidimensionales combinados juntos, para formar, en profundidad, una tercera dimensión. El cubo se construye con filas15, columnas16 y planos17. Por consiguiente, un elemento dado se localiza especificando su plano, fila y columna. Una definición de un arreglo tridimensional equipos es: int equipos[] [] [] = new int [3] [15] [10]; un ejemplo típico de un arreglo de tres dimensiones es el modelo libro, en el que cada pagina del libro es un arreglo bidimensional construido por filas y columnas. Así por 15 16 17 Dimensión vertical. Dimensión horizontal. Dimensión en profundidad. Programación orientada a objetos en Java 29 Unidad I. Arreglos unidimensionales y multidimensionales ejemplo, cada pagina tiene cuarenta y cinco líneas que forman las filas del arreglo y ochenta caracteres por línea, que forman las columnas del arreglo. Por consiguiente si el libro tiene quinientas paginas, existirán quinientos planos y el número de elementos será 500 x 80 x 45 = 1,800,000. 1.3.2 Aplicaciones El arreglo libro tiene tres dimensiones [PAGINA] [LINEAS] [COLUMNAS], que definen el tamaño del arreglo. El tipo de datos del arreglo es char, ya que los elementos, son caracteres. ¿Cómo se puede acceder a la información del libro? El método más fácil es mediante ciclos anidados. Dado que el libro se compone de un conjunto de páginas, el ciclo mas externo será el ciclo de pagina, y el ciclo de columna será el ciclo mas interno. Esto significa que el ciclo de filas se insertará entre los ciclos de páginas y columnas. El código siguiente permite procesar el arreglo: Código 1.3.2.1 Ilustración de acceso al arreglo libro de tres dimensiones mediante ciclos for anidados. Programación orientada a objetos en Java 30 Unidad I. Arreglos unidimensionales y multidimensionales Código 1.3.2.2 Determina si una matriz es simétrica. Si lo es, genera números aleatorios de 0 a 7. El programa itera hasta encontrar una raíz simétrica. Programación orientada a objetos en Java 31 Unidad I. Arreglos unidimensionales y multidimensionales 1.4 Ejercicios resueltos 1.4.1 Declare un arreglo x de 10 elementos e inicialícelo con valores del 1 al 10 y Muestre el séptimo valor del arreglo. 1.4.2. Declare un arreglo de flotantes de 70 elementos y muestre la suma de todos los elementos. 1.4.3 Declare un arreglo entero de 8 elementos y ordénelo usando el método de la burbuja. Programación orientada a objetos en Java 32 Unidad I. Arreglos unidimensionales y multidimensionales 1.4.4. Declare un arreglo bidimensional de 11 x 11 y imprima las tablas de multiplicar del 0 al 10. Programación orientada a objetos en Java 33 Unidad I. Arreglos unidimensionales y multidimensionales 1.5 Ejercicios propuestos 1.5.1 Determinar e imprimir los valores menor y mayor contenidos en el arreglo w con 99 elementos de punto flotante. 1.5.2 Diseñe un programa que guarde los 10 números dígitos y luego los imprima en forma ascendente y descendente. Usando, para ello, un arreglo de una dimensión. 1.5.3 En un arreglo se ha almacenado el número total de toneladas de cereales cosechadas durante cada mes del año anterior. Se desea la siguiente información: a) El promedio anual de toneladas cosechadas. b) ¿Cuántos meses tuvieron una cosecha superior al promedio anual? c) ¿Cuántos meses tuvieron una cosecha inferior al promedio anual?. Escriba un programa que proporciones estos datos. 1.5.4 Crear un vector de n elementos. Visualizarlo luego de desplazar los elementos una posición de tal forma que el último elemento ocupe la primera posición, el primero la segunda y así sucesivamente. 1.5.5 Diseñe una matriz en Java, de 6*6 elementos, y luego muestre, la suma de sus filas, y columnas por separado. 1.5.6 Se desea saber la suma y la multiplicación de dos matrices A y B, cada una con m*n elementos. 1.5.7 Realice una búsqueda binaria en un arreglo unidimensional. Una búsqueda binaria, es de la siguinte forma: se lee el valor que se desea buscar, se compara la primera posición, si son iguales, fin de la búsqueda; de lo contrario, compararlo con la posición dos, y así sucesivamente, si se llega al final del arreglo y no se encontró el valor, se debe indicar con una leyenda. Pero si se encuentra, se debe especificar la posición, que ese valor ocupa en el arreglo. 1.5.8 Etiquete los elementos del arreglo bidimensional ventas de 3 X 5, para indicar el orden en el que se establecen en cero, mediante el siguiente fragmento de programa. for (int fila = 0; fila < ventas.length; fila++) for( int col = 0; col > ventas[fila].length; col++) ventas[fila][col]=0; 1.5.9 Escriba un programa para simular el tiro de dos dados. El programa debe utilizar Math.random una vez para tirar el primer dado, y de nuevo para tirar el segundo dado. Después debe calcularse la suma de los dos valores que variar del 2 al 7, siendo 7 la suma más frecuente. En la figura 7 se muestran las 36 posibles combinaciones de los dos dados. El programa debe tirar los dados 36,000 veces. Utilice una arreglo unidimensional para registrar el número de veces que aparezca cada una de las posibles sumas. Programación orientada a objetos en Java 34 Unidad I. Arreglos unidimensionales y multidimensionales Figura 7: Las 36 posibles sumas de los dados. 1.5.10 El juego del ahorcado se juega con dos personas (o una persona y una computadora). Un jugador selecciona una palabra y el otro jugador trata de adivinar la palabra adivinando letras individuales. Diseñar un programa para jugar el ahorcado. Sugerencia: Almacenar una lista de palabras en un array y seleccionar palabras aleatoriamente. Programación orientada a objetos en Java 35 Unidad II. Métodos y mensajes UNIDAD II. MÉTODOS Y MENSAJES Concepto, Declaración, Tipos y Ejemplos de Métodos, Formas de pasar Argumentos y Estructura de Código. Programación orientada a objetos en Java 36 Unidad II. Métodos y mensajes UNIDAD 2. MÉTODOS Y MENSAJES 2.1 Atributos Const y Static Nota: El Atributo const no se utiliza en este lenguaje (java) Atributo Static18 Si se declara un atributo static, todas las instancias de esa clase comparten ese atributo, si lo cambia en una de ellas, todas ven el cambio en su propio atributo. El atributo pertenece a la clase, no a los objetos creados a partir de ella. El atributo static es atributo único para todos los objetos de la clase, estos son llamados también atributos de clase. El que un atributo sea común a todos los objetos de la clase se plasma en que sólo habrá una copia del atributo global a todos los objetos de la clase Ejemplos: static int a = 5, b = 4; En el ejemplo anterior se declaran tres variables (a y b). Las variables a y b son estáticas para toda la clase. 2.2 Concepto de método Los métodos son funciones que pueden ser llamadas dentro de la clase o por otras clases, por lo tanto los métodos son funciones definidas dentro de una clase. Salvo los métodos static o de clase, se aplican siempre a un objeto de la clase por medio del operador punto (.). Dicho objeto es su argumento implícito. Los métodos pueden además tener otros argumentos explícitos que van entre paréntesis. En general un método es: • • • 18 Un bloque de código que tiene un nombre Recibe unos parámetros o argumentos (opcionalmente) Contiene sentencias o instrucciones para realizar algo (opcionalmente) y devuelve un valor de algún tipo conocido (opcionalmente). Atributo Estático Programación orientada a objetos en Java 37 Unidad II. Métodos y mensajes 2.3 Declaración de Métodos Una declaración de método proporciona mucha información sobre el método al compilador, al sistema en tiempo de ejecución y a otras clases y objetos. Junto con el nombre del método, la declaración lleva información como el tipo de retorno del método, el número y el tipo de los argumentos necesarios, y qué otras clases y objetos pueden llamar al método. Los únicos elementos necesarios para una declaración de método son: • El nombre • El tipo de retorno del método Por ejemplo, el código siguiente declara un método llamado estaVacio() en la clase Pila que devuelve un valor booleano19. Código 2.3.1 Declaración de un método que devuelve un valor boleano. En el código siguiente se declara un método llamado suma que devuelve la suma de dos valores. El método recibe de parámetros dos valores enteros: Código 2.3.2 Llamada a un método llamado suma que recibe dos valores como parámetros y retorna un entero con la suma de los dos valores. 19 Los valores boléanos (boolean) solo aceptan dos valores, falso o verdadero. Programación orientada a objetos en Java 38 Unidad II. Métodos y mensajes 2.4 Llamadas a Métodos Para ejecutar o mandar llamar a un método sólo es necesario poner el nombre del objeto seguido por un punto y el nombre del método seguido de ( ). Si el método recibe algún valor como parámetro, dicho valor se debe especificar (entre paréntesis) a la hora de llamar al método. Los métodos pueden ser invocados o llamados de cualquier método de la clase, incluido él mismo. Además, si el método recibe parámetros, cuando se invoca, hay que pasar un valor a cada argumento, a través de una variable o un valor constante. En Java, la acción de pasar valores a parámetros de tipo primitivo (int, double, boolean, char..) se denomina paso de parámetros por valor. En éste caso, los argumentos que los reciben, no pueden ser modificados por la función. En caso que el parámetro sea de tipo Clase o arreglo, lo que se está haciendo es un paso de parámetros por referencia 20, y en este caso, los parámetros si pueden ser modificados por el método. En el paso de parámetro por referencia se pasa el valor como parámetro y cualquier cambio al valor en el método afecta directamente a la variable. 20 Programación orientada a objetos en Java 39 Unidad II. Métodos y mensajes Código 2.4.1 Llamada a un método que determina si un número es par o impar. Los métodos se invocan con su nombre, y pasando la lista de argumentos entre paréntesis. El par de argumentos se usa como si fuera una variable del Tipo devuelto por el método. Por ejemplo: char x; x = mayor(2,3); Se llama a los métodos dentro de una instancia de una clase utilizando el operador punto (.). La forma general de una llamada: referencia_a_objeto . nombre_de_método ( lista_de_parámetros ); Aquí, referencia_a_objeto es cualquier variable que se refiere a un objeto, nombre_de_método es el nombre de un método de la clase con la que se declaró referencia_a_objeto y lista_de_parámetros es una lista de valores o expresiones separados por comas que coinciden en número y tipo con cualquiera de los métodos declarados como nombre_de_método en la clase. En caso de que el método no reciba parámetros, en “lista argumentos” no se escribe nada. En este caso, se podría llamar al método init sobre cualquier objeto Point. Point p = new Point(); p.init(); 2.5 Tipos de Métodos 2.5.1 Métodos Estáticos o de Clase Se cargan en memoria en tiempo de compilación y no a medida que se ejecutan las líneas de código del programa. Van precedidos del modificador static. Para invocar a un método estático no se necesita crear un objeto de la clase en la que se define. Si se invoca desde la clase en la que se encuentra definido, basta con escribir su nombre. Si se le invoca desde una clase distinta, debe anteponerse a su nombre, el de la clase en que se encuentra seguido del operador punto (.) <NombreClase>.metodoEstatico() Suelen emplearse para realizar operaciones comunes a todos los objetos de la clase. No afectan a los estados de los mismos21. Por ejemplo, si se necesita un método para contabilizar el número de objetos creados de una clase, tiene sentido que sea 21 A los valores de sus variables de instancia. Programación orientada a objetos en Java 40 Unidad II. Métodos y mensajes estático ya que su función (aumentar el valor de una variable entera) se realizaría independientemente del objeto empleado para invocarle. No es conveniente usar muchos métodos estáticos, pues si bien se aumenta la rapidez de ejecución, se pierde flexibilidad, no se hace un uso efectivo de la memoria y no se trabaja según los principios de la Programación Orientada a Objetos. A veces se desea crear un método que se utiliza fuera del contexto de cualquier instancia. Todo lo que se tiene que hacer es declarar estos métodos como static. Los métodos estáticos sólo pueden llamar a otros métodos static directamente, y no se pueden referir a this o super 22 de ninguna manera. Las variables también se pueden declarar como static, pero debe ser consciente que es equivalente a declararlas como variables globales, que son accesibles desde cualquier fragmento de código. Se puede declarar un bloque static que se ejecuta una sola vez si se necesitan realizar cálculos para inializar las variables static. Ejemplos de Métodos Estáticos Ejemplo 1: Código 2.5.1.1 El ejemplo muestra una clase que tiene un método static, algunas variables static y un bloque de inicialización static. Esta es la salida del programa. bloque static inicializado x = 42 22 Estos dos conceptos (this y super) se explican en los temas posteriores. Programación orientada a objetos en Java 41 Unidad II. Métodos y mensajes a=3 b = 12 Ejemplo 2: Código 2.5.1.2 La funcionalidad de un método varía en función de si es invocado por el objeto hucha 1 o por hucha 2, no tendría sentido considerarlo estático. Código 2.5.1.3 La funcionalidad del método es la misma, independientemente del objeto empleado para invocarlo. Sí tiene sentido declararlo estático. Programación orientada a objetos en Java 42 Unidad II. Métodos y mensajes Ejemplo 3: Código 2.5.1.4 Programa que calcula la raíz cuadrada de 100 y con un retardo de 3 segundos calcula el valor de 2 a la 8 potencia. 2.5.2 Métodos Normales Los métodos son funciones definidas dentro de una clase. Salvo los métodos static o de clase, se aplican siempre a un objeto de la clase por medio del operador punto (.). Dicho objeto es su argumento implícito. Los métodos pueden además tener otros argumentos explícitos que van entre paréntesis, a continuación del nombre del método. La primera línea de la definición de un método se llama declaración o header; el código comprendido entre las llaves {…} es el cuerpo o body del método. Considérese el siguiente método tomado de la clase Circulo 23: Código 2.5.2.1 Demuestra encabezado y cuerpo de un método. El header consta del cualificador de acceso (public, en este caso), del tipo del valor de retorno (Circulo en este ejemplo, void si no tiene), del nombre de la función y de una lista de argumentos explícitos entre paréntesis, separados por comas. Si no hay argumentos explícitos se dejan los paréntesis vacíos. 23 Es una subclase no abstracta de la clase abstracta ObjetoGrafico Programación orientada a objetos en Java 43 Unidad II. Métodos y mensajes Los métodos tienen visibilidad directa de las variables miembro del objeto que es su argumento implícito, es decir, pueden acceder a ellas sin cualificarlas con un nombre de objeto y el operador punto (.). De todas formas, también se puede acceder a ellas mediante la referencia this, de modo discrecional o si alguna variable local o argumento las oculta. El valor de retorno puede ser un valor de un tipo primitivo o una referencia. En cualquier caso no puede haber más que un único valor de retorno (que puede ser un objeto o un array). Se puede devolver también una referencia a un objeto por medio de un nombre de interface. El objeto devuelto debe pertenecer a una clase que implemente esa interface. Se puede devolver como valor de retorno un objeto de la misma clase que el método o de una sub-clase, pero nunca de una super-clase. Los métodos pueden definir variables locales. Su visibilidad llega desde la definición al final del bloque en el que han sido definidas. No hace falta inicializar las variables locales en el punto en que se definen, pero el compilador no permite utilizarlas sin haberles dado un valor. A diferencia de las variables miembro, las variables locales no se inicializan por defecto. Si en el header del método se incluye la palabra native (Ej: public native void miMetodo();) no hay que incluir el código o implementación del método. Este código deberá estar en una librería dinámica (Dynamic Link Library o DLL 24). Es la forma de poder utilizar conjuntamente funciones realizadas en otros lenguajes desde código escrito en Java. Un método también puede declararse como synchronized (Ej: public synchronized double miMetodoSynch(){...}). Estos métodos tienen la particularidad de que sobre un objeto no pueden ejecutarse simultáneamente dos métodos que estén sincronizados 2.6 Referencia this 25 Java incluye un valor de referencia especial llamado this, que se utiliza dentro de cualquier método para referirse al objeto actual. El valor this se refiere al objeto sobre el que ha sido llamado el método actual. Se puede utilizar this siempre que se requiera una referencia a un objeto del tipo de una clase actual. Si hay dos objetos que utilicen el mismo código, seleccionados a través de otras instancias, cada uno tiene su propio valor único de this. Normalmente, dentro del cuerpo de un método de un objeto se puede referir directamente a las variables miembros del objeto. Sin embargo, algunas veces no se querrá tener ambigüedad sobre el nombre de la variable miembro y uno de los argumentos del método que tengan el mismo nombre. Estas librerías son archivos de funciones compiladas normalmente en lenguajes distintos de Java (C, C++, Fortran, etc.) 25 En español “este”. 24 Programación orientada a objetos en Java 44 Unidad II. Métodos y mensajes Por ejemplo, el siguiente constructor de la clase HSBColor inicializa alguna variable miembro de un objeto de acuerdo a los argumentos pasados al constructor. Cada argumento del constructor tiene el mismo nombre que la variable del objeto cuyo valor contiene el argumento. Código 2.6.1. Programa que ejemplifica el uso de referencia this. Se debe utilizar this en este constructor para evitar la ambigüedad entre el argumento luminosidad y la variable miembro luminosidad (y así con el resto de los argumentos). Escribir luminosidad = luminosidad; no tendría sentido. Los nombres de argumentos tienen mayor precedencia y ocultan a los nombres de las variables miembro con el mismo nombre. Para referirse a la variable miembro se debe hacer explícitamente a través del objeto actual--this. También se puede utilizar this para llamar a uno de los métodos del objeto actual. Esto sólo es necesario si existe alguna ambigüedad con el nombre del método y se utiliza para intentar hacer el código más claro. 2.7 Forma de Pasar Argumentos En el paso de parámetros a funciones hay dos aproximaciones clásicas: el paso por valor y paso por referencia. En el paso por valor se realiza una copia de los valores que se pasan, trabajando dentro de la función con la copia. Es por ello que cualquier cambio que sufran dentro, no repercute fuera de la función. En el paso por referencia no se realiza dicha copia, por lo que las modificaciones de dentro de las funciones afectan a los parámetros y esos cambios permanecen al final de la función. En Java el paso por parámetro es por valor, aunque los efectos son de paso por referencia cuando los argumentos son objetos. ¿cómo sucede eso? Pues es muy fácil, si una función tiene como argumento un tipo primitivo (int, float, etc...), en Java se realiza una copia para la función y cualquier cambio a dicho argumento no afecta a la variable original. Este paso de parámetros en Java está orientado a utilizar el valor de la variable para otros cálculos. Programación orientada a objetos en Java 45 Unidad II. Métodos y mensajes En el caso de los objetos es distinto. En realidad lo que sucede es que en Java siempre se tienen referencias a los objetos. Por eso al pasar a una función como argumento un objeto, se pasa la referencia al mismo, es decir, aunque se hace una copia para el paso por valor, como lo que se copia es una referencia, los cambios al objeto referenciado sí son visibles y afectan fuera de la función. La única excepción es la clase String , cuyos objetos no son mutables. Cualquier modificación de un objeto String lleva aparejada la creación de una nueva instancia del objeto. Si se desea el mismo comportamiento para el paso de parámetros del resto de objetos, se tiene que recurrir al objeto StringBuffer 26. Algunos métodos no necesitan parámetros, pero la mayoría sí. Los parámetros permiten generalizar un método. Es decir, un método con parámetros puede operar sobre una gran variedad de datos y/o utilizarse en un gran número de situaciones diferentes. Para ilustrar este punto se utilizará un ejemplo muy sencillo. Código 2.7.1 Función que devuelve el cuadrado del número 10. Efectivamente, este método devuelve el cuadrado de 10, pero su utilización es muy limitada. Con todo, si se modifica el método de manera que tome un parámetro, como se ve a continuación, se consigue que Cuadrado2() sea mucho más útil. Código 2.7.2 función que devuelve el cuadrado de cualquier valor que se llame con el. Es decir Cuadrado2() ahora es un método de propósito general que puede calcular el cuadrado de cualquier número entero, no sólo de 10. Verificar el ejemplo: La clase StringBuffer dispone de muchos métodos para modificar el contenido de los objetos StringBuffer. 26 Programación orientada a objetos en Java 46 Unidad II. Métodos y mensajes En la primera llamada a Cuadrado2(), el valor 5 se pasa al parámetro i. en la segunda llamada, i recibe el valor 9. En la tercera, se pasa el valor de y, que en este ejemplo es 2. Como muestran estos ejemplos, Cuadrado2() es capaz de devolver el cuadrado de cualquier dato que se pase al método. Es importante tener una idea precisa de lo que significan los términos parámetro y argumento. Un parámetro es una variable, definida por un método, que recibe un valor cuando se llama al método. Por ejemplo, en Cuadrado2(), i es un parámetro. Un argumento es un valor que se pasa a un método cuando se le llama. Por ejemplo Cuadrado2(100) pasa por 100 como argumento. Dentro de Cuadrado2(), el parámetro i recibe ese valor. Ejemplos de Llamadas a Métodos y Paso de Argumentos En el siguiente ejemplo, se llama a un método de la misma clase simplemente con el nombre del método y los parámetros entre paréntesis, como se ve en el ejemplo: Programación orientada a objetos en Java 47 Unidad II. Métodos y mensajes Código 2.7.3 programa que ejemplifica la llamada a métodos y el paso de argumentos. 2.8 Devolver un valor desde un método Java necesita que un método declare el tipo de dato del valor que devuelve. Si un método no devuelve ningún valor, debe ser declarado para devolver void 27. Los métodos pueden devolver tipos de datos primitivos o tipos de datos de referencia. En el siguiente ejemplo, el método estaVacio() de la clase Pila devuelve un tipo de dato primitivo, un valor booleano. 27 Nulo Programación orientada a objetos en Java 48 Unidad II. Métodos y mensajes Código 2.8.1 función que devuelve un valor boleano (falso o verdadero). Sin embargo, el método pop de la clase PILA devuelve un tipo de dato de referencia: un objeto. Código 2.8.2 función que devuelve un tipo se dato de referencia. Los métodos utilizan el operador return para devolver un valor. Todo método que no sea declarado como void debe contener una sentencia return. El tipo de dato del valor devuelto por la sentencia return debe corresponder con el tipo de dato que el método tiene que devolver; no se puede devolver un objeto desde un método que fue declarado para devolver un entero. Programación orientada a objetos en Java 49 Unidad II. Métodos y mensajes Cuando se devuelva un objeto, el tipo de dato del objeto devuelto debe ser una subclase o la clase exacta indicada. Cuando se devuelva un tipo interface 28, el objeto retornado debe implementar el interface especificado. Definición de métodos que no retornan datos Existen métodos que no devuelven datos. En este caso se estaría definiendo un procedimiento, la sintaxis es muy similar a la de muchos lenguajes de programación estructurados: Ejemplo: Código 2.8.3 Función que no devuelve valor y visualiza un numero complejo. En el ejemplo anterior se ha definido una clase que representa a un número complejo, la cual tiene un método que visualiza los valores real e imaginario del número. Declaración de un Método que no Devuelve Valores Donde la palabra reservada return indica que la ejecución del método ha concluido y establece el valor de retorno 29. Concepto ampliamente explicado en la unidad 6, subtema 6.3. En realidad los métodos que no retornan datos son un caso especial de los métodos que retornan datos pero se retorna un tipo de dato void el cual es un valor nulo. 28 29 Programación orientada a objetos en Java 50 Unidad II. Métodos y mensajes 2.9 Estructura del Código Generalmente la estructura de un método es la siguiente: <modificadores de acceso> <tipo de dato de retorno> <nombre del método>(tipo1 arg1,tipo2 arg2,...){ Cuerpo del método; } Explicación de cada uno de los componentes de un método: Nombre del método: el que desee el programador. Número y tipo de argumentos asociados al método (tipo1 arg1,tipo2 arg2,...): constituye su firma. Los argumentos pueden ser tanto variables primitivas como variables referenciadas. Las variables primitivas se emplean para almacenar números, caracteres o valores lógicos, mientras que las referenciadas están asociadas a objetos de clases. Tipo de dato de retorno o tipo de dato que devuelve el método: Dato asociado a una variable primitiva. Dato asociado a una variable referenciada. void. Es lo que debe ponerse cuando el método no devuelve nada y, por ejemplo, simplemente muestra por consola un mensaje. Es el caso del método "void mostrarMensaje()". Modificadores de acceso: public, private, protected y sin modificador. Sirven para fijar el grado de accesibilidad de un método. Además de los de acceso, se tienen otros como static, synchronized, final, abstract, etc. que afectan de un determinado modo al método. 2.10 Ejercicios Resueltos: 2.10.1 Implementar un programa que sume tres números utilizando un método estático. Programación orientada a objetos en Java 51 Unidad II. Métodos y mensajes 2.10.2 Implementar un programa que determine los 100 primeros números palíndromos (son los números que se leen igual de izquierda derecha que de derecha a izquierda) a partir de un número ingresado por teclado en adelante, usando un método para invertir los números. Programación orientada a objetos en Java 52 Unidad II. Métodos y mensajes 2.10.3. Se quiere determinar cuantos y cuales números perfectos y pares, existen entre a y b. Nota : Un numero perfecto es aquel que la suma de sus divisores sea igual al numero. Ej --> 6 : 1 + 2 + 3 2.1.0.4Se desea saber el valor de la siguiente sumatoria: s = SUMATORIA x^2 - y : desde a hasta b Siendo x : numero primo e y = numero múltiplo de 5 o 7 Programación orientada a objetos en Java 53 Unidad II. Métodos y mensajes 2.11 Ejercicios propuestos: 2.11.1 Escribir un método que tenga un argumento de tipo entero y que devuelva la letra P. si el número es positivo, y la letra N, si es cero o negativo. 2.11.2 Escribir un método lógico de dos argumentos enteros, que devuelva true si uno divide al otro y false en caso contrario. 2.11.3 Escribir un método que convierta una temperatura dada en grados Celsius a grados Fahrenheit. La formula de conversión es: F = 9/5 C + 32 Programación orientada a objetos en Java 54 Unidad II. Métodos y mensajes 2.11.4 Crear un programa que contenga un método y que muestre por consola el número de veces que aparece la letra "a" en una cadena, la cual será dada desde el teclado. 2.11.5 Escribir un método lógico que determine si un carácter es uno de los dígitos de 0 a 9. 2.11.6 escribir un método lógico que determine si un carácter es una vocal. 2.11.7 Escribir un método Redondeo que acepte un valor real cantidad y un valor entero decimales y devuelva el valor cantidad redondeado al número especificado de decimales. (Por ejemplo, Redondeo (20.5632.2) devuelve 20.56. 2.11.8 Escribir un método que acepte un número de día, mes y año y lo visualice en el formato. dd/mm/aa 2.11.9 Escribir un mètodo que lea un entero positivo y a continuación visualice sus factores primos. 2.11.10 Escribir un programa que permita al usuario elegir el cálculo del área de cualquier de las figuras geométricas: Círculo, cuadrado, rectángulo o triangulo, mediante métodos. Programación orientada a objetos en Java 55 Unidad III. Constructor, Destructor (Recolector de basura) UNIDAD III. CONSTRUCTOR, DESTRUCTOR (RECOLECTOR DE BASURA) Concepto, Declaración, Tipos y Aplicaciones de Constructores y Destructores (Recolectores de Basura) Programación orientada a objetos en Java 56 Unidad III. Constructor, Destructor (Recolector de basura) UNIDAD III. CONSTRUCTOR, DESTRUCTOR (RECOLECTOR DE BASURA) 3.1 Concepto de método constructor Un Constructor es un método especial en Java empleado para inicializar valores en Instancias de objetos, a través de este tipo de métodos es posible generar diversos tipos de instancias para la clase en cuestión; la principal característica de este tipo de métodos es que llevan el mismo nombre de la clase. Los constructores tienen el mismo nombre que la clase, no retornan ningún valor y no pueden ser heredados. Un método constructor inicializa las variables de instancia de una clase. Cuando se crea el objeto de una clase, new llama al método constructor de la clase para llevar acabo la inicialización. Un método constructor debe tener el mismo nombre que el de su clase30. Una clase puede contener constructores sobrecargados, los cuales permiten a los objetos de esa clase inicializarse de distintas maneras. Cuando un programa crea la instancia de un objeto de cierta clase, el programa puede proporcionar inicializadores (argumentos) entre paréntesis, a la derecha del nombre de la clase. Estos inicializadores se pasan como argumentos al constructor de la clase. Se requiere que toda clase tenga al menos un constructor. Por lo tanto, si no se declaran constructores para una clase, el compilador crea un constructor predeterminado que no toma argumentos. El constructor predeterminado para una clase llama al constructor sin argumentos para su superclase (la clase que extiende). 3.2 Recolector de basura (garbage collector)31 Muchos otros lenguajes orientados a objetos necesitan que se siga la pista de los objetos que se han creado y luego se destruyan cuando no se necesiten. Escribir código para manejar la memoria de esta es forma es aburrido y propenso a errores. En el lenguaje de programación Java no existen los destructores, en lugar de eso, el entorno de ejecución de Java tiene un recolector de basura que periódicamente libera la memoria ocupada por los objetos que no se van a necesitar más. Java permite crear tantos objetos como se quiera (sólo limitados por los que el sistema pueda manejar) pero nunca tienen que ser destruidos. El entorno de ejecución Java borra los objetos cuando determina que no se van a utilizar más. Este proceso es conocido como recolección de basura. El recolector de basura de Java es un barredor de marcas que escanea dinámicamente la memoria de Java buscando objetos, marcando aquellos que han sido referenciados. Después de investigar todos los posibles paths de los objetos, los que no 30 31 Incluyendo las mismas letras mayúsculas y minúsculas. Los destructores no existen en Java Programación orientada a objetos en Java 57 Unidad III. Constructor, Destructor (Recolector de basura) están marcados (esto es, no han sido referenciados) se les conoce como basura y son eliminados. Un objeto es ilegible para la recolección de basura cuando no existen más referencias a ese objeto. Las referencias que se mantienen en una variable desaparecen de forma natural cuando la variable sale de su ámbito. O cuando se borra explícitamente un objeto referencia mediante la selección de un valor cuyo tipo de dato es una referencia a null. El colector de basura funciona en un thread (hilo) de baja prioridad y funciona tanto síncrona como asíncronamente dependiendo de la situación y del sistema en el que se esté ejecutando el entorno Java. El recolector de basura se ejecuta síncronamente cuando el sistema funciona fuera de memoria o en respuesta a una petición de un programa Java. Un programa Java le puede pedir al recolector de basura que se ejecute en cualquier momento mediante una llamada a System.gc(). Aunque pedir que se ejecute el recolector de basura no garantiza que los objetos sean recolectados. La recolección de basura en Java funciona identificando objetos que son alcanzables por la máquina virtual de Java, como se muestra en la siguiente figura 8. De forma periódica32, la máquina virtual invoca al recolector de basura (GC)33. En la ejecución del programa, algunos objetos referencian a otros, creando un grafo dirigido. El recolector de basura examina todos los objetos en memoria y ve si son alcanzables desde el objeto raíz, es decir, si hay un camino en el grafo desde ese objeto raíz a cada objeto. Con esta información, el recolector de basura marca los objetos como alcanzables o no y libera todos los objetos que no son alcanzables. Figura 8: El recolector de basura de Java funciona determinando los objetos que son alcanzables. 32 33 Por ejemplo, cuando la cantidad usada de memoria sobrepasa un cierto límite. Garbage Collector Programación orientada a objetos en Java 58 Unidad III. Constructor, Destructor (Recolector de basura) 3.3 Declaración de métodos constructores Los métodos constructores tienen las siguientes características especiales: • • • • • Se llama igual que la clase. No devuelve nada, ni siquiera void. Pueden existir varios, pero siguiendo las reglas de la sobrecarga de funciones. De entre los que existan, tan sólo uno se ejecutará al crear un objeto de la clase. Dentro del código de un constructor generalmente suele existir inicializaciones de variables y objetos, para conseguir que el objeto sea creado con dichos valores iniciales. Sintaxis: Declaración de constructores: <modificadordeVisibilidad> <NombredelaClase> ( <argumentos> ) { < declaraciones> } Donde el modificadorvisibilidad es el tipo de modificador de acceso del constructor. El nombre de la clase como su nombre lo indica será el nombre del constructor. Se debe recordar que el constructor y la clase deben tener el mismo nombre. Los parámetros son las variables que reciben como parámetros el método constructor. Cabe mencionar que el constructor al ser método inicializador de variables, no devuelve ningún valor de retorno. Cuando se declaren constructores para las clases, se pueden utilizar los especificadores de acceso normales para especificar si otros objetos pueden crear ejemplares de su clase. private Niguna otra clase puede crear un objeto de su clase. La clase puede contener métodos públicos y esos métodos pueden construir un objeto y devolverlo, pero nada más. protected public Sólo las subclases de la clase pueden crear ejemplares de ella. Cualquiera puede crear un ejemplar de la clase. package-access Nadie externo al paquete puede construir un ejemplar de su clase. Visibilidad por omisión. Esto es muy útil si se quiere que las clases que se tengan en un paquete puedan crear ejemplares de la clase pero no se quiere que lo haga nadie más. Programación orientada a objetos en Java 59 Unidad III. Constructor, Destructor (Recolector de basura) Ejemplo: Código 3.3.1 Declaración de dos constructores. El primero no recibe argumentos y el segundo constructor recibe un valor entero de argumento. En este ejemplo se tiene la clase “clase1”, la cual tiene dos constructores, uno no recibe ningún parámetro e inicializa la variable “y” en 1. El otro constructor recibe de aegumento la variable “par” que se utiliza para inicializar la variable “y”. Ejemplo 2 Código 3.3.2 Declaración de dos constructores. El primero inicializa x e y en 0 y el segundo inicializa los valores x e y con los argumentos pasados a la función A. En el ejemplo anterior se tiene a la clase “A”, que tiene dos constructores, el primero inicializa las variables x e y en cero. Y el segundo constructor inicializa x e y con los valores pasados por los parámetros a y b. 3.4 Aplicaciones de constructores y destructores (Recolectores de basura) Ejemplo 1 Se puede tener cualquier número de constructores, todos los cuales tienen el mismo nombre. Al igual que otros métodos sobrecargados, los constructores se diferencian unos de otros en el número y tipo de sus argumentos. Programación orientada a objetos en Java 60 Unidad III. Constructor, Destructor (Recolector de basura) Se considerará la clase Rectangle del paquete java.awt 34 que proporciona varios constructores diferentes, todos llamados Rectangle(), pero cada uno con número o tipo diferentes de argumentos a partir de los cuales se puede crear un nuevo objeto Rectangle. Aquí están las firmas de los constructores de la clase java.awt.Rectangle. public Rectangle() public Rectangle(int width, int height) public Rectangle(int x, int y, int width, int height) public Rectangle(Dimension size) public Rectangle(Point location) public Rectangle(Point location, Dimension size) El primer constructor de Rectangle inicializa un nuevo Rectangle con algunos valores por defecto razonables, el segundo constructor inicializa el nuevo Rectangle con la altura y anchura especificadas, el tercer constructor inicializa el nuevo Rectangle en la posición especificada y con la altura y anchura especificadas, etc. Típicamente, un constructor utiliza sus argumentos para inicializar el estado del nuevo objeto. Entonces, cuando se crea un objeto, se debe elegir el constructor cuyos argumentos reflejen mejor cómo se quiere inicializar el objeto. Basándose en el número y tipos de los parámetros que se pasan al constructor, el compilador determina cual de ellos utilizar, Así el compilador sabe que cuando se escribe: new Rectangle(0, 0, 100, 200); El compilador utilizará el constructor que requiere cuatro argumentos enteros, y cuando se escribe: new Rectangle(miObjetoPoint, miObjetoDimension); Utilizará el constructor que requiere como argumentos un objeto Point y un objeto Dimension. Ejemplo 2. El constructor para esta subclase de Thread, un hilo que realiza animación, selecciona algunos valores por defecto como la velocidad de cuadro, el número de imágenes y carga las propias imágenes. 34 AWT es el acrónimo del X Window Toolkit para Java. Programación orientada a objetos en Java 61 Unidad III. Constructor, Destructor (Recolector de basura) Código 3.3.1 declaración de constructores. Obsérvese cómo el cuerpo de un constructor es igual que el cuerpo de cualquier otro método -- contiene declaraciones de variables locales, ciclos, y otras sentencias. Sin embargo, hay una línea en el constructor de Animacion que no se verá en un método normal--la segunda línea. super("Animacion"); Esta línea invoca al constructor proporcionado por la superclase de Animacion-Thread. Este constructor particular de Thread acepta una cadena que contiene el nombre del Thread. Frecuentemente un constructor se aprovechará del código de inicialización escrito para la superclase de la clase. En realidad, algunas clases deben llamar al constructor de su superclase para que el objeto trabaje de forma apropiada. Típicamente, llamar al constructor de la superclase es lo primero que se hace en el constructor de la subclase: un objeto debe realizar primero la inicialización de nivel superior. Ejemplo 3. A continuación se tiene a una clase árboles, la cual contiene cuatro constructores que inicializan los valores de manera diferente. El primer constructor solo imprime la cadena “un árbol genérico” El segundo constructor recibe de parámetro el tipo de árbol y lo imprime Programación orientada a objetos en Java 62 Unidad III. Constructor, Destructor (Recolector de basura) El tercer constructor recibe de parámetro la altura del árbol y la imprime El cuarto constructor recibe los dos parámetros, el tipo de árbol y la altura del árbol Código 3.3.2 Declaración de diferentes constructores que inicializan valores de forma diferente. 3.5 Tipos de Métodos Constructores y Destructores (Recolectores de Basura) Constructores Implícitos Cuando se escriben clases, no es obligatorio declarar constructores porque Java proporcionar constructores. El constructor por defecto, el constructor que no necesita argumentos, lo proporciona automáticamente el sistema para todas las clases. A este tipo de constructores se les llama constructores implícitos, porque se crea internamente en el lenguaje Java. Si no se declaran constructores automáticamente Java invoca al constructor implícito de la clase, al que ya tiene por defecto. Ejemplo: //constructor implícito Aquí se tiene a una Clase de Diseño llamada Bicicleta: Programación orientada a objetos en Java 63 Unidad III. Constructor, Destructor (Recolector de basura) Esta es la implementación de la Clase Bicicleta Código 3.4.1 Ilustración de constructores implícitos. Y ahora se crea una instancia de la clase Bicicleta desde cualquier otro Objeto Bicicleta b1 = new Bicicleta(); Se puede observar que la Clase Bicicleta no tiene ningún Constructor. En estos casos se dice que la Clase tiene un Constructor implícito y que el entorno de ejecución Java sólo utilizará el Constructor por defecto. Constructores Explícitos Los constructores explícitos son aquellos constructores que el programador declara. Cabe mencionar que los constructores también se pueden sobrecargas como los métodos normales. Métodos de Constructores Sobrecargados Los métodos de constructores de una clase pueden sobrecargarse. Los métodos constructores sobrecargados permiten a los objetos de una clase inicializarse de distintas Programación orientada a objetos en Java 64 Unidad III. Constructor, Destructor (Recolector de basura) formas. Para sobrecargar métodos constructores simplemente hay que proporcionar varias declaraciones del método constructor con distintas listas de parámetros. Estos Constructores sobrecargados • • Permiten crear Objetos pasándoles diferentes valores Estarán incluidos tanto en el código de la Clase como en las Clases de diseño de un Diagrama de Clases de diseño UML 35. Ejemplo 1: //constructor sobrecargado Aquí se tiene una Clase de Diseño llamada Bicicleta con un Constructor sobrecargado. Esta es la implementación de la Clase Bicicleta Código 3.4.2 Clase Bicicleta con sobrecarga de constructores. 35 Lenguaje Unificado de Modelado Programación orientada a objetos en Java 65 Unidad III. Constructor, Destructor (Recolector de basura) Este constructor sin argumentos tiene que estar escrito dentro de la Clase aunque no tenga contenido. Si no se hace así el compilador se quejará cuando desde otro Objeto se quiera crear una instancia de tipo Bicicleta con un Constructor sin argumentos Y ahora se crean dos instancias de la clase Bicicleta desde cualquier otro Objeto teniendo en cuenta que en estos momentos la Clase Bicicleta tiene declarado e inicializado de forma explícita un Constructor sobrecargado. Bicicleta b1 = new Bicicleta(); Bicicleta b2 = new Bicicleta("Cannondale"); Bicicleta b1 = new Bicicleta(); En este caso el entorno de ejecución Java utiliza: El Constructor por defecto que realiza lo siguiente - Inicializa la variable de instancia matricula a su valor por defecto que en este caso por ser una Clase lo inicializa a valor null. - Y obtiene una referencia al nuevo Objeto que se guarda en la variable de referencia b1 Bicicleta b2 = new Bicicleta("Cannondale"); En este caso el entorno de ejecución Java utiliza Primeramente el Constructor por defecto que realiza lo siguiente: - Inicializa la variable de instancia matricula a su valor por defecto que en este caso por ser una Clase lo inicializa a valor null Y seguidamente el Constructor sobrecargado con un argumento de tipo String que realiza lo siguiente: Inicializa la variable de instancia marca en este caso con el valor "Cannondale" y obtiene una referencia al nuevo objeto que se guarda en la variable de referencia b2 Ejemplo2. En este ejemplo se declaran dos constructores de la clase point, el primero recibe dos parámetros y establece las coordenadas x e y con dichos parámetros. Y el segundo constructor establece en -1 los valores de x e y. Programación orientada a objetos en Java 66 Unidad III. Constructor, Destructor (Recolector de basura) Código 3.4.3 Constructores sobrecargados de la clase Punto. Uno que recibe de parámetros dos valores enteros y el otro no recibe ningún parámetro. Primero se crea un objeto Point que llama al segundo constructor. En pantalla apareceran los valores de x = -1 e y = -1. Después se crea un segundo objeto que manda llamar al primer constructor. En pantalla aparecerá los valores de x = 4 e y = 16. Nota: Se recomienda siempre declarar un Constructor sin argumentos independientemente de si la clase tiene otro Constructor con argumentos por dos motivos - - Primero, la mayoría de los asistentes de los entornos de desarrollo Java al crear la plantilla de las Clases realizan las declaraciones de los Constructores sin argumentos, simplemente se ahorra tener que borrar el código correspondiente a la declaración del Constructor sin argumentos. Segundo, el programador se asegura de posibles errores de compilación si en un futuro desde otro Objeto decido además de crear una instancia de una Clase con su Constructor sobrecargado, también crear una instancia de esa misma Clase con un Constructor sin argumentos Programación orientada a objetos en Java 67 Unidad III. Constructor, Destructor (Recolector de basura) Tipos de Recolectores de Basura (Garbage Collectors) Java utiliza un modelo de memoria conocido como "administración automática del almacenamiento"36 en el que el sistema en tiempo de ejecución de Java mantiene un seguimiento de los objetos. En el momento que no están siendo referenciados por alguien, automáticamente se libera la memoria asociada con ellos. Existen muchas maneras de implementar recolectores de basura, entre ellas se tienen: • • 36 37 Contabilizar referencias. La máquina virtual Java asocia un contador a cada instancia de un objeto, donde se refleja el número de referencias hacia él. Cuando este contador es 0, la memoria asociada al objeto es susceptible de ser liberada. Aún cuando este algoritmo es muy sencillo y de bajo costo (en términos computacionales), presenta problemas con estructuras de datos circulares. Marcar e intercambiar37. Este es el esquema más común para implementar el manejo de almacenamiento automático. Consiste en almacenar los objetos en un montículo (heap) de un tamaño considerable y marcar periódicamente (generalmente mediante un bit en un campo que se utiliza para este fin) los objetos que no tengan ninguna referencia hacia ellos. Adicionalmente existe un montón alterno, donde los objetos que no han sido marcados, son movidos periódicamente. Una vez en el montículo alterno, el recolector de basura se encarga de actualizar las referencias de los objetos a sus nuevas localidades. De esta manera se genera un nuevo montículo, que contiene únicamente objetos que están siendo utilizados. Automatic Storage Managment. Mark-and-sweep. Programación orientada a objetos en Java 68 Unidad III. Constructor, Destructor (Recolector de basura) 3.6 Ejercicios propuestos 3.5.1 Realice un programa para realizar sumas de uno, dos, tres, cuatro o cinco dígitos implementando constructores para inicializar los valores según sea el caso. 3.5.2 Realice un programa que calcule el área de cualquier triangulo, usando constructores para asignar valores a las formulas. 3.5.3 Implemente un programa que calcule la serie de fibonacci entre dos rangos dados previamente desde el teclado (variables ri y rf). Use un constructor para asignar valores a ri y rf. 3.5.3 Escribir un programa que convierta un número romano (en forma de cadena de caracteres) en número arabigo, utilizando al menos un constructor explicito. 3.5.4 Escribir un programa que usando métodos y un constructor encuentre el valor mayor, el valor menor, y la suma de n números de entrada. 3.5.5 Escribir un método que calcule cuantos puntos de coordenadas enteras existen dentro de un triangulo del que se conocen las coordenadas de sus tres vértices. Use constructores. 3.5.6 Escriba un programa que utilice un método llamado CículoArea, que pida al usuario la radio de un círculo e imprima el área de ese círculo. Declare al menos un constructor explicito. 3.5.7 El máximo común divisor de dos enteros es el entero más grande que puede dividir a cada uno de dos números. Escriba un método que devuelva el máximo común divisor de dos números enteros. Use constructores. 3.5.8 Las computadoras están tomando un papel cada vez más importante en la educación. Escriba un programa que ayude a un estudiante de escuela primaria, para que aprenda a multiplicar. Use el método Math.random para producir dos números enteros positivos de un dígito. El programa debe entonces mostrar una pregunta en la barra de estado, como: ¿Cuánto es 6 x 7 ? El estudiante debe escribir la respuesta, si es correcta el programa manda un mensaje de “Muy bien” y si es incorrecta “Vuélvalo a intentar”. Use constructores. 3.5.10. Escribir un programa recursivo que calcule los N primero números naturales. Use constructores. Programación orientada a objetos en Java 69 Unidad IV. Sobrecarga UNIDAD IV. SOBRECARGA Conversión de Tipos y Sobrecarga de Métodos Programación orientada a objetos en Java 70 Unidad IV. Sobrecarga UNIDAD IV. SOBRECARGA 4.1 Conversión de Tipos En Java es posible transformar el tipo de una variable u objeto en otro diferente al original con el que fue declarado. Este proceso se denomina "conversión", "moldeado" o "tipado". Si se tiene alguna experiencia previa en programación, se sabe que es bastante común asignar un valor de un tipo a una variable de otro tipo. Si los dos tipos son compatibles, Java realizará una conversión automática. Por ejemplo, siempre es posible asignar un valor int a una variable long. Pero no todos los tipos son compatibles, y en consecuencia, no todas las conversiones son implícitamente posibles. Por ejemplo, no esta definida la conversión de double a byte, aunque, afortunadamente, se puede obtener una conversión entre tipos incompatibles. Para ello, se debe emplear un cast, que realiza una conversión explicita entre tipos incompatibles. Las normas de conversión entre tipos numéricos son las habituales en un lenguaje de Programación: si en una operación se involucran varios datos numéricos de distintos tipos todos ellos se convierten al tipo de dato que permite una mayor precisión y rango de representación numérica; así, por ejemplo: • • • Si cualquier operando es double todos se convertirán en double. Si cualquier operando es float y no hay ningún double todos se convertirán a float Si cualquier operando es long y no hay datos reales todos se convertirán en long Del mismo modo estas normas se extenderán para int, short y byte. En la conversión de tipos se establece la norma de que "en las conversiones el tipo destino siempre debe ser igual o mayor que el tipo fuente": Tipo Origen Byte Short Char Int Long Float double, double, double, double, double, double Tipo Destino float, long, int, char, short float, long, int float, long, int float, long float Tabla 4.1 Conversiones sin pérdidas de información Conversiones Automáticas de Java Cuando a un tipo de variable se le asigna un tipo de datos, se realiza una conversión automática de tipo siempre y cuando se cumplan estas dos condiciones: • Los dos tipos son compatibles. • El tipo de destino es más amplio que el tipo fuente. Programación orientada a objetos en Java 71 Unidad IV. Sobrecarga Si se cumplen estas dos condiciones, tiene lugar una conversión de ensanchamiento. Por ejemplo, el tipo int siempre es suficientemente grande para guardar todos los valores byte válidos, de manera que no se necesita una sentencia de conversión. Para las conversiones de ensanchamiento, los tipos numéricos, incluyendo los enteros y de coma flotante, son compatibles entre sí. Sin embargo, los tipos numéricos no son compatibles con los char o los boolean; tampoco los char o los boolean son compatibles entre sí. Ejemplo: 1 Por ejemplo se puede asignar un valor de tipo byte a una variable int, Ya que byte e int son compatibles y las variables int tienen un rango mayor que los valores byte. Por lo tanto, no se perderá ningún dato en la conversión de tipos. Código 4.1.1 Conversiones de tipo byte a tipo int El compilador de Java no tiene ningún problema con este código y hace la conversión de tipos automáticamente. Este es el resultado del programa C:\>java app int1 = 1 Este tipo de conversiones, en las que se convierte a un tipo de dato que tiene mayor rango, se llama extensión de conversiones. En ellas, los tipos numéricos, como entero o coma flotante, son compatibles entre sí. Por otro lado, los tipos char y boolean no son compatibles entre sí y tampoco con los tipos numéricos. Conversión de Tipos Incompatibles Aunque la conversión automática de tipos es útil, o llega a satisfacer todas las necesidades. Por ejemplo, ¿Qué ocurre si se intenta asignar un valor int a una variable byte? Esta conversión no se realizará automáticamente porque un byte es menor que un Programación orientada a objetos en Java 72 Unidad IV. Sobrecarga int 38. Este tipo de conversiones recibe a menudo el nombre de conversión de estrechamiento, porque se trata de estrechar el valor para que ajuste al tipo destino. Para crear una conversión entre dos tipos incompatibles, ha de utilizarse una cast. Un cast es simplemente una conversión explicita de tipos. Tiene esta forma general: (tipo-de-destino) valor Aquí, tipo-de-destino, especifica el tipo al que se desea convertir el valor determinado. Por ejemplo, el siguiente fragmento convierte un int en un byte. Si el valor del entero es mayor que el rango de un byte, se reducirá al módulo del rango del tipo byte. Código 4.1.2 Conversión de tipos, de tipo int a tipo byte. Este es el resultado de ejemplo anterior. C:\>java app byte1 = 1 Si no se hace explícitamente el cast, el compilador devolverá un error, pero con el cast de tipos, no hay problema, ya que Java decide que se conoce la posibilidad de perder algunos datos cuando se introduce un valor probablemente mayor en un tipo más pequeño. Por ejemplo cuando se pone un número en coma flotante en un long, la parte fraccional del número se truncará, y puede que se pierdan más datos si el valor en coma flotante esta fuera del rango que un long puede gestionar. Hay que tener en cuenta es que el compilador de Java convierte a un tipo de mayor precisión, si es necesario, al evaluar expresiones. Por ejemplo, se va a considerar el siguiente código, en el que parece que sólo hay bytes involucrados: 38 Conversión de estrechamiento. Programación orientada a objetos en Java 73 Unidad IV. Sobrecarga Código 4.1.3 Conversiones de tipos con perdida precisión. Este código se compila y se ejecuta como se esperaba, pero no sería así sin el cast (byte): C:\>java app Byte3 = 100 Una conversión diferente tendrá lugar cuando se asigne un valor de coma flotante a un tipo entero, en este caso, se trunca la parte decimal. Como ya se sabe, los enteros no tienen componentes decimales, por lo que cuando a un entero se le asigna un valor en coma flotante, el componente decimal se pierde. Por ejemplo si a un entero se le asigna el valor 1,23, el valor resultante será simplemente 1. el 0,23 será truncado. El siguiente programa es un ejemplo de algunas conversiones de tipo explicitas: Código 4.1.4 Diferentes tipos de cast. Programación orientada a objetos en Java 74 Unidad IV. Sobrecarga La salida que produce este programa es: Conversión de int a byte. i y b 257 1 Conversión de double a int. d y i 323.142 323 Conversion de double a byte. d y b 323.142 67 Obsérvese cada una de estas conversiones. Cuando el valor 272 se convierte en una variable byte, el resultado es el resto de la división de 257 entre 256 (el rango de un byte), que es 1 en este caso. Cuando la d se convierte en int, su componente decimal también se pierde, y ese valor se reduce al módulo 256, que en este caso es 67. Promoción Automática de Tipo en Expresiones39 Las conversiones de tipos pueden realizarse, además de en las asignaciones, en las expresiones. Para ver cómo sucede esto, se tiene en cuenta lo siguiente. En una expresión, la precisión necesaria de un valor intermedio a menudo supera el rango de cualquiera de los operándos. Por ejemplo, la siguiente expresión: Byte a = 40; Bye b = 50; Byte c = 100; Int d = a * b / c; El resultado del término intermedio a * b puede superar fácilmente el rango de cualquiera de sus operandos byte. Para tratar este tipo de problemas, Java promueve de una manera automática cada operando byte o short a int. Cuando evalúa una expresión. Esto significa que la subexpresíon a * b se calcula utilizando tipos enteros, no bytes. Por lo tanto, 2,000, que es el resultado de la expresión intermedia 50 * 40, es valido aunque se hayan especificado a y b como tipo byte A pesar de lo útiles que resultan las promociones automáticas, pueden causar errores de confusión en la compilación. Por ejemplo, este código, aparentemente correcto, puede causar un problema: Byte b = 50; b = b * 2; // ¡Error! ! No se puede asignar un int a un byte! Este código intenta almacenar 50 * 2, un valor de tipo byte perfectamente válido, en una variable byte. Sin embargo, cuando se evaluó la expresión, los operandos se promocionaron automáticamente al tipo int. Por lo tn anto, el tipo de la expresión ahora es de tipo int, y no se puede asignar al tipo byte sin utilizar la conversión explícita. Esto se 39 Conversiones Implícitas Programación orientada a objetos en Java 75 Unidad IV. Sobrecarga verifica incluso en este caso particular, en el que el valor que se intenta asignar se ajusta al tipo de destino. En casos en los que se prevén las consecuencias del desbordamiento, se debería utilizar la conversión explicita. byte b = 50; b = (byte) (b * 2); Que conduce al valor correcto de 100. 4.2 Sobrecarga de Métodos La sobrecarga de métodos es una técnica de la orientación a objetos que permite definir diferentes versiones de un método, todos con el mismo nombre pero con diferente lista de parámetros. Cuando se usa un método sobrecargado, el compilador de Java sabe cual es el que se quiere utilizar por el numero y/o tipo de parámetros que se le pasen, y buscará la versión del método con la lista de argumentos correcta. En java dentro de una misma clase podemos definir dos o más métodos que compartan el mismo nombre, aunque la declaración de sus parámetros sea diferente. Cuando éste es el caso, se dice que los métodos están sobrecargados, y que el proceso es una sobrecarga del método. La sobrecarga de métodos es una de las maneras en que Java implementa el polimorfismo40. El polimorfismo es una de las características más interesantes de Java. Cuando se llama a un método sobrecargado, Java utiliza el tipo y/o número de argumentos como guía para determinar a qué versión del método sobrecargado se debe llamar. Por lo tanto, los métodos sobrecargados han de ser diferentes en el tipo y/o número de parámetros. Mientras que los métodos sobrecargados pueden tener diferentes tipos, el tipo devuelto es por sí solo lo suficiente para distinguir dos versiones de un método. Cuando Java encuentra una llamada a un método de sobrecarga, simplemente se ejecuta la versión del método cuyos parámetros coinciden con los argumentos utilizados en la llamada. Ejemplo 1 Véase el siguiente ejemplo. Para sobrecargar un método, sólo hay que definirlo más de una vez, especificando una nueva lista de parámetros en cada una de ellas. Cada lista de parámetros deber ser diferente de cualquier otra de alguna forma, el número de parámetros o el tipo de uno o más de ellos. Por ejemplo se creara una clase llamada calculador con un método llamado “suma” que se sobrecarga dos veces. En la primera declaración recibe dos números de parámetros y en la segunda declaración recibe tres parámetros. 40 Se explicara detalladamente en los capítulos posteriores. Programación orientada a objetos en Java 76 Unidad IV. Sobrecarga Código 4.2.1 Sobrecarga de métodos. Ahora se pueden usar ambos métodos en el código, como sigue: Código 4.2.1 Sobrecarga de métodos. Uso de los métodos sobrecargados en el código anterior. Este es el resultado del programa: C:\>java app suma(2,2) = 4 suma(2,2,2) = 6 Como se puede ver, la sobrecarga proporciona una técnica potente, especialmente en el código que se entrega a otros desarrolladores, porque permite pasar diferentes listas de parámetros a un método, haciéndolo más fácil de usar de diferentes formas en el código. Ejemplo 2 Aquí se tiene otro ejemplo sencillo que ilustra la sobrecarga de métodos: Programación orientada a objetos en Java 77 Unidad IV. Sobrecarga Código 4.2.1 Sobrecarga de métodos. Sobrecarga del método prueba. Este programa genera la siguiente salida: No tiene parámetros a: 10 a y b: 10 20 a double: 123,25 Resultado de ob.prueba(2.67,12.68): = 15.35 Como se puede observar, el método prueba() se sobrecarga en cuatro ocasiones. La primera versión no tiene parámetros, la segunda tiene un parámetro entero, la tercera tiene dos parámetros enteros y la cuarta, dos parámetro double que suma y retorna el valor de dicha suma. El hecho de que la cuarta versión de prueba() tambien devuelva un valor no esta relacionado con la sobrecarga, ya que los tipos devueltos no desempeñan ningún papel en la resolución de la sobrecarga. Cuando se llama a un método sobrecargado, java busca las coincidencias entre los argumentos utilizados para llamar al método y los parámetros del método, sin embargo, esta coincidencia no siempre tiene que ser exacta. En algunos casos, las conversiones automáticas de tipos de Java pueden tener su importancia en la resolución de sobrecarga. Programación orientada a objetos en Java 78 Unidad IV. Sobrecarga Por ejemplo, véase el siguiente programa: Código 4.2.3 Aplicación de la conversión automática de tipos en la sobrecarga de métodos. Este programa genera la siguiente salida: No tiene parametros a y b: 10 20 prueba(double) a: 80 prueba(double) a: 123.2 Esta versión de SobrecargaDemo no se define define(int). Además, cuando se llama a prueba() con un argumento entero dentro de sobrecarga, no se encuentra ninguna coincidencia con los parámetros del método. Sin embargo, Java puede convertir automáticamente un entero en double, y esta conversión puede utilizarse para resolver la llamada. Por este motivo, cuando no se encuentra el método prueba(int), Java eleva i a double y entonces llama a prueba(double). Por supuesto, si prueba (int) se hubiera definido, este método hubiera sido llamado en lugar de prueba (double). Java empleará este tipo de conversiones automáticas únicamente en caso de que no se encuentren coincidencias exactas. Programación orientada a objetos en Java 79 Unidad IV. Sobrecarga Cuando se sobrecarga un método, cada versión de ese método puede realizar la actividad que desee. No existe ninguna regla que establezca que los métodos de sobrecarga han de relacionarse entre si, pero desde el punto de vista de estilo, la sobrecarga de métodos implica la relación. Por consiguiente, aunque puede utilizarse el mismo nombre para sobrecargar métodos que no están relacionados es mejor no hacerlo. Por ejemplo, podría utilizarse el nombre sqr para crear métodos que devuelvan el cuadrado de un entero y la raíz cuadrada de un valor en coma flotante, pero estas dos operaciones son muy distintas y esto va en contra de un propósito original de la sobrecarga de métodos. En la práctica, sólo se han de sobrecargar operaciones estrechamente relacionadas. Sobrescritura de Métodos En una jerarquía de clases, cuando un método de una subclase tiene el mismo nombre y tipo que un método de superclase, se dice que el método de la subclase sobrescribe al método de la superclase. Cuando se llama a un método sobrescrito desde una subclase, esta llamada siempre se refiere a la versión de ese método definida por la subclase. La versión del método definido por la superclases quedaría oculta. Véase el siguiente ejemplo: Código 4.2.4 Sobrescritura de métodos. Sobrecritura del método mostrar de la subclase B sobre la superclase A. Programación orientada a objetos en Java 80 Unidad IV. Sobrecarga La salida que produce el programa es: K=3 Cuando se llama a mostrar() en un objeto del tipo B, se utiliza la versión de mostrar() definida en B; es decir, la versión mostrar() dentro de B domina sobre la versión declarada en A. si se quiere acceder a la versión de la superclase de una función sobrescrita, se utilizará el método super. Por ejemplo, en esta versión de la subclase. Esto permite imprimir todas las variables de instancia: Código 4.2.5 En este caso no se da sobrescritura del método “mostrar” debido a que al llamarse se hace referencia al método de la superclase A con la cláusula super. La salida del código anterior es la siguiente: i and j: 1 2 k:3 Aquí super.mostrar() llama a la versión de show de la superclase(). La sobrescritura de métodos tan sólo se da cuando los nombres y el tipo de dos métodos son idénticos, si no lo son, entonces los métodos simplemente están sobrecargados. Por ejemplo, véase esta versión modificada del ejemplo anterior. Programación orientada a objetos en Java 81 Unidad IV. Sobrecarga Código 4.2.6 Los métodos con diferentes tipo de parámetros se sobrecargan, no se sobrescriben. La salida generada por este programa es la siguiente: Este es k: 3 i y j: 1 2 La versión de mostrar() en B toma un parámetro de tipo string. Esto hace que su tipo sea diferente al de A, que no toma parámetros. En consecuencia, no se la sobrescritura u ocultamiento de nombre. Programación orientada a objetos en Java 82 Unidad IV. Sobrecarga 4.3 Sobrecarga de Operadores 41 La sobrecarga de operadores permite redefinir ciertos operadores, para usarlos con las clases que se definen. Se llama sobrecarga de operadores porque se están utilizando el mismo operador con un número de usos diferentes, y el compilador decide cómo usar ese operador dependiendo sobre qué opera. La sobrecarga de operadores sólo se puede utilizar con objetos de clases, no se pueden redefinir los operadores para los tipos simples predefinidos. El Método toString()42 A veces es conveniente o necesario convertir un objeto a una cadena o String porque se necesitará pasarlo a un método que sólo acepta Strings. Todas las clases heredan toString() desde la clase Object y muchas clases del paquete java.lang sobreescriben este método para proporcionar una implementación más acorde con la propia clase. Por ejemplo, las clases Character, Integer, Boolean, etc.. sobreescriben toString() para proporcionar una representación en String de los objetos. La sobrecarga de operadores no se da en Java. El método toString() como método de clase permite el uso implicito del operador sobrecargado de concatenación ‘+’. 41 42 Programación orientada a objetos en Java 83 Unidad IV. Sobrecarga 4.4. Ejercicios resueltos 4.4.1 Implementar un programa que sume dos valores enteros y dos valores flotantes implementando sobrecarga de métodos. 4.4.2 Implementar un programa que calcule el área de dos rectángulos utilizando sobrecarga de constructores. Programación orientada a objetos en Java 84 Unidad IV. Sobrecarga 4.4.3 Implementar un programa que reciba dos valores enteros a y b. Si a > b hacer a/b, si a < b hacer suma de a+b y si son iguales, mandar el mensaje de igualdad. Usar sobrecarga de métodos. Programación orientada a objetos en Java 85 Unidad IV. Sobrecarga Programación orientada a objetos en Java 86 Unidad IV. Sobrecarga 4.5 Ejercicios Propuestos 4.5.1 Escribir las sentencias de asignación que permitan intercambiar los contenidos de los valores de dos variables. 4.5.2 Escriba un programa que lea dos enteros en la variables x e y, y a continuación obtenga los valores de 1: x /y, 2: x % y. ejecutar el programa varias veces con diferentes pares de enteros como entrada. 4.5.3 Escribir un programa que convierta un número dado en segundos en equivalente de minutos y segundos. 4.5.4 Determinar si el carácter asociado a un código introducido por teclado corresponde a un carácter alfabético, dígito, de puntuación, especial o no imprimible. Usar sobrecarga de métodos. 4.5.5 Escribir un programa que mediante métodos determine el área del circulo correspondiente a la circunferencia circunscrita de un triangulo del que se conoce las coordenada de los vértices. 4.5.6 Construir un programa que calcule y escriba el producto, cociente entero y resto de dos números de tres cifras. 4.5.7 La fuerza de atracción entre dos masas, m1 y m2, separadas por una distancia d, está dada por la formula: F= G * m1 * m2 / d2 Escribir un programa que lea la masa de dos cuerpos y la distancia entre ellos y a continuación obtenga la fuerza gravitacional entre ella. La salida debe ser en dinas. Una dina es igual a gr.cm/seg2. 4.5.8 la realción entre los dos lados (a,b) de un triángulo y la hipotenusa (h) viene dada por la formula a2 + b2 = h2. Escribir un programa que lea la longitud de los lados y calcule la hipotenusa. 4.5.9 Escribir un programa que determine si un año es bisiesto. Un año es bisiesto si es múltiplo 4 por ejemplo, 1984. sin embargo, los años múltiplos de 100 sólo son bisiestos cuando a la vez son múltiplos de 400. por ejemplo, 1800 no es bisiesto, mientras que 200 si lo será. 4.5.10 Usando sobrecarga de métodos, mplementar un programa que realice todas las operaciones básicas (suma, resta, multiplicación y división) con dos números dados. Programación orientada a objetos en Java 87 Unidad V. Herencia UNIDAD V. HERENCIA Herencia Simple, Herencia Múltiple, Clases Base, Clase Derivada, Miembros de las Clases Derivadas y Aplicaciones de Herencia Programación orientada a objetos en Java 88 Unidad V. Herencia UNIDAD V. HERENCIA 5.1 Introducción a la Herencia En la programación orientada existe la herencia, una de las características principales de programación orientada a objetos43. La herencia es una forma de reutilización del software en la que las clases, se crean absorbiendo los datos (atributos) y métodos (comportamientos) de una clase existente, y se mejoran con nuevas capacidades, o con modificaciones en las capacidades ya existentes. La reutilización de software ahorra tiempo durante el desarrollo de programas. También fomenta la reutilización de software probado y depurado de alta calidad, el cual aumenta la probabilidad de que un sistema se implemente con efectividad. La herencia es una de las piedras angulares de la programación orientada a objetos porque permite la creación de clasificaciones jerárquicas. Mediante la herencia el programador puede crear una clase general que defina los rasgos comunes de un conjunto de términos relacionados. Esa clase puede ser heredada por otras clases mas especificas, cada unas de las cuales añadirá aquellos rasgos que las hace únicas. Al crear una clase, en vez de declarar miembros (variables y métodos) completamente nuevos, el programador puede designar que la nueva clase herede los miembros de una clase existente. Esta clase existente se conoce como superclase, y la nueva clase se conoce como subclase. Una vez creada cada subclase puede convertirse en superclase de futuras subclases. Una subclase generalmente agrega sus propias variables y métodos, por lo tanto una subclase es más específica que una superclase y representa a un grupo mas especializado de objetos. Generalmente la subclase exhibe los comportamientos de su superclase junto los comportamientos específicos de esta subclase. La superclase, es la superclase directa a partir de la cual la subclase hereda en forma explicita. Una superclase indirecta se hereda de dos o más niveles arriba en la jerarquía de clases, la cual define las relaciones de herencia entre las clases. La experiencia en la creación de sistemas de software indica que algunas cantidades considerables de código tratan con casos especiales, estrechamente relacionados. Cuando los programadores se preocupan con casos especiales, los detalles pueden oscurecer el panorama general. Con la programación orientada a objetos, los programadores se enfocan en los elementos comunes entre los objetos en el sistema, en vez de enfocarse en los casos especiales. A este proceso se conoce como abstracción. En Java existen los términos comunes “tiene un” y “es un”. Es necesario hacer una diferencia entre la relación “es un” y la relación “tiene un”. La relación “es un” representa la Herencia; en este tipo de relación, un objeto de una subclase puede tratarse también como un objeto de sus superclases. Por ejemplo un auto es un vehiculo. En contraste, la relación “tiene un” identifica a la composición; en este tipo de relación, un objeto contiene 43 POO. Programación orientada a objetos en Java 89 Unidad V. Herencia una o más referencias a objetos como miembros. Por ejemplo, un auto tiene un volante de dirección. Las clases nuevas pueden heredar de las clases en diversas bibliotecas de clases. Las organizaciones desarrollan sus propias bibliotecas de clases y pueden aprovechar las que ya están disponibles en todo el mundo. En la siguiente figura 9, se tiene una clase bicicleta que es superclase de bicicleta de montaña, bicicleta de camino y bicicleta de dos asientos. Bicicleta Bicicleta de montaña Bicicleta de camino Bicicleta tandem Figura 9: Clase bicicleta 5.2 Herencia Simple La herencia simple es uno de los aspectos de la programación orientada a objetos que se ha definido formalmente. La herencia simple, se puede definir como la herencia en la cual una clase hereda los métodos y atributos de solo una clase superior. En éste tipo de herencia una clase se extiende a partir de una sola superclase. La herencia múltiple, es la posibilidad de que un objeto tenga la herencia de más de una clase; esta ventaja fue considerada por los desarrolladores pero al final no la incluyeron por lo cual Java no lo contempla. Utilizando la herencia simple, se puede derivar una nueva clase a partir de la base. La clase nueva se llama clase derivada y la clase original, clase base. La idea es añadir lo que se quiera a la nueva clase para darle más funcionalidad que a la clase base. A menudo un objeto de una clase “es un” objeto de otra clase también. Por ejemplo, en la geometría un rectángulo es un cuadrilátero (al igual que los cuadrados, los paralelogramos Programación orientada a objetos en Java 90 Unidad V. Herencia y los trapezoides). Por lo tanto en Java puede decirse que la clase Rectángulo hereda de la clase Cuadrilátero. En este contexto la clase Cuadrilátero es una superclase, y la clase Rectángulo es una subclase. Un rectángulo es un tipo específico de cuadrilátero, pero es incorrecto decir que todo cuadrilátero es un rectángulo; el cuadrilátero podría ser un paralelogramo o alguna otra figura. En la siguiente tabla se muestran ejemplos sencillos de superclases y subclases. Superclase Estudiante Figura Prestamo Empleado CuentaBancaria Subclases EstudianteGraduado,EstudianteNoGraduado Circulo, Triangulo, Rectangulo PrestamoAutomovil, PrestamoMejoraCasa, PrestamoHiopotecario Docente, Administrativo CuentaDeCheques, CuentaDeAhorros Como todo objeto de una subclase “es un” objeto de su superclase, y como una superclase puede tener muchas subclases, el conjunto de objetos representados por una superclase generalmente es mas grande que el conjunto de objetos representado por cualquiera de sus subclases. Por ejemplo la superclase Vehiculo representa a todos los vehículos, incluyendo autos, camiones, barcos, bicicletas, etc. En contraste, la clase Auto representa a un subconjunto más pequeño y específico de todos los vehículos. Las relaciones de herencia forman estructuras jerárquicas en forma de árbol. Una superclase existe en una relación jerárquica con sus subclases. Aunque las clases pueden existir de manera independiente, cuando participan en relaciones de herencia se afilian con otras clases. Una clase se convierte ya sea en una superclase, proporcionando datos y comportamientos a otras clases, o en una subclase, heredando sus datos y comportamientos de otras clases. Es posible tratar a los objetos de superclases y a los objetos de subclases de manera similar; sus similitudes se expresan en los miembros de la superclase. Los objetos de todas las clases que extienden a una superclase común pueden tratarse como objetos de esa superclase (es decir, dichos objetos tienen una relación “es un” con la superclase). Sin embargo, los objetos de una superclase no pueden tratarse como objetos de sus subclases. Por ejemplo, todos los autos son vehículos pero no todos los vehículos son autos. Un problema con la herencia es que una subclase puede heredar métodos que no necesita, o que no debe tener. A pesar de que en un método de superclase sea apropiado para una subclase, a menudo esa subclase requiere que el método realice su tarea de una manera específica para la subclase. En dichos casos, la subclase puede sobrescribir44 el método de la superclase con una implementación apropiada. 44 Redefinir. Programación orientada a objetos en Java 91 Unidad V. Herencia Ejemplo 1 Se tiene la clase Ave, se puede crear la subclase Pato, que es una especialización de Ave. class Pato extends Ave { int numero_de_patas; } La palabra clave extends 45 se usa para generar una subclase (especialización) de un objeto. Una Pato es una subclase de Ave. Cualquier cosa que contenga la definición de Ave será copiada a la clase Pato, además, en Pato se pueden definir sus propios métodos y variables de instancia. Se dice que Pato deriva o hereda de Ave. Además, se pueden sustituir los métodos proporcionados por la clase base En Java no se puede hacer herencia múltiple 46. Por ejemplo, de la clase aparato con motor y de la clase animal no se puede derivar nada, sería como obtener el objeto toro mecánico a partir de una máquina motorizada (aparato con motor) y un toro (animal). En realidad, lo que se pretende es copiar los métodos, es decir, pasar la funcionalidad del toro de verdad al toro mecánico, con lo cual no sería necesaria la herencia múltiple sino simplemente la comparación de funcionalidad. Ejemplo 2 La herencia simple se puede ilustrar con un ejemplo sencillo como el siguiente: Código 5.1.1 Ilustración de herencia simple. Esta clase no está muy completa así, pero da una idea… Es una clase heredera de la clase Frame (un tipo de ventana) que tiene un par de botones y un texto. Contiene los atributos 45 46 Extender de En Java se solucionan esos problemas con la implementación de Interfaces. Programación orientada a objetos en Java 92 Unidad V. Herencia ("si" y "no"), que es dos objetos del tipo Button, y un constructor llamado Muestra (igual que la clase, por lo que es lo que se llama un constructor). Ejemplo 3 Cuando en Java se indica que una clase “extends” otra clase se esta indicando que es una clase hija de esta y que, por lo tanto, hereda todos sus métodos y variables. Código 5.1.2 Herencia. La clase Perro hereda métodos y características de la superclase Animal. Programación orientada a objetos en Java 93 Unidad V. Herencia Se puede observar un objeto de tipo perro creado invocando al constructor por defecto perro(). Se puede observar como la variable edad se ha inicializado a 0, y la variable nombre a “Tobi”, como se indicó en el constructor. Así mismo se ve como el objeto perro ha heredado todos los métodos definidos en la clase animal. 5.3 Herencia Múltiple La herencia múltiple es una de las oportunidades que ofrece el lenguaje c++ y es la posibilidad de que un objeto tenga la herencia de más de una clase; esta ventaja fue considerada por los desarrolladores de Java como una pega y la quitaron, e incluso hay desarrolladores de c++ que prefieren evitar este tipo de herencia ya que puede complicar mucho la depuración de programas Para ilustrar un caso de herencia múltiple se ha definido la superclase Animal de ella heredan dos clases distintas: Mamífero y Vertebrado . Ahora se quiere definir un objeto que tiene propiedades de esas dos clases: Hiena, ya que es un animal mamífero y además es vertebrado. 5.4 Clase Base y Clase Derivada 5.4.1 Definición de la Clase Base Clase base. También se le llama superclase, es la clase que hereda los métodos y atributos a la clase derivada o clase base. Código 5.1.3 Declaración de una clase base o superclase. 5.4.1.2 Declaración de la Clase Base La declaración de una clase base o superclase es una declaración normal. La siguiente es la definición más simple de una clase: Como se puede observar, la definición de una superclase consta de dos partes fundamentales: * La declaración de la superclase Indica el nombre de la superclase precedido por la palabra clave class. Programación orientada a objetos en Java 94 Unidad V. Herencia * El cuerpo de la superclase El cuerpo de la clase sigue a la declaración de la clase y está contenido entre la pareja de llaves ({ y }). El cuerpo de la clase contiene las declaraciones de las variables de la clase, y también la declaración y la implementación de los métodos que operan sobre dichas variables. 5.4.2 Definición de Clase Derivada Clase derivada. También se le conoce como “subclase” es aquella que hereda los métodos y atributos de la superclase o clase base. 5.4.2.2 Declaración de la Clase Derivada En la declaración de la subclasese utiliza la palabra reservada extends para especificar que dicha clase heredera los métodos de la superclase: Código 5.1.4 Declaración de una subclase. La extensión de clases se lleva a cabo mediante la cláusula extends. Extends establece una relación de especialización –generalización entre clases. La clase extendida es la superclase. La clase que se extiende es la subclase. Una clase puede tener, a lo sumo, una superclase47. La subclase puede introducir nuevos métodos. Así mismo, la subclase puede redefinir métodos de la superclase Ejemplo: Supóngase que se tiene una clase llamada “numeros_imaginarios” que hereda métodos o que es subclase de “numeros”, la declaración de dicha clase sería: 47 De hecho en Java toda clase tiene su superclase. Programación orientada a objetos en Java 95 Unidad V. Herencia 5.5 Parte Protegida48 5.5.1 Propósito de la Parte Protegida Intuitivamente, protected49 se utiliza para aquellos atributos y métodos que pueden ser únicamente accedidos a nivel de subclase. De esta forma, la parte protegida de una clase es aquella parte que se publica únicamente a los implementadores que extienden la clase. No obstante, su significado es más sutil. Un miembro protegido puede ser referido desde el código de una clase a través de referencias a objetos que son, al menos, del mismo tipo que dicha clase. Los identificadores creados en la parte protegida no serán accesibles desde otros programas o módulos, pero sí tendrán acceso a ellos aquellos objetos que sean derivados del nuestro También pueden ser accedidos desde cualquier clase que pertenece al paquete de la clase que lo declara. Constructores en las clases Derivadas El constructor de una clase derivada puede llamar al constructor de su clase base mediante la palabra super. Subclase(lista_parámetros){ super(lista_parámetros); // sentencias adicionales } Si no se llama explícitamente al constructor de la clase base, se llama automáticamente al constructor por defecto de la clase base. 5.5.1.1 Llamada a constructor por defecto. 48 49 Protected Protegido(a). Programación orientada a objetos en Java 96 Unidad V. Herencia 5.6 Redefinición de los miembros de las clases derivadas El uso de super Además se podría pensar en redefinir algunos métodos de la clase base pero haciendo que métodos con el mismo nombre y características se comporten de forma distinta. Por ejemplo se podría pensar en rediseñar el método toString de la clase Empleado añadiendo las características propias de la clase Ejecutivo. Así se podría poner: 5.6.1. Redefinición de métodos utilizando “super”. De esta forma cuando se invoque jefe.toString() se usará el método toString de la clase Ejecutivo en lugar del existente en la clase Empleado. Obsérvese en el ejemplo el uso de super, que representa referencia interna implícita a la clase base (superclase). Mediante super.toString() se invoca el método toString de la clase Empleado Inicialización de clases derivadas Cuando se crea un objeto de una clase derivada se crea implícitamente un objeto de la clase base que se inicializa con su constructor correspondiente. Si en la creación del objeto se usa el constructor no-args 50, entonces se produce una llamada implícita al constructor no-args para la clase base. Pero si se usan otros constructores es necesario invocarlos explícitamente. Ejemplo: 50 Constructor por defecto. Programación orientada a objetos en Java 97 Unidad V. Herencia Código. 5.6.2 Uso de “super” en la inicialización de clases derivadas. Observe que el constructor de Ejecutivo invoca directamente al constructor de Empleado mediante super(argumentos). En caso de resultar necesaria la invocación al constructor de la superclase debe ser la primera sentencia del constructor de la subclase. 5.7 Clases Virtuales y Visibilidad Virtual es una palabra clave que tiene dos acepciones completamente diferentes dependiendo del contexto de su utilización. Utilizada con nombres de clase sirve para controlar aspectos del mecanismo de herencia; utilizada con nombres de funcionesmiembro, controla aspectos del polimorfismo y del tipo de enlazado que se utiliza para tales funciones51 5.8 Constructores en Clases Derivadas En el tema de la herencia, los constructores no se heredan, por ejemplo: Código. 5.8.1 Los constructores no se heredan. 51 Java no soporta las clases virtuales. Programación orientada a objetos en Java 98 Unidad V. Herencia B b = new B(1,2); // error, los constructores no se heredan Código. 5.8.2. Invocación del constructor de la clase base utilizando “super”. La invocación del constructor de A siempre debe ser la primera instrucción del constructor de B. El principio es que en B las componentes de la clase base (A) deben inicializarse antes que las componentes que se agregan en la clase B. 5.9 Aplicaciones Aplicación 1. Ejemplo Práctico de Herencia Véase ahora un ejemplo más práctico que ayudará a ilustrar el poder de la herencia utilizando la clase box. Programación orientada a objetos en Java 99 Unidad V. Herencia Programación orientada a objetos en Java 100 Unidad V. Herencia Código. 5.9.1. Se extiende la clase box para agregarle un cuarto componente que es peso. Por lo tanto, la nueva clase contiene el largo, el ancho, el alto y el peso de la caja. La salida de este programa es: El voulmen de micaja1 es 3000.0 El peso de micaja1 es 34.3 El volumen de micaja2 es 24.0 El peso de micaja2 es 0.076 BoxPeso hereda todas las características de Box y añade el componente peso. No es necesario que BoxPeso vuelva a crear todas las características de Box, le basta con ampliar la clase Box. La principal ventaja de la herencia es que, una vez se ha creado una superclase que define los atributos comunes a un conjunto de objetos, puede utilizarse para crear cualquier número de subclases más específicas. Cada subclase puede adoptar de forma mas precisa su propia clasificación. Por ejemplo: Programación orientada a objetos en Java 101 Unidad V. Herencia Código. 5.9.2. La clase ColorCaja hereda de Box y añade un atributo del color. Hay que recordar que una vez se ha creado la superclase que define los aspectos generales de un objeto, esta superclase puede heredarse para formar clases especializadas. Cada subclase simplemente añade sus propios atributos. Ésa es la esencia de la herencia. Aplicación 2. Herencia derivando la clase figura. La clase base es Figura, y de ella van a heredar las clases Poligono y Circulo. A su vez, de Poligono heredarán las clases Cuadrado y Triangulo. El diagrama esquemático de clases es el siguiente: La clase base Figura solo tiene dos atributos: nombre de la figura y perímetro (longitud total de su borde exterior) y, aparte del constructor, un único método llamado imprimirInformacion que imprime por pantalla la información de la figura. Aquí está la clase como se muestra en el código 5.9.3. Código. 5.9.3. Clase Figura programada. En el método main, crea tres objetos y luego imprime la información de cada uno de ellos: un cuadrado de lado 2 metros, un círculo de radio 2 metros y un triángulo de lado 3 metros. Programación orientada a objetos en Java 102 Unidad V. Herencia Compila y ejecuta el programa. La salida por pantalla debe ser la siguiente: Soy un cuadrado y mi perímetro es 8.0 m Soy un círculo y mi perímetro es 12.5663704 m Soy un triángulo y mi perímetro es 9.0 m Aplicación 3 Clase Circulo. Ahora se va a programar la clase Circulo, que hereda de la clase anterior Figura. Esta nueva clase tiene un atributo más (aparte de los heredados), de tipo double, que almacena el radio del círculo, que se le pasa en el constructor. El radio sirve para calcular el perímetro del círculo (2*pi*radio). Además esta clase sobre escribe el método anterior imprimirInformacion para añadir un mensaje más explicativo con el valor del radio. El código de la clase Circulo se muestra en el código 5.9.4: Código 5.9.4. Clase circulo. Modifica el método main, y crea dos objetos de la clase Circulo, uno de radio 2 metros y otro de radio 3 metros, e imprime su información. Tras compilar y ejecutar el programa, la salida por pantalla debe ser la siguiente: Soy un círculo y mi radio es 2.0 m, con lo que mi perímetro es 12.5663704 m Soy un círculo y mi radio es 3.0 m, con lo que mi perímetro es 18.849555600000002 m Aplicación 4. Clase polígono. Ahora toca la clase Poligono, muy parecida a la clase Circulo anterior. La diferencia es que en vez de radio los atributos del polígono son el número de lados y la longitud de cada lado, con los que podemos calcular el valor del perímetro. Programación orientada a objetos en Java 103 Unidad V. Herencia Este ejercicio consiste en programar la clase Poligono. En el método main crea dos polígonos, uno de 3 lados con longitud 3 (triángulo) y otro de 4 lados con longitud 4 (cuadrado). El esqueleto de la clase se muestra en el código 5.9.5: Código 5.9.5 Clase polígono. La salida en pantalla debe ser la siguiente: Soy un polígono cuadrado, tengo 4 lados de longitud 2.0 m, con lo que mi perímetro es 8.0 m Soy un polígono triángulo, tengo 3 lados de longitud 3.0 m, con lo que mi perímetro es 9.0 m Aplicación 5. Clase cuadrado y triangulo. Sólo quedan las clases Cuadrado y Triangulo, que derivan de la clase Poligono. Estas dos clases no tienen atributos nuevos y ni siquiera sobreescriben el método imprimirInformacion. Programa las dos clases. En cada una de ellas, añade un main que cree dos objetos. Los esqueletos de las clases son los siguientes: Programación orientada a objetos en Java 104 Unidad V. Herencia Código 5.9.6 Clase cuadrado. Código 5.9.7 clase triangulo. Aplicación 7. Creación de una jerarquía multinivel Se pueden construir jerarquías que contengan tantos niveles de herencia como se quiera. Es totalmente aceptable utilizar una subclase como una superclase de otra. Por ejemplo, en las clases A, B y C, C puede ser la subclase de B, que a su vez es una subclase de A. cuando se da esta situación, cada subclase hereda todos los rasgos de sus superclases. En este caso, C hereda todos los aspecto de B y A, para ver la utilidad de una jerarquía multinivel, observe el siguiente ejemplo: Programación orientada a objetos en Java 105 Unidad V. Herencia Programación orientada a objetos en Java 106 Unidad V. Herencia Programación orientada a objetos en Java 107 Unidad V. Herencia Código. 5.9.8 En las tres secciones de código anteriores, la subclase BoxPeso se utiliza como una superclase para crear la subclase llamada Transporte. transporte hereda todas las características de BoxPeso y Box, y añade un campo llamado costo, que contiene el coste del envió del paquete. La salida del programa es: El Volumen de transporte1 es 3000.0 El peso de transporte1 10.0 Costo de Transporte1: $3.41 El volumen del transporte2 es 24.0 El peso de transporte2 es 0.76 Costo de transporte2: $1.28 Gracias a la herencia, Transporte 52 puede utilizar las clases Box y BoxPeso definidas previamente, añadiendo sólo la información adicional que necesita para su aplicación especifica. La reutilización del código es una de las ventajas de la herencia. Este ejemplo pode de relieve otro aspecto importante: super() siempre hace referencia al constructor de la superclase más próxima. El método super() en Transporte llama al constructor de Box. En una jerarquía de clases, si el constructor de una superclase necesita parámetros, entonces todas las subclases han de pasar esos parámetros en línea ascendente. Esto es así tanto si la subclase necesita esos parámetros como si no. 52 Transporte. Programación orientada a objetos en Java 108 Unidad V. Herencia ORDEN DE EJECUCION DE LOS CONSTRUCTORES Cuando se crea una jerarquía ¿En qué orden se ejecutan los constructores de las clases que forman esa jerarquía? Por ejemplo, con una subclase llamada B y una superclase llamada A. ¿Se ejecutará el constructor A antes que el B, o a la inversa? La respuesta es que en la jerarquía de clases, los constructores se ejecutan en el orden en que se derivan, es decir, desde la superclase a la subclase. Además, dado que super() tiene que ser la primera sentencia que se ejecute en el constructor de una subclase, este orden es el mismo tanto si se usa super() como si no. En el caso de que no se utilice super(), se ejecutará el constructor por defecto o el constructor sin parámetros de cada superclase. El siguiente ejemplo muestra el momento en que se ejecutan los constructores: Código 5.9.9. Orden de ejecución de constructores en una jerarquía de clases. La salida de este programa es:: Dentro del constructor A Dentro del constructor B Dentro del constructor C Como se ve, los constructores se ejecutan en el orden en que se derivan. Esto es lógico, ya que una superclase no conoce sus subclases y cualquier inicialización que necesite realizar es independiente, y posiblemente un requisito previo para cualquier inicialización realizada por la subclase, por lo que se debe ejecutar en primer lugar. Programación orientada a objetos en Java 109 Unidad V. Herencia 5.10 Ejercicios propuestos. 5.10.1 Implementar una clase Automóvil (carro) dentro de una jerarquía de herencia. Considere que además de ser un vehiculo, un automóvil es también una comodidad, un símbolo de estado social, un modo de transporte etc. 5.10.2 Implementar una jerarquía de herencia de animales tal que contenga al menos seis niveles de derivación y doce clases. 5.10.3 Deducir las clases necesarias para diseñar un programa de ordenador que permita jugar a diferentes juegos de cartas. 5.10.4 Confeccionar una clase Persona que tenga como atributos el nombre y la edad. Definir como responsabilidades un método que cargue los datos personales y otro que los imprima. Plantear una segunda clase Empleado que herede de la clase Persona. Añadir un atributo sueldo y los métodos de cargar el sueldo e imprimir su sueldo. Definir un objeto de la clase Persona y llamar a sus métodos. También crear un objeto de la clase Empleado y llamar a sus métodos. 5.10.5 Elaborar una jerarquía de herencia que modele los seres vivos capaces de hablar. Las clases deben modelar al menos a los loros, los profesores y los alumnos. Todas las clases elaboradas deben disponer de un método habla sin argumentos que proporcione una salida por pantalla similar a la siguiente: Hola, me llamo Pedro y se hablar. Soy racional. Tengo 40 años. Nací el 1 de enero de 1965 Soy profesor. Para que el ejercicio sea interesante es necesario que todos los objetos habladores tengan un conjunto de características que les diferencian de los demás, por ejemplo, que los loros no sean conscientes de su edad o su fecha de nacimiento. 5.10.6 Los loros del ejercicio anterior no pueden ser universitarios simultáneamente, pero un profesor puede ser también alumno. Elaborar un conjunto de clases que permitan modelar esta situación de forma que un objeto pueda cambiar su forma de hablar en tiempo de ejecución en función de la recepción de algún mensaje adecuado. 5.10.7 Escribir un programa que lea del dispositivo estándar de entrada los datos para crear una lista de personas: a) general; b) estudiantes; c) empleados; d) estudiantes empleados. El programa debe permitir ordenar alfabéticamente por el primer apellido. 5.10.8 implementar una jerarquía Librería que tenga al menos una docena de clases. Considérense una librería que tenga colecciones de libros de literatura, humanidades, tecnología, etc. Programación orientada a objetos en Java 110 Unidad V. Herencia 5.10.9 Implementar una jerarquía de tipos datos númericos que extienda los tipos de datos fundamentales tales como int y float, disponibles en Java. Las clases a diseñar pueden ser complejo, vector, matriz, etc. Programación orientada a objetos en Java 111 Unidad VI. Polimorfismo UNIDAD VI. POLIMORFISMO Y REUTILIZACION Concepto de Polimorfismo, Clases Abstractas, Definición e Implementación de una Interfaz, Definición y creación de Paquetes. Programación orientada a objetos en Java 112 Unidad VI. Polimorfismo UNIDAD VI. POLIMORFISMO 6.1 Concepto de polimorfismo El concepto de Polimorfismo es uno de los fundamentos para cualquier lenguaje orientado a Objetos, las mismas raíces de la palabra pueden ser una fuerte pista de su significado: Poli = Multiple, morfismo= Formas , esto implica que un mismo Objeto puede tomar diversas formas. A través del concepto de Herencias 53 es posible ilustrar este comportamiento: Figura 10: Ilustración del polimorfismo. El poder manipular un Objeto como si éste fuera de un tipo genérico otorga mayor flexibilidad al momento de programar con Objetos, el término Polimorfismo también es asociado con un concepto llamado Late-Binding54, obsérvese el siguiente fragmento de código: Figura a = new Circulo(); Figura b = new Triangulo(); Inicialmente se puede pensar que este código generaría un error debido a que el tipo de referencia es distinta a la instancia del objeto, sin embargo, el fragmento anterior es correcto y demuestra el concepto de Polimorfismo. El polimorfismo permite “programar en forma general”, en vez de “programar en forma específica”. En especial, el polimorfismo nos permite escribir programas que procesen 53 54 Inheritance. Ligamiento Tardío. Programación orientada a objetos en Java 113 Unidad VI. Polimorfismo objetos de clases que formen parte de la misma jerarquía de clases, como si todos fueran objetos de sus superclases. El polimorfismo en java consiste en declarar y definir varios métodos con el mismo nombre, pero con diferente número y/o tipo de argumentos y que realizan diferentes operaciones. Sin embargo, el tipo que devuelven los métodos debe coincidir. Java es un lenguaje totalmente orientado a objetos, y como tal podemos utilizar las propiedades que este tipo de programación ofrece. Una de estas propiedades es el polimorfismo, pero ¿Qué es el polimorfismo?. La palabra polimorfismo significa múltiples formas y es la propiedad que tiene un lenguaje de programación de que una clase (conjunto de objetos con características en común), pueda tener diferentes comportamientos. Imagínese que es responsable de programar un editor de facturas, el cual tendrá dos salidas, una es el envió de la factura por fax y la otra es la impresión de la factura. Si el programa encargado enviar faxes y el de impresión ya están elaborados, y solo pueden ser utilizados por documentos que puedan ser faxeados y documentos que puedan ser impresos respectivamente, como se puede adicionarlos al editor para faxear e imprimir las facturas. Para el problema descrito anteriormente una buena opción es aplicar polimorfismo, para que la clase Factura pueda tener el comportamiento adicional que necesita, es decir, pueda ser de tipo documento faxeable o documento imprimible y los módulos de envió de fax e impresión se utilicen sin hacerles ningún cambio. La figura 11 muestra como la clase cliente Factura hace uso de los servicios de la clase Fax e Impresora implementando las interfaces DocumentoFaxeable y DocumentoImprimible respectivamente, por lo tanto la clase Factura se puede comportar como un objeto de la clase DocumentoFaxeable y/o DocumentoImprimible, y son estos tipos de objetos los únicos que son aceptados por las clases Impresora y Fax. Figura 11: Polimorfismo y reutilización Programación orientada a objetos en Java 114 Unidad VI. Polimorfismo Existe otra forma de utilizar polimorfismo sin tener que implementar un contrato, es por medio de herencia, la herencia permite que una clase padre “herede” sus características a una clase hija, y establece que clase hija será del tipo de la clase padre y tendrá sus características55 y comportamientos56. Una de las utilidades de la herencia es que se puede crear una clase General (propiedades y características en común) y clases hijas que solo modificarán la parte que ellas requieren, permitiendo así que tengan el comportamiento de la clase padre con sus propias características, y como valor agregado la reutilización de código. Supóngase que ahora se esta encargado de programar la parte de administración de clientes de una aplicación de ventas, y se sabe que existen dos tipos de clientes candidatos a clases, el cliente que tiene las características de un cliente común, y el cliente preferencial que tiene todas las propiedades del cliente común pero además se le aplican descuentos, ¿Es necesario programar cada tipo de cliente por separado? ¿Si aparece otro tipo de cliente se programará desde cero?....No. Utilizando herencia la clase Cliente puede ser nuestra clase padre y la clase ClientePreferencial la clase hija de Cliente que en cada compra se le aplicará un descuento y si apareciera otro tipo de cliente, solo heredaría de Cliente y modificaríamos la parte que lo hace diferente. La figura 12 muestra como a través de herencia la clase Cliente (clase padre) hereda sus características a la clase ClientePreferencial (clase hija), y esta modifica un método para adicionar su propia implementación. Figura 12 Ilustración de la herencia 55 56 Propiedades. Métodos. Programación orientada a objetos en Java 115 Unidad VI. Polimorfismo Ahora se sabe, que la próxima vez que se tenga la necesidad de crear programas donde las clases tengan diferentes comportamientos para realizar diferentes acciones, donde sea indispensable crear un conjunto de clases que compartirán una serie de características y además se demande la reutilización de código. Una buena opción en un lenguaje orientado a objetos, es la utilización del polimorfismo. Ejemplo: Una sintaxis algo genérica puede ser la que sigue: Código 6.1.1. Ejemplo de polimorfismo 6.2 Clases abstractas 6.2.1 Definición Una de las características más útiles de cualquier lenguaje orientado a objetos es la posibilidad de declarar clases que definen como se utiliza solamente, sin tener que implementar método. Esto en Java se hace mediante interfaces y con clases abstractas. Una clase abstracta es una clase de la que no se puede crear objetos. La utilidad de estas clases estriba en que otras clases hereden de ésta, por lo que con ello se conseguirá reutilizar código. Para declarar una clase como abstracta se utiliza la palabra clave abstract. Los métodos para los que no aporte una implementación serán declarados a su vez abstractos. Si una clase tiene un método abstract es obligatorio que la clase sea abstract. Todas las subclases que hereden de una clase abstracta tendrán que redefinir los métodos abstractos dándoles una implementación. En el caso de que no implementen alguno de esos métodos la clase hija también será abstracta y tendrá que declararse como tal (tanto la clase como los métodos que siguen siendo abstractos). En método abstract no puede ser static, ya que estos no pueden ser redefinidos por las subclases. Programación orientada a objetos en Java 116 Unidad VI. Polimorfismo Hay ocasiones, cuando se desarrolla una jerarquía de clases en que algún comportamiento está presente en todas ellas pero se materializa de forma distinta para cada una. Por ejemplo, supóngase una estructura de clases para manipular figuras geométricas. Se podría pensar en tener una clase genérica, que podría llamarse FiguraGeometrica y una serie de clases que extienden a la anterior que podrían ser Circulo, Poligono, etc. Podría haber un método dibujar dado que sobre todas las figuras puede llevarse a cabo esta acción, pero las operaciones concretas para llevarla a cabo dependen del tipo de figura en concreto (de su clase). Por otra parte la acción dibujar no tiene sentido para la clase genérica FiguraGeometrica, porque esta clase representa una abstracción del conjunto de figuras posibles. Código 6.2.1.1. Ilustración de clases abstractas. Para resolver esta problemática Java proporciona las clases y métodos abstractos. Un método abstracto es un método declarado en una clase para el cual esa clase no proporciona la implementación (el código). Una clase abstracta es una clase que tiene al menos un método abstracto. Una clase que extiende a una clase abstracta debe implementar los métodos abstractos (escribir el código) o bien volverlos a declarar como abstractos, con lo que ella misma se convierte también en clase abstracta. Caracteristicas de las clases Abstractas en Java a) Una clase que tiene al menos un método abstracto, es una clase abstracta. b) Para declarar un método o una clase abstracta se usa la palabra abstract. c) Cuando en una clase derivada no se (re)define un método abstracto mediante una implementación, el método continúa siéndolo en la clase derivada. d) Se exige que todas las clases abstractas sean declaradas como tales. e) No se permite la creación de objetos de clases abstractas. Como ya se dijo anteriormente para declarar una clase o un método como abstractos, se utiliza la palabra reservada abstract. Programación orientada a objetos en Java 117 Unidad VI. Polimorfismo Una clase abstracta no se puede instanciar57 pero si se puede heredar y las clases hijas serán las encargadas de agregar la funcionalidad a los métodos abstractos. Si no lo hacen así, las clases hijas deben ser también abstractas. Ejemplo Se diseñará una clase con un método abstracto f(double x) que representa una función de parámetro x cualquiera. Se observa que por lógica esta función se debe implementar en alguna clase derivada para que tenga sentido. Además de este método abstracto, se implementara un método llamado evaluar que llama al método f, retornando su valor. Código 6.2.1.2. Clases abstractas. Con esto se puede comprobar que no se puede crear un objeto de una clase abstracta. Ejemplo : Funcion func = new Funcion( ); double v = func.evaluar(0.6); /* error logico!! */ 6.2.2 Redefinición La redefinición de una clase abstracta consiste en extenderla y redefinir sus miembros para dicha clase puede ser utilizada y generar resultados. Continuando con el ejemplo anterior: Ahora se extenderá la funcionalidad de clase abstracta Funcion creando la clase derivada FuncionExp que representa a la función exponencial. En esta clase se implementa el método abstracto f. 57 No se pueden crear objetos directamente de ella. Programación orientada a objetos en Java 118 Unidad VI. Polimorfismo Código 6.2.2.1. Implementación de un método abstracto. Usando las características de herencia, se tiene el siguiente ejemplo : Código 6.2.2.2 Llamado a métodos implementados de clases abstracta. Al igual que la clase FuncionExp, se crea a continuación una clase que representa a una función lineal, y se redefinen los métodos. Código 6.2.2.3 Redefinición de métodos. Y a continuación se crea un programa de prueba para los ejemplos anteriores: Programación orientada a objetos en Java 119 Unidad VI. Polimorfismo Programa de prueba Código 6.2.2.4 Programa de prueba para los dos ejemplos anteriores. 6.3 Definición de una Interfaz Las interfaces Java son expresiones puras de diseño. Se trata de auténticas conceptualizaciones no implementadas que sirven de guía para definir un determinado concepto (clase) y lo que debe hacer, pero sin desarrollar un mecanismo de solución58. Se trata de declarar métodos abstractos y constantes que posteriormente puedan ser implementados de diferentes maneras según las necesidades de un programa. En otras palabras, una interfaz es un conjunto de declaraciones de funciones. Si una clase implementa una interfaz, debe definir todas las funciones especificadas por la interfaz. Las interfaces pueden definir también variables finales (constantes). Una clase puede implementar más de una interfaz, representando una alternativa a la herencia múltiple. En algunos aspectos los nombres de las interfaces pueden utilizarse en lugar de las clases. Por ejemplo, las interfaces sirven para definir referencias a cualquier objeto de cualquiera de las clases que implementan esa interfaz. Con ese nombre o referencia, sin embargo, sólo se pueden utilizar los métodos de la interfaz. Éste es un aspecto importante del polimorfismo. Una interfaz puede derivar de otra o incluso de varias interfaces, en cuyo caso incorpora las declaraciones de todos los métodos de las interfaces de las que deriva (a diferencia de las clases, las interfaces de Java sí tienen herencia múltiple). Utilizando la palabra clave interfaz, se puede abstraer completamente la interfaz de una clase de su implementación. Es decir, con interfaz se puede especificar lo que una clase debe hacer, pero no cómo ha de hacerlo. Las interfaces tienen una sintaxis parecida a la de las clases, pero les faltan las variables de instancia, y sus métodos se declaran sin cuerpo. En la práctica, esto significa que se pueden definir las interfaces que no hagan 58 Es la alternativa que Java otorga a la herencia múltiple. Programación orientada a objetos en Java 120 Unidad VI. Polimorfismo suposiciones sobre cómo se implementan. Una vez que está definida, cualquier número de clases puede implementar una interfaz. Asimismo, una clase puede implementar cualquier número de interfaces. Para implementar una interfaz, una clase ha de crear el conjunto completo de métodos definidos por la interfaz. Sin embargo, cada clase tiene libertad de determinar los detalles de su propia implementación. Con la palabra clave interfaz Java permite la utilización completa del aspecto del polimorfismo, “una interfaz, múltiples métodos”. Las interfaces están diseñadas para dar soporte a la resolución dinámica de métodos durante la ejecución. Normalmente, para que un método de una clase pueda ser llamado desde otra clase, es necesario que las dos clases estén presentes durante la compilación para que el compilador java pueda comprobar que el formato de los métodos es compatible. Este requisito da lugar por si sólo a un entorno de clase no estático y extensible. Inevitablemente, en un sistema así, las clases, de forma que los mecanismos estarán disponibles para más y más subclases. Las interfaces, desconectando la definición de un método o de un conjunto de métodos de la herencia jerárquica, eliminan ese problema. Como las interfaces tienen una jerarquía distinta de la de las clases, es posible que clases que no están relacionadas en términos de jerarquía implementen la misma interfaz, lo que pone de manifiesto la verdadero potencia de las interfaces. 6.4 Implementación de la Definición de una Interfaz Una interfaz se define como una clase. La forma general de definir una interfaz es ésta: Código 6.4.1 Implementación de la de definición de una interfaz. En este ejemplo el acceso o es público o no se usa. Cuando no se incluye el especificador de acceso, se utiliza el acceso por defecto, y la interfaz está disponible sólo para otros miembros del paquete en que se declara. Cuando se declara como public, la interfaz puede utilizarse por otro código. Nombre es el nombre de la interfaz, y debe ser un identificador válido. Obsérvese que los métodos que se declaran no tienen cuerpos y terminan con un punto y coma después de la lista de parámetros. Son esencialmente métodos abstractos, ya que no puede haber implementación por defecto de un método Programación orientada a objetos en Java 121 Unidad VI. Polimorfismo declarado dentro de una interfaz. Cada clase que incluya una interfaz ha de implementar todos los métodos. Las variables pueden declararse dentro de las declaraciones de la interfaz y son, implícitamente, final y static: esto significa que no pueden ser alteradas por la implementación de la clase y que deben inicializarse con un valor constante. Todos los métodos y variables son implícitamente public si la interfaz se declara public. Un ejemplo de definición de una interfaz sencilla, que contiene un método llamado callback() que toma un solo parámetro entero. interface Callback( void callback(int param); } Implementación de una Interfaz Una vez definida una interfaz una o más clases pueden implementarse. Para implementar una interfaz, se ha de incluir la cláusula implements en una definición de clase, y luego crear los métodos definidos por la interfaz. La forma general de una clase que incluye la cláusula implements es esta: Código 6.4.2 Implementación de una interfaz. El acceso es public o no se usa. Si una clase implementa más de una interfaz, las interfaces se separan con comas. Si una clase implementa dos interfaces, entonces los clientes de las dos interfaces deberán usar el mismo método. Los métodos que implementan una interfaz deben declararse como public. Además, el formato del método implementado debe coincidir exactamente con el formato especificado en la definición de la interfaz. A continuación, un pequeño ejemplo de una clase que implementa la interfaz DevolverLllamada.: Código 6.4.3 Implementación de la interfaz DevolverLlamada. Programación orientada a objetos en Java 122 Unidad VI. Polimorfismo Obsérve que DevolverLlamada() se declara por medio del especificador de acceso public. Es habitual y además permitido que las clases que implementen interfaces definan miembros adicionales propios. Por ejemplo, la siguiente versión de Cliente implementa DevolverLlamada y añade el método NoMetododeInterfaz( ); Código 6.4.4 Definición de miembros adicionales en clases que implementan interfaces. 6.5 Reutilización de la definición de una Interfaz Se pueden declarar variables como referencias a objetos que usan una interfaz en lugar de un tipo de clases. Se puede hacer referencia a cualquier instancia de cualquier clase que implementa una interfaz declarada por medio de tales variables. Cuando se llama a un método por medio de estas referencias se llamará a la versión correcta que se basa en la instancia actual de la interfaz que esta siendo referenciada. Está es una de las característica clave de las interfaces. El método que se va ejecutar se determina dinámicamente durante la ejecución y se permite que las clases en las que se encuentra ducho método se creen después del código llamante, que puede seleccionar una interfaz sin tener ningún conocimiento sobre el método al que se ha llamado. Este proceso es similar al que se seguía cuando se utilizaba una referencia de una superclase para acceder a un objeto de una subclase. El siguiente ejemplo llama al método DevuelveLlamada() por medio de una variable de referencia: Código 6.5.1. Llamada al método DevuelveLlamada() por medio de una variable de referencia. La salida es: Programación orientada a objetos en Java 123 Unidad VI. Polimorfismo DevuelveLlamada llamada con 42 Observe que la variable c se ha declarado como el tipo de la interfaz DevuelveLlamada, aunque se le ha asignado una instancia de Clienet. A pesar de que c se puede utilizar para acceder al método DevuelveLlamada (), no sirve para acceder a otros miembros de la clase Cliente. Una variable de referencia a una interfaz solo tiene conocimiento de los métodos que figuran en la declaración de su interfaz. Por lo tanto, c no podría utilizarse para acceder al método NoMetododeInterfaz() debido a que está definido por Cliente, pero no por DevuelveLlamada. El ejemplo anterior mostraba, de forma mecánica, como una variable de referencia a una interfaz puede acceder a la implementación de un objeto, pero no demostraba la potencia del polimorfismo. Como ejemplo se creará una segunda implementación de DevuelveLlamada: Ahora, se probará con la siguiente clase: Código 6.5.2 Demostración del potencial del polimorfismo implementando la clase OtroCliente. La salida del programa es la siguiente: DevuelveLlamada llamado con 42 otra version de DevuelveLlamada Programación orientada a objetos en Java 124 Unidad VI. Polimorfismo p al cuadrado es 1764 Como se observa la versión del método DevuelveLlamada() al que se ha llamado está determinada por el tipo de objeto al que c hace referencia durante la ejecución. Aunque el ejemplo que ahora se muestra es demasiado sencillo, enseguida se muestra otro más práctico. Implementación parcial Si una clase incluye una interfaz pero no se implementa completamente los métodos definidos por esa interfaz, entonces debe ser declarada como abstract. Por ejemplo: Código 6.5.3. La implementación parcial de una interfaz la convierte en una clase abstracta. En este caso, la clase Incompleta no implementa el método DevuelveLlamada() y ha de ser declarada como abstracta. Cualquier clase que herede Incomplete ha de implementar DevuelveLlamada(), o bien declararse como abstract. Aplicación de las Interfaces Para comprender la potencia de las interfaces, véase ahora un ejemplo más práctico. Existen varias formas de implementar una pila 59, por ejemplo, la pila puede ser de tamaño fijo o de tamaño creciente. La pila también puede estar contenida dentro de una matriz, una lista, un árbol binario, etc. No importa de qué manera se implemente la pila, su interfaz permanece inalterable. Es decir, los métodos push()60y pop()61 definen la interfaz a la pila independientemente de los detalles de la implementación. Así, es fácil definir dicha interfaz y dejar para cada implementación los detalles más específicos. Véanse dos ejemplos. El primero de ellos presenta una interfaz que define una pila de enteros y se guarda en un archivo llamado pila.java. Esta interfaz se utilizará para las dos implementaciones de la pila. 59 60 61 Stack. Insertar un elemento a la pila. Eliminar un elemento de la pila. Programación orientada a objetos en Java 125 Unidad VI. Polimorfismo Código 6.5.4. Declaración de una interfase pila para almacenar y recuperar un elemento. El siguiente programa crea una clase llamada PilaFija y que implementa una versión de una pila de enteros de longitud fija: Programación orientada a objetos en Java 126 Unidad VI. Polimorfismo Código 6.5.3. Programa que crea una clase llamada PilaFija y que implementa una versión de una pila de enteros de longitud fija. A continuación se muestra otro ejemplo de la implementación de Pila que crea una pila dinámica utilizando la misma definición de la interfaz. En esta implementación, cada pila se construye partiendo de una longitud inicial. Si se sobrepasa esa longitud, se incrementa el tamaño de la pila. Cada vez que se necesite más espacio, se doblará el tamaño de la pila. Programación orientada a objetos en Java 127 Unidad VI. Polimorfismo Código 6.5.4. Programa que crea una clase llamada PilaDnamica y que implementa una versión de una pila de enteros de longitud dinámica. La siguiente clase utiliza las implementaciones PilaFija y PilaDinamica, por medio de una referencia a la interfaz. Esto significa que las llamadas a los métodos push() y pop() se resuelven durante el tiempo de la ejecución y no en el de compilación. Programación orientada a objetos en Java 128 Unidad VI. Polimorfismo Código 6.5.5. Implementación de PilaFija y PilaDinamica. En este programa, mipila es una referencia a la interfaz Pila. Por lo tanto, cuando se refiere a ds, utiliza las versiones de push() y pop() definidas por la implementación PilaDinamica. Cuando se refiere a fs, utilza las versiones de push() y pop() definidas por PilaFija. Como ya se ha explicado, estas decisiones se toman durante la ejecución. El acceso a múltiples implementaciones de una interfaz a través de una variable de referencia de la interfaz es la forma más eficaz de que dispone Java para lograr el polimorfismo en el tiempo de ejecución. 6.6 Definición y Creación de Paquetes Conforme empieza a crecer un desarrollo de software surge la necesidad de reutilizar ciertos componentes que ya han sido escritos, así como dar cierta estructura para mantener una organización de código; al igual que otros lenguajes, esta organización se lleva acabo mediante librerías, denominadas "packages" en el mundo Java . Java toma una perspectiva nueva respecto a otros lenguajes, adoptando una convención a partir de dominios en Internet, esto es, los programas y por ende librerías ("packages") estarán basados en el nombre de la empresa/dominio para la cual son diseñados, lo anterior permite evitar la tan conocida Colisión de Nombres en Software. Programación orientada a objetos en Java 129 Unidad VI. Polimorfismo Esta colisión de nombres se da cuando dos clases llevan el mismo nombre y ambas requieren ser utilizadas dentro de un programa en particular, empleando Dominios en Internet se garantiza que el nombre de la clase/librería sea única , puesto que es una característica de Dominios en Internet. Para que puedan ser asignadas clases a determinadas librerías es necesario indicar dicha Librería("Package") en la definición inicial de un programa, además de esto, es necesario crear una estructura de directorios para generar las determinadas Clases; lo anterior se ilustra mejor a través de un ejemplo: Creación de Librerías Estructura de Directorios para utilizar Librerías ("Paquetes") Para utilizar "paquetes" en Java es necesario generar una estructura de directorios que lleven la misma secuencia de las librerías que desean diseñarse, observe: +-com+| +-osmosislatina+| +-escritura+| +-graficas+| +-correo+| +-errores+| +-auxiliares+L o anterior demuestra una estructura para las librerías del dominio com.osmosislatina, se debe mencionar que no existe ninguna restricción en el número de directorios anidados, de tal forma, es posible que existan librerías de 5 o 10 niveles de profundidad, esta sub-clasificación se va realizando en base a los requerimientos de las librerías y es dentro de cada sub-directorio donde son colocadas las distintas Clases. Además de la generación de estos directorios, se debe incluir un calificador de la librería en el código fuente de cada Clase: package com.osmosislatina.escritura; Lo anterior sería incluido en todos los programas pertenecientes a la librería ("Package") com.osmosislatina.escritura, debe mencionarse que este calificador debe ser la primer declaración en todo programa; recuérdese que además de este calificador el programa debe estar colocado en la estructura de directorios correspondiente, lo anterior es sumamente importante al llevarse acabo la compilación para la generación de Byte-Code Programación orientada a objetos en Java 130 Unidad VI. Polimorfismo Código Fuente Pantalla.java La siguiente Clase define dos métodos que pueden ser utilizados para imprimir resultados a pantalla: La siguiente Clase debe ser colocada bajo la estructura de directorios com/osmosislatina/escritura, en base a su ambiente (Linux o Windows). Por favor genérese dicha estructura para colocar esta clase package com.osmosislatina.escritura; public class Pantalla { public static void sinSalto(String s) { System.out.print(s); } public static void conSalto(String s) { System.out.println(s); } } • • • • Primeramente se declara la librería ("package") a la que será asignada el programa a través del vocablo package. Se define la Clase con los respectivos métodos sinSalto y conSalto. NOTA: Recuerde que este programa debe residir dentro del directorio com/osmosislatina/escritura/. NOTA 2: Obsérvese que esta Clase no posee el método principal main. Para compilar esta Clase basta ejecutar el comando: $ javac Pantalla.java Lo anterior genera el archivo de ByteCode : Panatalla.class. En la siguiente sección será diseñado un programa que haga uso de esta librería ("package"). Programación orientada a objetos en Java 131 Unidad VI. Polimorfismo 6.7 Reutilización de las clases de un Paquete / Librería Para emplear librerías 62 en un programa es necesario importarlas, esto es llevado acabo mediante el calificativo import como se demuestra a continuación: import com.osmosislatina.graficas.Recuadro; La declaración anterior permite que la clase Recuadro de la librería com.osmosislatina.graficas este disponible en el programa en cuestión, las características del uso de import son las siguientes: Se debe declarar al inicio de un programa, antes de la definición de la clase, aunque posterior a la definición de package (si existiese) . • • Se pueden utilizar un número ilimitado de import. • Es posible importar todas las clases de una librería a través de un asterisco (*). Código Fuente MandaPantalla.java La siguiente Clase manda llamar métodos definidos en la Clase Pantalla.java. Código 6.7.1. Código Fuente MandaPantalla.java 62 • Primeramente se declara la librería a la que será asignada el programa a través del vocablo package. • Posteriormente se importan las clases que serán utilizadas en el programa a través del vocablo import. Conocidas como package. Programación orientada a objetos en Java 132 Unidad VI. Polimorfismo Se define el método main dentro del cual se genera una instancia de la case Pantalla (aquella importada) y sobre la que posteriormente se mandan llamar los distintos métodos a través de su referencia. • La compilación (generación de bite-code) de esta Clase requiere que, esta sea llevada a cabo bajo el directorio raíz de la estructura de Clases, esto es, aunque esta Clase radique dentro del directorio com/osmosislatina/auxiliares es necesario llevar acabo la compilación desde la raíz (./com), lo anterior se debe a que esta Clase requiere ubicar (importar) las clases de la librería com/osmosislatina/escritura; los detalles de esta rutina serán descritos en la sección de CLASSPATH. Para ejecutar este programa también se requiere descender al directorio raíz de la librería ("Package). El paquete (package) • • • Los paquetes son una forma de organizar grupos de clases. Un paquete contiene un conjunto de clases relacionadas bien por finalidad, por ámbito o por herencia. Los paquetes resuelven el problema del conflicto entre los nombres de las clases. Al crecer el número de clases crece la probabilidad de designar con el mismo nombre a dos clases diferentes. Las clases tienen ciertos privilegios de acceso a los miembros dato y a las funciones miembro de otras clases dentro de un mismo paquete. Cuando se crea un proyecto nuevo se crea en un subdirectorio que tiene el nombre del proyecto. A continuación, se crea la aplicación, un archivo .java que contiene el código de una clase cuyo nombre es el mismo que el del archivo. Se pueden agregar nuevas clases al proyecto, todas ellas contenidas en archivos .java situadas en el mismo subdirectorio. La primera sentencia que se encuentra en el código fuente de las distintas clases que forman el proyecto es package o del nombre del paquete. Programación orientada a objetos en Java 133 Unidad VI. Polimorfismo 6.8 Clases Genéricas (Plantillas) 63 Las clases plantilla, clases genéricas, o generadores de clases, son un artificio que permite definir una clase mediante uno o varios parámetros. Este mecanismo es capaz de generar la definición de clases (instancias o especializaciones de la plantilla) distintas, pero compartiendo un diseño común. Se puede imaginar que una clase genérica es un constructor de clases, que como tal, acepta determinados argumentos (no confundir con el constructor de-una-clase, que genera objetos). 6.9 Ejercicios Resueltos 6.9.1 Crear una clase abstracta denominada Animal de la cual se derivan las clases Gato y Perro. Ambas clases redefinen la función habla declarada abstracta en la clase base Animal. El polimorfismo permite pasar la referencia a un objeto de la clase Gato, Perro o Pajaro a una función hazleHablar que conoce al objeto por su clase base Animal. 63 Este mecanismo no es implementado por java Programación orientada a objetos en Java 134 Unidad VI. Polimorfismo El compilador no sabe exactamente que objeto se le pasará a la función hazleHablar en el momento de la ejecución del programa. Si se pasa un objeto de la clase Gato se imprimirá ¡Miau!, si se pasa un objeto de la clase Perro se imprimirá ¡Guau!. Y si se le pasa un objeto de la clase Pajaro imprimirá ¡pió! El compilador solamente sabe que se le pasará un objeto de alguna clase derivada de Animal. Por tanto, el compilador no sabe que función habla será llamada en el momento de la ejecución del programa. El polimorfismo ayuda a hacer el programa más flexible, por que en el futuro se pueden añadir nuevas clases derivadas de Animal, sin que cambie para nada el método hazleHablar. 6.9.2 Crear una interface denominado Parlanchin que contenga la declaración de una función denominada habla. Se desarrolla la jerarquía de clases que deriva de Animal implemente la interface Parlanchi. Programación orientada a objetos en Java 135 Unidad VI. Polimorfismo Ahora véase otra jerarquía de clases completamente distinta, la que deriva de la clase base Reloj. Una de las clases de dicha jerarquía Cucu implementa el interface Parlanchin y por tanto, debe de definir obligatoriamente la función habla declarada en dicho interface. Se define la función hazleHablar de modo que conozca al objeto que se le pasa no por una clase base, sino por el interface Parlanchin. A dicha función se le puede pasar cualquier objeto que implemente el interface Parlanchin, este o no en la misma jerarquía de clases. Programación orientada a objetos en Java 136 Unidad VI. Polimorfismo Al ejecutar el programa, se vera que se imprime en la consola ¡Miau!, por que a la función hazleHablar se le pasa un objeto de la clase Gato, y después ¡Cucu, cucu, ..! por que a la función hazleHablar se le pasa un objeto de la clase Cucu. Si solamente hubiese herencia simple, Cucu tendría que derivar de la clase Animal (lo que no es lógico) o bien no se podría pasar a la función hazleHablar. Con interfaces, cualquier clase en cualquier familia puede implementar el interface Parlanchin, y se podrá pasar un objeto de dicha clase a la función hazleHablar. Esta es la razón por la cual los interfaces proporcionan más polimorfismo que el que se puede obtener de una simple jerarquía de clases. 6.9.3 Implemente una llamada Forma de la cual se deriven la clase Círculo, la clase Cuadrado y la Clase Triangulo. Implemente el método de Dibujar y el Método de borrar en cada una. Utilice polimorfismo. Programación orientada a objetos en Java 137 Unidad VI. Polimorfismo Programación orientada a objetos en Java 138 Unidad VI. Polimorfismo 6.10 Ejercicios Propuestos 6.10.1 Modificar el ejercicio 6.9.3 en el apartado de problemas resueltos, creando una nueva clase denominada pentágono que herede de Forma y que sobrecargue los métodos dibujar y borrar. Compilar y ejecutar. 6.10.2 Crear una interfaz llamado Manejo que contenga los métodos dibujar y borrar y modificar el ejercicio anterior para que la clase Forma implemente el interfaz manejo. 6.10.3 Implementar una clase que se llame Perro y que tenga un método sobrecargado llamado ladrido. Sobrecárgalo de tal manera que el tipo de parámetro determine el tipo de ladrido. Exteriorizar el tipo de ladrido con un mensaje. Dentro del programa principal llamar al método ladrido con diferentes parámetros. 6.10.4 Implementar una jerarquía Empleado de cualquier tipo de empresa que le sea familiar. La jerarquía debe tener al menos cuatro niveles, con herencia de miembros de dato, y métodos. Los métodos deben poder calcular salarios, despidos, promoción, dar de alta jubilación etc. Los métodos también deben poder calcula aumentos salariales y primas para Empleados de acuerdo con su categoría y productividad. La jerarquía de herencia debe poder ser utilizada para proporcionar diferentes tipos de acceso a Empleados. 6.10.5 Se requiere realizar una aplicación para que cada profesor de la universidad gestione las fichas de sus alumnos. Un profesor puede impartir una o varias asignaturas y dentro de cada asignatura puede tener distintos grupos de alumnos. Los alumnos pueden ser presénciales o a distancia. Al comenzar las clases, se entrega al profesor un listado con los alumnos por cada asignatura. Escribir un programa de tal forma que el listado de alumnos se introduzca por el teclado y se den de alta calificaciones de exámenes y prácticas realizadas. Se podrán obtener listados de calificaciones una vez realizados los exámenes y porcentajes de aprobados. 6.10.6 Implementar un programa que utilizando polimorfismo implemente la forma de hablar de un ladrón, un ingeniero, un político y un abogado. 6.10.7 Implementar que imprima los diferentes sonidos de los instrumentos musicales: Guitarra, saxogon, piano, Guzla y Ukele. Asígnele una frase a cada instrumento que represente el sonido. Utilice polimorfismo. Programación orientada a objetos en Java 139 Unidad VII. Excepciones UNIDAD VII. EXCEPCIONES Definición, Gestión, Tipos, Manejo y Lanzamiento de Excepciones Definidas por el Lenguaje y Definidas por el Usuario Programación orientada a objetos en Java 140 Unidad VII. Excepciones UNIDAD VII. EXCEPCIONES 7.1 Definición 7.1.1 ¿Qué son las Excepciones? Una excepción es la indicación de un problema que ocurre durante la ejecución de un programa. El nombre “excepción” viene del hecho del que, aunque puede ocurrir un problema, este ocurre con poca frecuencia, si la “regla” es que una instrucción generalmente se ejecuta en forma correcta, entonces la “excepción a la regla” es cuando ocurre un problema. El manejo de excepciones permite a los programadores crear aplicaciones que puedan resolver (o manejar) las excepciones. En muchos casos, el manejo de una excepción permite que el programa continúe su ejecución como si no se hubiera encontrado el problema. Un problema más grave podría evitar que el programa continuara su ejecución normal en vez de requerir al programa que notifique al usuario sobre el problema antes de terminar de una manera controlada. A diferencia de otros lenguajes de programación orientados a objetos como C/C++, Java incorpora en el propio lenguaje la gestión de errores. El mejor momento para detectar los errores es durante la compilación. Sin embargo prácticamente sólo los errores de sintaxis son detectados durante este periodo. El resto de problemas surgen durante la ejecución de los programas. En el lenguaje Java, una Exception es un cierto tipo de error o una condición anormal que se ha producido durante la ejecución de un programa. Algunas excepciones son fatales y provocan que se deba finalizar la ejecución del programa. Algunos ejemplos de datos anormales son; Intentar manejar archivos que no existen, accesos no legales en arrays y operaciones aritméticas ilegales como una división por cero. Cuando surge una condición excepcional, se crea un objeto que representa esta excepción y se lanza método que ha causado el error. Este método puede escoger entre gestionar el mismo la excepción o pasarla. De cualquiera de las dos maneras, en un punto determinado, se cazará la excepción y se procesará. Las excepciones pueden generarse por el intérprete de Java o de forma manual por el propio código. Normalmente, las excepciones generadas por Java están relacionadas con errores fundamentales que violan las reglas del lenguaje Java o las restricciones del entorno de ejecución Java. Las excepciones generadas de forma manual se utilizan, generalmente, para informar acerca de alguna condición de error en la parte del código que llama al método. En todos los casos donde surgen excepciones es recomendable terminar ordenadamente y dar un mensaje explicando el tipo de error que se ha producido. Otras, como por ejemplo no encontrar un fichero en el que hay que leer o escribir algo, pueden ser recuperables. En este caso el programa debe dar al usuario la oportunidad de corregir el error(indicando una nueva localización del archivo no encontrado). Un buen programa debe gestionar correctamente todas o la mayor parte de los errores que se pueden producir. Programación orientada a objetos en Java 141 Unidad VII. Excepciones 7.1.2 Clases de Excepciones Predefinidas por el Lenguaje Todos los tipos de excepciones son subclases de la clase Throwable64, incorporada en Java. Por lo tanto, Throwable ocupa el primer puesto en la jerarquía de clases de excepciones. Inmediatamente después se encuentran dos subclases que dividen las excepciones en dos grupos. Uno de estos grupos están encabezados por Exception, esta clase se utiliza para las condiciones excepcionales que los usuarios de los programas deben capturar, también es la clase de la que derivan las subclases necesarias para crear los tipos propios de las excepciones, existe una importante subclase de Exception, llamada RuntimeException. Las excepciones de este tipo se dignen se definen de forma automática por los programas que se escriben e incluyen aspectos como la división por cero, la utilización de un índice un arreglo no válido. El otro grupo esta encabezado por Error, que define las excepciones que no se espera que el programa pueda capturar en circunstancias normales. En intérprete Java utiliza las excepciones de tipo Error para indicar los errores relacionados con el momento de la ejecución, el desbordamiento de la pila es un ejemplo de este tipo de error. Las excepciones predefinidas por la implementación actual del lenguaje Java y su jerarquía interna de clases son las que se representan en el esquema de la figura 13 que aparece a continuación: Figura 13 Jerarquía de las clases de excepciones. 64 La clase Throwable es la clase base de las excepciones. Programación orientada a objetos en Java 142 Unidad VII. Excepciones Los nombres de las excepciones indican la condición de error que representan. Las siguientes son las excepciones predefinidas más frecuentes que se pueden encontrar: ArithmeticException Las excepciones aritméticas son típicamente el resultado de división por 0: int i = 12 / 0; NullPointerException Se produce cuando se intenta acceder a una variable o método antes de ser definido: class Hola extends Applet { Image img; paint( Graphics g ) { g.drawImage( img,25,25,this ); } } IncompatibleClassChangeException El intento de cambiar una clase afectada por referencias en otros objetos, específicamente cuando esos objetos todavía no han sido recompilados. ClassCastException El intento de convertir un objeto a otra clase que no es válida. y = (Prueba)x; // donde x no es de tipo Prueba NegativeArraySizeException Puede ocurrir si hay un error aritmético al cambiar el tamaño de un array. OutOfMemoryException ¡No debería producirse nunca! El intento de crear un objeto con el operador new ha fallado por falta de memoria. Y siempre tendría que haber memoria suficiente porque el garbage collector se encarga de proporcionarla al ir liberando objetos que no se usan y devolviendo memoria al sistema. NoClassDefFoundException Se referenció una clase que el sistema es incapaz de encontrar. ArrayIndexOutOfBoundsException Programación orientada a objetos en Java 143 Unidad VII. Excepciones Es la excepción que más frecuentemente se produce. Se genera al intentar acceder a un elemento de un array más allá de los límites definidos inicialmente para ese array. UnsatisfiedLinkException Se hizo el intento de acceder a un método nativo que no existe. Aquí no existe un método y se llama a a.kk(), cuando debería llamar a A.kk(). InternalException Este error se reserva para eventos que no deberían ocurrir. Por definición, el usuario nunca debería ver este error y esta excepción no debería lanzarse. El compilador Java obliga al programador a proporcionar el código de manejo o control de algunas de las excepciones predefinidas por el lenguaje. Por ejemplo, el siguiente programa, no compilará porque no se captura la excepción InterruptedException que puede lanzar el método sleep() 65. Código 7.1.2.1 En el ejemplo no se captura la Excepción InterruptedException, por lo tanto no se compilará. Este es un programa muy simple, que al intentar compilar, producirá el siguiente error de compilación que se visualizará en la pantalla tal como se reproduce a continuación: 65 Dormir. Programación orientada a objetos en Java 144 Unidad VII. Excepciones % Javac Java701.java Java701.java:41: Excepcion java.lang.InterruptedException debe ser tomada, o debe ser debe ser declarada en la cláusula throws de este método. Thread.currentThread().sleep( 1000 ); // currentThread() genera Como no se ha previsto la captura de la excepción, el programa no compila. El error identifica la llamada al método sleep() como origen del problema. Así que, la siguiente versión del programa, Java903.java, soluciona el problema generado por esta llamada. Código 7.1.2.1 En este programa se resuelve el problema del código anterior, se implementa la excepción por lo tanto ya se compilará. Lo único que se ha hecho es indicar al compilador que el método miMetodo() puede lanzar excepciones de tipo InterruptedException. Con ello se consigue propagar las excepción que genera el método sleep() al nivel siguiente de la jerarquía de clases. Es decir, en realidad no se resuelve el problema sino que se está pasando a otro método para que lo resuelva él. En el método main() se proporciona la estructura que resuelve el problema de compilación, aunque no haga nada, por el momento. Esta estructura consta de un bloque try y un bloque catch, que se puede interpretar como que intentará ejecutar el código del bloque try y si hubiese una nueva excepción del tipo que indica el bloque catch, se ejecutaría el código de este bloque. La transferencia de control al bloque catch no es una llamada a un método, es una transferencia incondicional, es decir, no hay un retorno de un bloque catch. Programación orientada a objetos en Java 145 Unidad VII. Excepciones 7.1.3 Propagación de Excepciones Cuando un método lanza una excepción, quiere decir que detecta que se ha producido una situación anómala, pero no sabe que hacer con ella. En ese caso, el método lanza la excepción y termina, propagando la excepción al método que le llamó, que quizá sepa qué hacer en ese caso. Las excepciones se propagan por todos los métodos de la pila de llamadas, hasta que se encuentre un método que la gestione. Código 7.1.3.1 Propagación de excepciones. La propagación de excepciones se da como lo muestra la figura 14. Figura 14: Propagación de excepciones. Programación orientada a objetos en Java 146 Unidad VII. Excepciones main (de PedirNumero) readLine (de BufferedReader) Lanza IOException Se produce la excepción Termina la ejecución Se relanza (se podría haber capturado) 7.2 Gestión de Excepciones Excepciones No Capturadas Antes de aprender a gestionar las excepciones en el programa, será útil ver que ocurre cuando no se gestiona en absoluto. Este programita incluye una excepción que, de forma intencionada provoca un error debido al la división entre ceo. Código 7.2.1 Se provoca una excepción intencional debido a la división entre cero. Cuando el intérprete Java detecta un intento de división entre cero, construye un nuevo objeto de excepción y luego lanza esa excepción, esto provoca que la ejecución de Exc0 se detenga. Porque una vez que la excepción se ha lanzado, ha de cazarse con un gestor de excepciones y tratarse de forma inmediata., en este ejemplo no se han proporcionado gestores de excepciones, de manera que la excepción se ha capturado por el gestor por defecto que proporciona el intérprete Java, cualquier excepción que no el programa cace, será, en última instancia, procesada por el gestor por defecto, que representa un mensaje que describe la excepción, imprime el trazado de la pila y concluye el programa. Aquí se tiene la excepción que se genera cuando el programa se ejecuta. Java.lang.ArithmeticException: //por cero en Exc0.main(Exc0.java 4) Obsérvese como el nombre de clase, Exc0; el nombre de método, main; el nombre del archivo, exc0.java; y el número de línea 4, se incluye en el trazado simple de la pila. Obsérvese también, que el tipo de excepción lanzada, denominada ArithmeticException, describe de manera más específica el tipo de error que se ha producido. Java proporciona bastantes tipos de excepciones que se ajustan a las distintas clases de errores de momento de ejecución que se pueden generar. El trazado de la pila siempre mostrará la secuencia de llamadas a métodos que preceden al error. Por ejemplo, aquí se presenta otra versión del programa anterior que introduce el mismo error pero en un método distinto de main(): Programación orientada a objetos en Java 147 Unidad VII. Excepciones El trazado de la pila que resulta del gestor de excepciones por defecto muestra la pila de llamadas completa: java.lang.ArithmeticException: / by zero at Exc1.subroutine(Exc1.java:4) at Exc1.main(exc1.java:7) Como puede verse, el final de la pila es línea 7 de main, que es donde se llama al método subroutine(), que provoca la excepción en la línea 4. La pila de llamadas es muy útil para la depuración porque muestra la secuencia exacta de pasos que condujo al error. 7.2.1 Manejo de Excepciones La lógica del programa evalúa frecuentemente condiciones que determinan cómo debe proceder la ejecución del programa. Considérese el siguiente seudocódigo. Figura 15 Manejo de Excepciones. En este seudocódigo se empieza realizando una tarea; después, se evalúa si esa tarea se ejecutó correctamente. Si no lo hizo, se realiza el procesamiento de los errores. De otra manera, se continúa con la siguiente tarea. Aunque esta forma de manejo de errores funciona, al entremezclar la lógica del programa con la lógica del manejo de errores podría ser difícil de leer, mantener y depurar; especialmente en aplicaciones extensas. De hecho si los problemas potenciales ocurren con poca frecuencia, al entremezclar la lógica del Programación orientada a objetos en Java 148 Unidad VII. Excepciones programa y la lógica del manejo de errores se puede degradar el rendimiento del programa, ya que este debe evaluar la lógica del manejo de errores para determinar si se puede llevar acabo la siguiente tarea. El manejo de excepciones permite al programador remover el código para el manejo de errores de la “línea principal” de ejecución del programa, lo cual mejora la claridad y capacidad de modificación del mismo. Los programadores pueden optar por manejar las excepciones que elijan: todas las excepciones, todas las excepciones de cierto tipo o todas las excepciones de un grupo de tipos relacionados 66. Esta flexibilidad reduce la probabilidad de que los errores se pasen por alto y, por consecuencia, hace que un programa sea más robusto. El manejo de excepciones está diseñado para procesar errores sincrónicos, que ocurren cuando se ejecuta una instrucción. Ejemplos comunes de estos errores son los índices fuera de rango, el desbordamiento aritmético (es decir, un valor fuera de rango representable de valores), la división entre cero, los parámetros inválidos de método, la interrupción de subprocesos y la asignación fallida de memoria (debido a la falta de ésta). El manejo de excepciones no esta diseñado para procesar los problemas asociados con los eventos asíncronos 67. los cuales ocurren en paralelo con, y en forma independiente de flujo de control del programa. Con lenguajes de programación que no soportan el manejo de excepciones, los programadores a menudo retrasan la escritura de código de procesamiento de errores, o algunas veces olvidan incluirlo. Esto hace que los productos de software sean menos robustos. Java permite al programador tratar con el manejo de excepciones fácilmente, desde el comienzo de un proyecto. Sin embargo, el programador debe incorporar una estrategia de manejo de excepciones en los proyectos de software. El mecanismo de manejo excepciones también es útil para procesar los problemas que ocurren cuando un programa invoca a los métodos de otras clases. En vez de manejar los problemas internamente, dichos métodos utilizan por lo común excepciones para notificar a los métodos que hacen las llamadas cuando ocurren los problemas. Esto permite a los programadores implementar un manejo de errores personalizado para cada aplicación. Las aplicaciones complejas normalmente consisten de componentes predefinidos de software y de componentes específicos para cada aplicación. Cuando un componente predefinido se encuentra con un problema, ese componente necesita de un mecanismo para comunicar el problema al componente específico de la aplicación: el componente predefinido no puede saber de antemano cómo procesa cada aplicación cierto problema ocurrido. El manejo de excepciones simplifica la combinación de componentes de software y les permite trabajar efectivamente, al permitir que los componentes predefinidos comuniquen los problemas a los componentes específicos de la aplicación, los cuales podrán entonces procesar los problemas de una manera especifica para cada aplicación. El manejo de excepciones esta dirigido a situaciones en las que el método que detecta un problema es incapaz de manejarlo. Dicho método lanza una excepción. No hay garantía de que habrá un manejador de excepciones (código que se ejecuta cuando el programa 66 Por ejemplo, los tipos de excepciones que pertenecen a una jerarquía de herencia. Por ejemplo, completar las operaciones de E/S de disco, la llegada de mensajes de red, clics del ratón y pulsaciones de teclas. 67 Programación orientada a objetos en Java 149 Unidad VII. Excepciones detecta una excepción) para procesar este tipo de excepción. Si existe, el manejador de excepciones atrapa y maneja a esa excepción. El resultado de una excepción no atrapada a menudo produce efectos adversos y podría terminar con la ejecución del programa. Java proporciona las instrucciones try para permitir el manejo de excepciones. Una instrucción try consiste de la palabra clave try, seguida por llaves ({}) que delimitan a ese bloque try. El bloque try contiene instrucciones que podrían ocasionar excepciones, e instrucciones que no deberían ejecutarse en caso de que ocurra una instrucción. Debe haber por lo menos una cláusula catch (a la que también se le llama manejador de excepciones) o una cláusula finally inmediatamente después del bloque try. Cada cláusula catch específica entre paréntesis un parámetro de excepción, el cual identifica al tipo de excepción que puede procesar el manejador. El nombre del parámetro de excepción permite que la cláusula catch interactúe con un objeto de excepción atrapada. Después del último manejador catch, una clausula finally opcional proporciona código que siempre se ejecuta, sin importar que ocurra o no una excepción. El punto en el programa en que ocurre una excepción (es decir, la ubicación en la que un método detecta y lanza una excepción) se conoce como el punto de lanzamiento. Si ocurre una excepción en un bloque try, ese bloque termina inmediatamente y el control del programa se transfiere a la primer cláusula catch que vaya después del bloque try. Esto se conoce como modelo de terminación del manejo de excepciones, ya que el bloque try que encierra a una excepción lanzada termina al ocurrir esa excepción. Al igual que con cualquier otro bloque de código, cuando termina un bloque try las variables locales que estén declaradas en el bloque quedan fuera de alcance. A continuación, el programa busca la primera cláusula catch que puede procesar el tipo de excepción que ocurrió. El programa ubica la cláusula catch que concuerde, comparando el tipo de la excepción lanzada con el tipo de parámetro de excepción de cada cláusula catch, hasta que el programa encuentre una concordancia. La concordancia ocurre si los tipos son idénticos, o si el tipo de la excepción lanzada es una subclase del tipo de parámetro de excepción, cuando ocurre una concordancia, se ejecuta el código contenido dentro del manejador catch concordante. Cuando una cláusula catch termina de procesarse, las variables locales que se declaran dentro de la cláusula (incluyendo su parámetro) quedan fuera de alcance. Cualquier cláusula catch restante que corresponda a ese bloque try se ignora, y la ejecución continúa en la primera línea de código después de la secuencia try/catch. Si no ocurren excepciones en un bloque try, el programa ignora el (los) manejador(es) para ese bloque. La ejecución del programa continúa con la siguiente instrucción que haya después de la secuencia try/catch. Si aparece una cláusula finally después de la última cláusula catch, la cláusula finally se ejecutará sin importa que ocurra o no una excepción. Si ocurre una excepción en un método y no es atrapada, o la instrucción que produjo la excepción no se encuentra dentro de un bloque try, hace que termine inmediatamente y el programa trata de localizar un bloque try circundante en el método que hizo la llamada. En la declaración de un método, una cláusula throws específica las excepciones que lanza ese método. Esta cláusula aparece después de la lista de parámetros y antes del cuerpo del método. La cláusula contiene una lista separada por comas de las excepciones que lanzará el método, si ocurre un problema cuando éste se ejecute- dichas excepciones pueden ser lanzadas por instrucciones en el cuerpo del método, o pueden lanzarse Programación orientada a objetos en Java 150 Unidad VII. Excepciones mediante los métodos que se llamen en el cuerpo. Un método puede lanzar excepciones de clases indicadas, o puede lanzar excepciones de sus subclases. Ejemplo de manejo de excepciones - Error de División entre Cero. Para evitar esta situación y gestionar un error de tiempo de ejecución, sencillamente hay que incluir el código que quiera controlar dentro de un bloque try. Inmediatamente después del bloque try, se incluye la cláusula match que especifica el tipo de excepción que se desea capturar. Para mostrar como puede hacerse esto de forma sencilla, el siguiente programa incluye un bloque try y una cláusula match que procesa la excepción ArithmeticException generada por el error debido a la división entre cero. Código 7.2.1.1. Error de división entre cero. Este programa genera la siguiente salida: División entre cero. Después de la sentencia Catch. Nótese que la llamada a println() dentro del bloque try no llega a ejecutarse. Una vez que se lanza una excepción. Por lo tanto, no aparece en pantalla la línea “Esto no será impreso”. Una vez que se ejecuta la sentencia catch, el control del programa continua con la siguiente línea al bloque try/catch. Un bloque try y su correspondiente sentencia catch forman una unidad. El campo de acción de una cláusula catch se limita a aquellas sentencias especificadas por la sentencia try que le precede inmediatamente. Una sentencia catch no puede capturar una ejecución lanzada por otra sentencia try, excepto en el caso de las sentencias try anidadas. Las sentencias protegidas por try han de estar entre llaves, es decir han de estar en un bloque no puede usarse try en una sentencia individual. Programación orientada a objetos en Java 151 Unidad VII. Excepciones El objetivo de la mayoría de cláusulas catch bien construidas ha de ser el de resolver la condición excepcional y luego continuar como si el error nunca hubiese ocurrido. Por ejemplo, en el siguiente programa cada iteración de bloque for obtiene dos enteros aleatorios. Estos dos enteros se dividen uno entre el otro, y el cociente se utiliza como divisor del valor 12345. El resultado final se almacena en la variable a. si en cualquiera de las dos operaciones se produce una división entre cero, se captura el error, el resultado se pone a cero y el programa continua. Código 7.2.1.2 Manejo de excepciones. 7.2.1 Lanzamiento de Excepciones El lanzamiento de las excepciones se hace con las palabras try – catch – finally Try Es el bloque de código donde se prevé que se genere una excepción. Es como si se dijera "intenta estas sentencias y mira a ver si se produce una excepción". El bloque try tiene que ir seguido, al menos, por una cláusula catch o una cláusula finally. Catch Es el código que se ejecuta cuando se produce la excepción. Es como si se dijera "controlo cualquier excepción que coincida con mi argumento". En este bloque se tiene que asegurar de colocar código que no genere excepciones. Se pueden colocar sentencias catch sucesivas, cada una controlando una excepción diferente. No debería intentarse capturar todas las excepciones con una sola cláusula, como esta: Programación orientada a objetos en Java 152 Unidad VII. Excepciones catch( Excepcion e ) { ... Esto representaría un uso demasiado general, podrían llegar muchas más excepciones de las esperadas. En este caso es mejor dejar que la excepción se propague hacia arriba y dar un mensaje de error al usuario. Se pueden controlar grupos de excepciones, es decir, que se pueden controlar, a través del argumento, excepciones semejantes. Por ejemplo: Código 7.2.1.3 Control de exepciones La cláusula catch comprueba los argumentos en el mismo orden en que aparezcan en el programa. Si hay alguno que coincida, se ejecuta el bloque. El operador instanceof se utiliza para identificar exactamente cual ha sido la identidad de la excepción. Finally Es el bloque de código que se ejecuta siempre, haya o no excepción. Hay una cierta controversia entre su utilidad, pero, por ejemplo, podría servir para hacer un log o un seguimiento de lo que está pasando, porque como se ejecuta siempre puede dejar grabado si se producen excepciones y se ha recuperado de ellas o no. Programación orientada a objetos en Java 153 Unidad VII. Excepciones Este bloque finally puede ser útil cuando no hay ninguna excepción. Es un trozo de código que se ejecuta independientemente de lo que se haga en el bloque try. Cuando se va a tratar una excepción, se plantea el problema de qué acciones se van a tomar. En la mayoría de los casos, bastará con presentar una indicación de error al usuario y un mensaje avisándolo de que se ha producido un error y que decida si quiere o no continuar con la ejecución del programa. Para lanzar una excepción se tienen que hacer dos cosas como mínimo. La primera es crear el objeto que se va a lanzar, la segunda, lanzar el objeto. Para realizar esto se crea una instancia de un objeto perteneciente a la jerarquía de java.lang.Throwable, luego se utiliza la instrucción throw para lanzar el objeto. Normalmente esta tarea se realiza en una sola línea ya que al lanzar el objeto se crea información para que la excepción sea reportada y es conveniente que la línea que se reporta como origen de la excepción sea la misma donde se lanza. La siguiente es un ejemplo de esto: throw new IOException("No se encuentra el archivo"); Java requiere, como regla general que todo método que pueda lanzar una excepción debe declarase específicamente, no importa si es explícitamente lanzada por el método o por un método que fue llamado. Para realizar esto se utiliza la instrucción throws en la declaración del método de la siguiente forma: public void dividir(int x, int y) throws ArithmeticException, IOException { } La jerarquía de clases que existe bajo la clase java.lang.Throwable esta dividida en tres partes. Una parte, java.lang.Error contiene excepciones que se utilizan para indicar errores, java.lang.Exception que contiene las llamadas excepciones de verificación y java.lang.RuntimeException que es subclase de java.lang.Exception y agrupa las llamadas excepciones en tiempo de ejecución que son siempre por errores de programación. Las excepciones de verificación describen los problemas que pueden darse en un programa, típicamente, dificultades con el ambiente como problemas de conexión con la red. En un programa comercial se debe escribir código que pueda manejar y recuperarse de estas excepciones. De hecho, el compilador verifica que se estén manejando estas excepciones por lo que entrega un mensaje de error en tiempo de compilación si alguna no esta manejada. Las excepciones en tiempo de ejecución típicamente describen errores de programa como índices fuera de rango, aquellas que con un código correctamente escrito normalmente no es necesario manejar. Lógicamente no se deberían de manejar errores que no deberían de sucederse. Programación orientada a objetos en Java 154 Unidad VII. Excepciones java.lang.Error describe problemas que son muy inusuales y por lo tanto de difícil recuperación por lo tanto no se requiere que se manejen. Estos reflejan un error de programa o problemas de ambiente como puede ser la memoria agotada. Una estrategia de diseño e implementación de programa que es muy efectiva a la hora de producir código robusto y confiable es programar por contrato. Usando esta estrategia se definen las responsabilidades de los métodos y de aquellos que llaman a esos métodos. En un método dividir se puede requerir que el divisor sea distinto de cero, en caso contrario se lanza una excepción dado que el contrato entre el método que llama y el llamado se ha roto. Para este tipo de mensajes se deben utilizar excepciones de tiempo de ejecución ya que la llamada claramente es inapropiada y el que realiza la llamada debe verificar estos errores de programación y corregirlos. Para manejar una excepción de verificación se debe decidir que hacer con ella. Se puede colocar un bloque try/catch para tratar de recuperarse o decidir que el método debe ser abandonado cuando se sucede la excepción. En este último caso no es necesario utilizar try/catch pero si hay que especificar que excepción será lanzada utilizando throws en la declaración del método. Cuando se extiende una clase y se sobrecarga un método el compilador insiste en que todas las excepciones lanzadas por el nuevo método deben ser las mismas o subclases de las lanzadas por el método original. De la misma forma no puede lanzar ninguna excepción que no sea subclase de la excepción declarada en la clase base. 7.3 Excepciones Definidas por el Usuario 7.3.1 Clase Base de las Excepciones Todas las excepciones que se lanzan en Java están consideradas en el árbol de excepciones que se deriva de la clase Throwable. Por lo cual la clase Throwable es considerada la clase base de las excepciones. Existen dos subclases directas de Throwable: Error y Exception. Los objetos de la clase Error provocan que el intérprete de Java presente un mensaje de información y concluya la ejecución del programa. Los objetos de la clase Exception indican condiciones anómalas durante la ejecución de un método. Esta clase tiene, a su vez, nueve subclases predefinidas. Las clases de excepciones definidas por el programador se construyen como subclases de la clase Exception. En la siguiente figura 16 se observa parte de la jerarquía de clases derivada de Throwable: La clase Error está relacionada con errores de la máquina virtual de Java y no el código, generalmente estos errores no dependen del programador por lo que no debe preocuparse por tratarlos. En la clase Exception se encuentran las excepciones RuntimeException, producidas por errores de programación. El compilador de Java obliga a corregirlas. Programación orientada a objetos en Java 155 Unidad VII. Excepciones Figura 16: Clase base de la clase Throwable. Como se puede ver en la figura 16, la clase Throwable tiene dos descendientes directos: Error y Exception. - Error Cuando falla un enlace dinámico, y hay algún fallo "hardware" en la máquina virtual, ésta lanza un error. Tipicamente los programas Java no capturan los Errores. Pero siempre lanzarán errores. - Exception La mayoría de los programas lanzan y capturan objetos derivados de la clase Exception. Una Excepción indica que ha ocurrido un problema pero que el problema no es demasiado serio. La mayoría de los programas que se escriben lanzarán y capturarán excepciones. La clase Exception tiene muchos descendientes definidos en los paquetes Java. Estos descendientes indican varios tipos de excepciones que pueden ocurrir. Por ejemplo, IllegalAccessException señala que no se puede encontrar un método particular, y NegativeArraySizeException indica que un programa intenta crear un array con tamaño negativo. Una subclase de Exception tiene un significado especial en el lenguaje Java: RuntimeException. Excepciones en Tiempo de Ejecución La clase RuntimeException representa las excepciones que ocurren dentro de la máquina virtual Java 68(durante el tiempo de ejecución). Un ejemplo de estas excepciones es NullPointerException, que ocurre cuando un método intenta acceder a un miembro de un objeto a través de una referencia nula. Esta excepción puede ocurrir en cualquier lugar en 68 Java Virtual Machine (JVM). Programación orientada a objetos en Java 156 Unidad VII. Excepciones que un programa intente desreferenciar una referencia a un objeto. Frecuentemente el costo de checar estas excepciones sobrepasa los beneficios de capturarlas. Los paquetes Java definen varias clases RuntimeException. Se pueden capturar estas excepciones al igual que las otras. Sin embargo, no se requiere que un método especifique que lanza excepciones en tiempo de ejecución. Además puedes crear sus propias subclases de untimeException. Excepciones en Tiempo de Ejecución -- La Controversia contiene una explicación detallada sobre cómo utilizar las excepciones en tiempo de ejecución. 7.3.2. Creación de una Clase Derivada del Tipo Excepción Aunque las excepciones que incorpora Java gestionan la mayoría de los errores más comunes, es probable que el programador prefiera crear sus tipos de excepciones para gestionar situaciones específicas a sus aplicaciones, esto es bastante sencillo de conseguir definiendo una subclase de Throwable. En realidad, no es necesario que estas subclases que crea el programador implementen nada. simplemente, es su presencia en el sistema lo que permitirá que se utilicen como excepciones. La clase exception no define ningún método por si misma, pero, obviamente, hereda los métodos que proporciona la clase Throwable. Por tanto todas las excepciones, incluyendo aquellas que crea el programador, pueden disponer de métodos definidos por Throwable. Estos métodos, que además pueden sobrescribirse en las clases de excepción propias, se recogen en la tabla siguiente: Tabla. Métodos definidos por la clase Throwable Método Throwable Throwable getCause() Descripción Devuelve un objeto de la clase throwable que contiene el trazado completo de la pila Devuelve la excepción que subyace en la excepción actual. Si no hay excepciones subyacentes, se devuelve null Devuelve una descripción localizada de la excepción. String getLocalizedMessage() StackTraceElement[] getStack Trace() Devuelve una descripción de la excepción Devuelve una matriz que contiene el trazado de la pila, un elemento cada vez, y una matriz de StackTraceElement. El primer método de la pila es el último de ser llamado antes de que se lancé la excepción este método se encuentra en el primer elemento de la matriz. La clase StackTraceElement da al programa acceso a la información acerca de cada elemento en el trazado, así como acerca de los nombres de los métodos. Programación orientada a objetos en Java 157 Unidad VII. Excepciones Asocia causeExc con la llamada a la excepción como causa de la excepción de la llamada. Devuelve una referencia a la excepción Presenta en la pantalla el trazado de la pila Envía el trazado de la pila a flujo determinado Throwable InitCause (throwable causeExc) void printStackTrace() void printStackTrace (PrintSreamstream) Envía el trazado a los elementos pasados en elementos. Este método es para aplicaciones especializadas , no para un uso común Devuelve una cadena con la descripción de la excepción , ese método es llamado como println() cuando se desea imprimir un objeto de la clase throwable. void printDStackTrace (Snack TraceElementselements) String toString() El siguiente ejemplo declara una nueva subclase de excepcion y luego utiliza esta subclase para señalar una condición de error en un método. Esta subclase sobrescribe el método toString() para permitir que la descripción de la excepción puede impimirse mediante println(): Programación orientada a objetos en Java 158 Unidad VII. Excepciones Código 7.3.2.1 Programa que crea un tipo propio de excepción. Este ejemplo define una subclase de Exception, llamada MiExcepcion. Esta subclase es bastante sencilla, sólo contiene un constructor y un método sobrecargado, toString(), que permitirá presentar el valor de la excepción. La clase ExcepcionDemo define un método, llamado compute(), que lanza un objeto del tipo MiExcepcion. La excepción se lanza cuando el parámetro entero de compute() es superior a 10. El método main() establece un gestor de excepciones para MiExcpecion, luego llama a compute() con un valor legal inferior a 10, y con un valor no válido. Para mostrar las dos vías que sigue el código. Este es el resultado: Llamado a compute(1) Salida normal Llamado a compute(20) Agarrada MiExcepcion[20] 7.3.3 Manejo de una Excepción Definida por el Usuario Para el manejo de las excepciones propias, solo se extiende la clase Exception. Programación orientada a objetos en Java 159 Unidad VII. Excepciones Por ejemplo, considérese un programa cliente/servidor. El código cliente se intenta conectar al servidor, y durante 5 segundos se espera a que conteste el servidor. Si el servidor no responde, el servidor lanzaría la excepción de time-out: Si se quieren capturar las propias excepciones, se deberá utilizar la sentencia try: Código 7.3.2.Manejo de una excepción definida por el usuario. Sólo se extiende la clase Exception Cualquier método que lance una excepción también debe capturarla, o declararla como parte del interfaz del método. Cabe preguntarse entonces, el porqué de lanzar una excepción si hay que capturarla en el mismo método. La respuesta es que las excepciones no simplifican el trabajo del control de errores69. Tienen la ventaja de que se puede tener muy localizado el control de errores y no hay que controlar millones de valores de retorno, pero no van más allá. 7.4 Ejercicios resueltos 7.4.1 Implementar un programa que pida un entero y lance una excepción si se teclea una letra o cualquier otro carácter. 69 Aunque muchos desarrolladores de software tienen esa noción. Programación orientada a objetos en Java 160 Unidad VII. Excepciones 7.4.2 Implementar un programa que realice una operación de división introduciendo los dos valores desde el teclado y mande una excepción en caso de que uno de los valores no sea numérico o la división de cero. 7.4.3 Implementar un programa que reciba dos parámetros desde la línea de comandos y los sume. Si no se omite alguno de los dos, se manda una excepción. Programación orientada a objetos en Java 161 Unidad VII. Excepciones 7.4.4 Implementar un programa realice la división de dos números naturales, si uno de los dos no lo es, que arroje una excepción. Programación orientada a objetos en Java 162 Unidad VII. Excepciones 7.5 Ejercicios propuestos: 7.5.1 Escribir un código de una clase Java que lance excepciones para cuantas condiciones considere convenientes. Utilizar una cláusula catch que utilice la sentencia switch para seleccionar el mensaje apropiado y terminar el calculo. Nota. Utilice una jerarquía de clases para listas las condiciones de error. 7.5.2 Escribir un código de un método en el cual se defina un bloque try y dos manejadores catch. En uno de ellos se relanza la excepción. También ha de haber un manejador finally, que lanzara una excepción que relanza el catch, para lo cual, escribir un sencillo programa en que se genere la excepción que es captada por el catch descrito. 7.5.3 Escribir el código de una clase para tratar el error que se produce cuando un argumento de un algoritmo neperiano es negativo. El constructor de la clase tendrá como argumento una cadena y el valor que generado el error. 7.5.4 Escriba un programa Java en el que se genere la excepción del problema anterior, y se capture. 7.5.5 Definir una clase para tratar los errores en el manejo de cadenas de caracteres. A continuación, definir una subclase para tratar el error supuesto de cadenas de longitud mayor de 30 caracteres, y otra subclase que maneje los errores de cadenas que tienen caracteres no alfabéticos. 7.5.6 Escribir un programa java en el que se de entrada a cadenas de caracteres y se capturen excepciones del tipo mencionado en el problema anterior. 7.5.7 Escribir un programa que demuestren como se atrapan las diversas excepciones mediante: catch (Excepction exception) 7.5.8Escriba un programa que demuestre como un constructor pasa información sobre falla del constructor a un manejador de excepciones. 7.5.9 Escribir un programa que demuestre como volver a lanzar una excpecion. 7.5.10 Escriba un programa que demuestre que un método con su propio bloque try no tiene que atrapar cada posble error generado dentro de este bloque try. Algunas excepciones podrían escabullirse y manejarse en otros alcances. Programación orientada a objetos en Java 163 Unidad VIII. Flujos y archivos UNIDAD 8.FLUJOS Y ARCHIVOS Definición, Tipos y Operaciones básicas de Archivos Programación orientada a objetos en java 164 Unidad VIII. Flujos y archivos UNIDAD 8.- FLUJOS Y ARCHIVOS 8.1 Definición de archivos de texto y Archivos Binarios Archivos de texto: Es una estructura de datos permanente no estructurada formada por una secuencia de caracteres ASCII compuestos 70 . Los archivos de texto son aquellos que están únicamente por texto sin formato, sólo caracteres. Estos caracteres se pueden codificar de distintos modos dependiendo de la lengua usada. Algunos de los sistemas de codificación más usados son: ASCII, ISO-8859–1 o Latín-1, Unicode, etc. Se les conoce también como archivos de texto plano por carecer de información destinada a generar formatos y tipos de letra (por ejemplo, tipo de letra: Arial, Times, Courier; formato: negritas, subrayado, cursivas; tamaño, etc.). Las aplicaciones destinadas a la escritura y modificación de archivos de texto se llaman editores de texto. La costumbre ha hecho que se nombren con la extensión de archivo .TXT aunque pueden tener cualquier otra, a capricho del usuario (son válidas y habituales .INF .80 .DAT .TMP .PRV .HLP etc.). Los archivos .BAT (o de proceso por lotes), los .HTM y muchos otros son también archivos de texto, que tienen funciones especiales. Archivos binarios: Es una estructura de datos permanente compuesto por registros (filas) y éstos a su vez por campos (columnas). Se caracteriza por tener un tipo de dato asociado, el cual define su estructura interna. Los archivos binarios son archivos electrónicos que han sido guardados utilizando el código básico de las computadoras u ordenadores: una sucesión de ceros y unos. Constituyen, en última instancia, la forma en la cual almacenan la información, aunque su interacción con los usuarios requiere de lenguajes auxiliares que resulten más inteligibles al ser humano. 70 Acrónimo de American Standard Code for Information Interchange (Código Normalizado Americano para el Intercambio de la Información). Programación orientada a objetos en java 165 Unidad VIII. Flujos y archivos 8.2 Operaciones Básicas en Archivos de Texto y Binario 8.2.1 Crear Para crear un archivo la sintaxis es: FileWriter nombre_variable = new FileWriter(“Nombre del archivo incluyendo ruta”) Por ejemplo: FileWriter archivo = new FileWriter("c:/prueba.txt"); En el ejemplo anterior se crea un archivo de nombre pureba.txt ubicado en la ruta c:. Si quiere añadir al final de un archivo ya existente, simplemente se debe poner un flag a true como segundo parámetro del constructor de FileWriter FileWriter archivo = new FileWriter("c:/prueba.txt",true); En este ejemplo se añadirán líneas al final del archivo prueba.txt 8.2.2 Abrir La apertura de un archivo se hace con la siguiente sentencia: FileReader nombre_variable = new FileReader(“Nombre del archivo incluyendo ruta”) Por ejemplo: FileReader fr2 = new FileReader("archivo.txt"); Esto es equivalente a: File archi = new File("archivo.txt"); FileReader fr2 = new FileReader(archi); Si a la hora de la apertura no encuentran el archivo indicado, los constructores de FileReader y FileInputStream pueden lanzar la excepción java.io.FileNotFoundException. Programación orientada a objetos en java 166 Unidad VIII. Flujos y archivos 8.2.3 Cerrar public void close() throws IOException Cierra el stream de entrada. Este método debe invocarse para liberar los recursos71 asociados al stream. Una vez que el stream ha sido cerrado, las operaciones posteriores sobre dicho sfream producirán una exepción IOException. Cerrar un stream previamente cerrado no tiene ningún efecto. La implementación por defecto de close no hace nada. 8.2.4 Lectura y Escritura Frecuentemente los programas necesitan traer información desde una fuente externa o enviar información a una fuente externa. La información puede estar en cualquier parte, en un archivo, en disco, en algún lugar de la red, en memoria o en otro programa. También puede ser de cualquier tipo: objetos, caracteres, imágenes o sonidos. Para traer la información, un programa abre un stream sobre una fuente de información (un archivo, memoria, un socket) y lee la información serialmente, de esta forma: Flujo Lectura Fuente Programa Similarmente, un programa puede enviar información a un destino externo abriendo un stream sobre un destino y escribiendo la información serialmente, de esta forma: Escritura Flujo Fuente Destino No importa de donde venga o donde vaya la información y tampoco importa el tipo de los datos que están siendo leídos o escritos, los algoritmos para leer y escribir son casi siempre los mismos. Leer abrir un stream mientras haya información leer información cerrar el stream 71 Escribir abrir un stream mientras haya información escribir información cerrar el stream Como los descriptores de archivos. Programación orientada a objetos en java 167 Unidad VIII. Flujos y archivos El paquete java.io contiene una colección de clases stream que soportan estos algoritmos para leer y escribir. Estas clases están divididas en dos árboles basándose en los tipos de datos (caracteres o bytes) sobre los que opera. Flujo de caracteres Flujo de bytes Sin embargo, algunas veces es más conveniente agrupar las clases basándose en su propósito en vez en los tipos de datos que lee o escribe. Así, se pueden agrupar los streams dependiendo de si leen u escriben lados en las "profundidades" o procesan la información que está siendo leída o escrita. Flujo de caracteres Flujo de bytes Caída de flujo de datos Procesando flujos InputStream La clase abstracta InputStream declara métodos para leer bytes de una fuente particular InputStream es la superclase de la mayoría de los streams de bytes de entrada en java y tiene los siguientes métodos: public abstract int read() throws IOException Lee un solo byte de datos y devuelve el byte leído como un valor entero en el intervalo de O a 255, no de -128 a 127. En otras palabras, el valor del byte se trata como un entero sin signo. Si no hay un byte disponible debido a que se ha alcanzado el final del stream, se devuelve el valor -1. Este método se bloquea hasta que haya entrada disponible, se encuentre el final del stream o se lance una excepción. Se devuelve un int en vez valor de byte real porque se necesita devolver todos los valores de byte válidos, Más un indicador de final de stream. Para ello se requieren más valores de los que puede permitir un byte, por lo que se usa el tipo mayor de int. Programación orientada a objetos en java 168 Unidad VIII. Flujos y archivos public int read(byte[] buf, int despl, int cuenta) throws IOException Lee bytes y los almacena en una parte de un array de byte. El máximo número de bytes que se leen es cuenta. Los bytes se almacenan desde buf [despl] hasta un máxima buf [despl +cuenta-1 ]. Todos los demás valores de buf se dejan inalterados. Se devuelve el número de bytes que se han leído realmente. Si no se leen bytes debido a que se detectado el final del stream, se devuelve el valor -1. Si cuenta vale cero, no se leen bytes y se devuelve cero. Este método se bloquea hasta que haya entrada disponible, se alcance el final del stream o se lance una excepción. Si no se puede leer el primer byte, una razón diferente a la de detectar el final del stream (Por ejemplo, stream ya ha sido cerrado), se lanza una excepción IOException. Una vez que se ha leído el byte, cualquier fallo posterior al tratar de leer bytes subsecuentes no se indica con una excepción, sino que se trata como si se encontrara el final del stream: el método se completa normalmente y devuelve el número de bytes leídos antes de que se produjera el fallo. public int read(byte[] buf) throws IOException Es equivalente a read (buf, 0, buf .length). public long skip(long cuenta) throws IOException Salta un máximo de cuenta bytes de la entrada, o hasta que se detecte el final del stream. Devuelve el número real de bytes que se han saltado. Si cuenta es negativo, no se salta ningún byte. public int available() throws IOException Devuelve el número de bytes que se pueden leer (o saltar) sin bloquearse. La implementación por defecto devuelve cero. public void close() throws IOException Cierra el stream de entrada. Este método debe invocarse para liberar los recursos (Como los descriptores de archivos) asociados al stream. Una vez el stream ha sido cerrado, las operaciones posteriores sobre dicho stream producirán una excepción IOException. Cerrar un stream previamente cerrado no tiene ningún efecto. La implementación por defecto de close no hace nada. La implementación de InputStream requiere sólo que una subclase proporcione la variante de un solo byte de read, ya que los otros métodos read se definen en función de aquélla. La mayoría de los Streams, sin embargo, pueden mejorar las prestaciones redefiniendo también otros métodos. Las implementaciones por defecto de available y close necesitan generalmente ser redefinidas para adecuarlas a un stream particular. El programa siguiente muestra el uso de streams de entrada para contar el número total de bytes de un archivo, o de System.in si no se especifica ningún archivo: Programación orientada a objetos en java 169 Unidad VIII. Flujos y archivos Código 8.2.4.1 Programa que toma un archivo de la línea de comandos y cuenta los bytes. El programa toma un nombre de archivo de la línea de comandos. La variable in representa el stream de entrada. Si no se proporciona un nombre de archivo, se utiliza el stream de entrada estándar, System.in. Si se proporciona, se crea un objeto FilelnputStream, una subclase de InputStream. El ciclo72 while cuenta el número total de bytes del archivo. Al final, se imprime el resultado. Ésta es la salida cuando el programa se ejecuta sobre su propio archivo fuente: • 318 bytes Se podría estar tentado de obtener el total utilizando el método available, pero esto no funcionaría con muchos tipos de streams. El método available devuelve el número de bytes que se pueden leer sin bloqueo. En el caso de un archivo, el número de bytes disponibles es generalmente su contenido total. Pero si System.in está asociada a un teclado, la respuesta podría ser incluso cero, ya que si no hay entrada pendiente el siguiente read se bloqueará. OutputStream La clase abstracta OutputStream es análoga a InputStream. Proporciona una abstracción para escribir bytes en un destino. Sus métodos son: public abstract void write(int b) throws IOException Escribe b como un byte. Este byte se pasa como un int porque a menudo es el resultado de una operación aritmética sobre un byte. Las expresiones que involucran bytes son de tipo int, por lo que hacer que el parámetro sea int significa que el resultado puede pasar a byte sin realizar una conversión explícita de tipo. Nótese, sin embargo. Que sólo se escriben los 8 bits de menor peso del int. Este método se bloquea hasta que el byte se escribe. 72 Ciclo. Programación orientada a objetos en java 170 Unidad VIII. Flujos y archivos public void write(byte[] buf, int despl, int cuenta) throws IOException Escribe parte de un array de bytes, empezando en buf [despl], y escribiendo cuenta bytes. Este método se bloquea hasta que los bytes han sido escritos. public void write(byte[] buf) throws IOException Equivale a write (buf, 0, buf .length) public vaid flush() throws IOException Vacía el stream. Si el stream tiene bytes en buffers correspondientes a varios métodos. write, flush los envía inmediatamente a su destino. Si el destino es otro stream, también se vacía. Una sola invocación a flush vaciará todos los streams de una cadena. Si el stream no es buffered, puede ocurrir que flush no haga nada (que es su implementación por •defecto). public void close() throws IOException Cierra el stream de salida. Este método se debe invocar para liberar los recursos (como los descriptores de archivos) asociados al stream. Una vez que un stream ha sido cerrado, las operaciones posteriores sobre el mismo harán que se lance una IOException. Cerrar un stream previamente cerrado no tiene ningún efecto. La implementación por defecto de close no hace nada. La implementación de OutputStream requiere sólo que una subclase proporcione la variante de un solo byte de write, ya que los otros métodos write se definen en términos de aquélla. La mayoría de los streams, sin embargo, pueden mejorar sus prestaciones redefiniendo también otros métodos. Las implementaciones por defecto de flush y close necesitarán generalmente ser redefinidas de forma apropiada para cada stream particular. Concretamente, por ejemplo, puede ser necesario vaciar los streams buffered cuando se cierran. Se presenta seguidamente un programa que copia su entrada en su salida, transformando un valor particular de byte en otro por el camino. El programa TransformarByte toma dos parámetros: un byte desde y un byte hacia los bytes que coinciden con el valor en la cadena de texto. Código 8.2.4.2 Programa que toma dos parámetros, un byte desde y un byte hacia. Programación orientada a objetos en java 171 Unidad VIII. Flujos y archivos Por ejemplo, si se invoca al programa de la siguiente forma: java TransformarByte b B Si se tecleara el texto ¡abracadabra!, se obtendría como salida ¡aBracadaBra! El manejo de datos de un stream después de leerlo, o antes de escribirlo, se realiza generalmente escribiendo streams Filter, en lugar de codificar directamente dicho manejo en un • programa. Como en el caso de streams de bytes, los streams de caracteres se deben cerrar explícitamente para liberar sus recursos asociados. Reader73 La clase abstracta Reader proporciona un stream de caracteres análogo al stream de bytes InputStream, y los métodos de Reader son esencialmente un reflejo de los de InputStream: public int read() throws IOException Lee un solo carácter y lo devuelve como un valor entero en el intervalo de O a 65535. Si no hay un carácter disponible debido a que se ha alcanzado el final del stream. Se devuelve el valor -1. Este método se bloquea hasta que haya entrada disponible, se encuentre el final del stream o se lance una excepción. public abstract int read(char[l buf, int despl, int cuenta) throws IOException Lee caracteres y los almacena en una parte de un array de char. El máximo número de caracteres que se leen es cuenta. Los caracteres leídos se almacenan desde buf [despl] hasta un máximo de buf[despl+cuenta-1]. Todos los demás valores de buf se dejan inalterados. Se devuelve el número de caracteres que se han leído realmente. Si no se leen caracteres debido a que se detecta el final del stream, se devuelve el valor -1. Si cuenta vale cero, no se leen caracteres y se devuelve cero. Este método se bloquea hasta que haya entrada disponible, se alcance el final del stream o se lance una excepción. Si no se puede leer el primer carácter por una razón diferente a la de detectar el final del stream (concretamente por ejemplo, si el stream ya ha sido cerrado), se lanza una excepción IOException. Una vez que se ha leido un carácter, cualquier fallo posterior al tratar de leer caracteres subsecuentes no se indica con una excepción, sino que se trata como si se encontrara el final del stream: el método se completa normalmente y devuelve el número de caracteres leídos antes de que se produjera el fallo. public int read(char[] buf) throws IOException Es equivalente a read (buf, 0, buf .length). 73 Lector. Programación orientada a objetos en java 172 Unidad VIII. Flujos y archivos public long skip(long cuenta) throws IOException Salta un máximo de cuenta caracteres de la entrada, o hasta que se detecte el final del stream. Devuelve el número real de caracteres que se han saltado. El valor de cuenta no puede ser negativo. public boolean ready() throws IOException Devuelve true si el stream está listo para ser leído, es decir, si hay al menos un carácter disponible para ser leído. Nótese que un valor de retorno de false no garantiza que la siguiente invocación de read se bloqueará, ya que pueden haber llegado nuevos datos disponibles en el momento que se produce la invocación a read. public abstract void close() throws IOException Cierra el stream de entrada. Este método debe invocarse para liberar los recursos (como los descriptores de archivos) asociados al stream. Una vez el stream ha sido cerrado, la operaciones posteriores sobre dicho stream producirán una exepción IOExeeption. Cerrar un stream previamente cerrado no tiene ningún efecto. La implementación de Reader requiere sólo que una subclase proporcione la implementación del método read que lee en un array de char, y el método close. Muchas subclases pueden mejorar las prestaciones redefiniendo también los otros métodos. Hay varias diferencias entre Reader e InputStream. En el caso de Reader, el método fundamental de lectura lee en un array de char, y los otros métodos read se definen en función de ese método fundamental. Por el contrario, la clase InputStream usa el método read de un solo byte como método fundamental de lectura. En la clase Reader las subclases deben implementar el método abstracto close, y no heredan una implementación vacía (muchas clases de streams necesitarán saber al menos si han sido o no cerradas, por lo que close necesitará generalmente ser redefinido). Finalmente, InputStream tiene un método available para indicar cuántos datos hay disponibles para lectura, mientras que Reader tiene un método ready que indica simplemente si hay datos disponibles. Programación orientada a objetos en java 173 Unidad VIII. Flujos y archivos Código 8.2.4.3. Programa que cuenta el número de caracteres blancos que hay en un stream de caracteres. total caracteres , 111 espacios Writer74 La clase abstracta writer proporciona un stream análogo a OutputStream, pero diseñado para trabajar con caracteres en vez de con bytes. Los métodos de writer son esencialmente un reflejo de los OutputStream, pero añaden algunas formas útiles de write. void write(char[] buf, int despl, int cuenta) thro IOException Escribe parte de un array de caracteres, empezando en buf [despl], y escribiendo cuenta caracteres. Este método se bloquea hasta que los caracteres han sido escritos. public void write(char[] buf) throws IOException Es equivalente a write (buf , 0 , buf .length). public void write(String cad, int despl, int cuenta) throws IOException Escribe cuenta caracteres de la cadena de texto cad, empezando en cad. charAt (despl). 74 Escritor. Programación orientada a objetos en java 174 Unidad VIII. Flujos y archivos public void write(String cad) throws IOException Equivale a write (cad , 0 , cad. length (c) public abstract void flush() throws IOException Vacía el stream. Si el stream tiene caracteres en buffers correspondientes a varios métodos write, flush los envía inmediatamente a su destino. Si el destino es otro stream, también se vacía. Una sola invocación a flush vaciará todos los streams de una cadena. Si el stream no es buffered, flush no hará nada. public abstract void close() .throws IOException Cierra el stream de salida, vaciándolo si es necesario. Este método se debe invocar para liberar los recursos (como los descriptores de archivos) asociados al stream. Una vez que un stream ha sido cerrado, las operaciones posteriores sobre el mismo harán que se lance una IOException_ Cerrar un stream previamente cerrado no tiene ningún efecto. Las subclases de Writer deben implementar la variante de write que opera sobre un array. y los métodos close y flush. Todos los demás métodos de Writer se implementan en función de estos tres. Esto es una diferencia con OutpuStream, que utiliza la variante de un solo byte del método write como método fundamental, y proporciona implementaciones por defecto de flush y close_ Como en el caso de Reader, muchas subclases pueden dar mejores prestaciones si redefinen también otros métodos. public abstract void close() .throws IOException Cierra el stream de salida, vaciándolo si es necesario. Este método se debe invocar para liberar los recursos (como los descriptores de archivos) asociados al stream. Una vez que un stream ha sido cerrado, las operaciones posteriores sobre el mismo harán que se lance una IOException_ Cerrar un stream previamente cerrado no tiene ningún efecto. Las subclases de Writer deben implementar la variante de write que opera sobre un array. y los métodos close y flush. Todos los demás métodos de Writer se implementan en función de estos tres. Esto es una diferencia con OutpuStream, que utiliza la variante de un solo byte del método write como método fundamental, y proporciona implementaciones por defecto de flush y close_ Como en el caso de Reader, muchas subclases pueden dar mejores prestaciones si redefinen también otros métodos. InputStreamReader 75 y OutputStreamWriter76 Los streams de conversión InputStreamReader y OutputStreamWriter realizan conversiones entre Unicode y streams de bytes utilizando una determinada codificación, o la 75 Para java, un InputStream es cualquier cosa de la que se leen bytes. Puede ser el teclado, un archivo, un socket, o cualquier otro dispositivo de entrada. 76 Para Java, un OutputStreamWriter es cualquier cosa en donde se puedan escribir bytes, un archivo, un socket o cualquier dispositivo de salida. Programación orientada a objetos en java 175 Unidad VIII. Flujos y archivos codificación por defecto del sistema local. Los objetos InputS reamReader reciben como fuente un stream de bytes de entrada y producen los correspondientes caracteres de Unicode. Los objetos OutputStreamReader reciben como destino un stream de bytes de salida y producen las formas codificadas en bytes de los caracteres Unicode que se escriben. Por ejemplo, el código que se presenta a continuación lee bytes correspondientes a caracteres árabes codificados con ISO 8859-6, Y los convierte en los caracteres de Unicode apropiados. Por defecto, estos streams de conversión funcionarán con la codificación por defecto de la plataforma, y si se desean utilizar otras codificaciones, deberán especificarse. Estas clases son el "pegamento" que permite utilizar codificaciones locales de caracteres de 8 bits de forma consistente e independiente de la plataforma. public InputStreamReader(InputStream in) Crea un InputStreamReader para leer de un InputStream dado utilizando la codificación de caracteres por defecto. public InputStreamReader(InputStream in, String codificacion) throws UnsupportedEncodingException Crea un InputStreamReader para leer del InputStream dado utilizando la codificación de caracteres dada. Si el nombre de la codificación no se admite, se lanza una excepción Un supportedEncoding Exception. public OutputStreamWriter(OutputStream out) Crea un OutputStreamWriter para escribir el OutputStream dado utilizando la codificación de caracteres por defecto. public OutputStreamWriter(OutputStream out, String codificacion) throws UnsupportedEncodingException Crea un InputStreamWriter para escribir en un OutputStream dado utilizando la codificación de caracteres dada. Si el nombre de la codificación no se admite, se lanza una excepción UnsupportedEncodingException. Los métodos read de InputStreamReader simplemente leen bytes de su InputStream asociado y los convierten en caracteres utilizando la codificación adecuada para ese stream. Similarmente, los métodos write de OutputStreamWriter toman los caracteres que se les suministran, los convierten en bytes utilizando la codificación apropiada y los escriben en su correspondiente OutputStream asociado. En ambas clases, si se cierra el stream de conversión, se cierra también el stream de Programación orientada a objetos en java 176 Unidad VIII. Flujos y archivos bytes asociado. Esto no siempre tiene por qué ser deseable (como por ejemplo, al convertir los streams estándar), por lo que es conveniente considerar cuidadosamente cuándo se cierran los streams de conversión. Ambas clases admiten también el método getEncoding, que devuelve una cadena de texto que representa el nombre canónico de la codificación de caracteres que se utiliza en el stream. o null si el stream ha sido cerrado. Las clases FileReader y FileWriter son subclases de estos streams de conversión. Esto nos ayuda en la lectura y escritura de archivos locales de forma consistente utilizando Unicode, mediante la codificación local. Pero si la codificación local por defecto no es la que necesitamos, debemos utilizar objetos InputStreamReader o OutputStreamWriter explícitos. No existen clases ReaderlnputStream ni WriterOutputStream para convertir streams de caracteres en streams de bytes. 8.2.5 Recorrer En una Lectura El corrimiento de archivos se hace de línea en línea con ciclos, que mejor que un ejemplo para mostrar como funciona; Código 8.2.5. Recorrer un archivo. El recorrimiento se hace con el ciclo while de línea en línea hasta encontrar el fin de archivo. En una Escritura En este caso se utiliza un ciclo for para recorrer el archivo e insertarle 10 líneas al mismo. Código 8.2.6. Recorrer un archivo para escritura. Programación orientada a objetos en java 177 Unidad VIII. Flujos y archivos 8.3 Aplicaciones Ejemplo 1: Código 8.3.1. Lectura de un archivo de texto en java. Ejemplo 2: Código 8.3.2. Escritura de un archivo de texto en java. Programación orientada a objetos en java 178 Unidad VIII. Flujos y archivos Ejemplo 3: Archivo: archivo.txt El proceso de lectura de un archivo de texto es similar a la lectura desde el dispositivo estándar. Se crea un objeto entrada de la clase FileReader en vez de InputStreamReader. El final del archivo viene dado cuando la función read devuelve -1. El resto del código es similar. Código 8.3.3. Lectura de un archivo Para mostrar el archivo de texto en la pantalla del monitor, se imprime el contenido del objeto str de la clase StringBuffer. System.out.println(str); Una vez concluido el proceso de lectura, es conveniente cerrar el flujo de datos, esto se realiza en una cláusula finally que siempre se llama independientemente de que se produzcan o no errores en el proceso de lectura/escritura. Programación orientada a objetos en java 179 Unidad VIII. Flujos y archivos El código completo de este ejemplo es el siguiente: Código 8.3.4. Lectura del archivo “archivo2.java” en pantalla. Ahora se crea un código completo que genera un archivo copia del original, es el siguiente: Programación orientada a objetos en java 180 Unidad VIII. Flujos y archivos Código 8.3.5. Crea una copia del archivo “archivo3.java”. Ejemplo 4. El siguiente ejemplo, genera un archivo de prueba que contiene números enteros aleatorios. Crea un archivo llamado prueba.dat. Programación orientada a objetos en java 181 Unidad VIII. Flujos y archivos Código 8.3.6 Genera un archivo de prueba *.dat y lo llena de números enteros. Ejemplo 5. La siguiente clase permite leer el archivo "prueba.dat" utilizando las clases FileReader y BufferReader: Código 8.3.7 Lee prueba.dat utilizando BufferReader y BufferReader. Programación orientada a objetos en java 182 Unidad VIII. Flujos y archivos 8.4 Ejercicios propuestos 8.4.1 Crear una aplicación que lea de teclado una oración dada por el usuario y que ésta se escriba en un archivo. 8.4.2 Crear una aplicación para mostrar directorios en una lista, que pida al usuario una ruta absoluta y luego muestre todos los nombres de archivos que haya dentro de ese directorio. Si alguno de los archivos es un directorio, debe imprimirse la palabra "dir" después de su nombre. 8.4.3 Construir un directorio de amigos. Considérese que la información más relevante para representar a un amigo es: nombre, teléfono, correo-e y fecha de cumpleaños. Utilizar objetos serializables para escribir y leer de archivo. 8.4.4 Escribir un método en que se abra un flujo, RandomAccessFile, en modo lectura (“r”) en el caso de que la operación lance una excepción abrir el archivo en modo lectura/escritura(“rw”). 8.4.5 Escribir un método para copiar un archivo. El método entrá dos argumentos de tipo cadena, el primero es el archivo original y el segundo es el archivo destino. Utilizar flujos FileInputStream y FileOutPutStream. 8.4.6 Se tiene un archivo de caracteres de nombre “SALAS.DAT”. Escribir un programa para crear el archivo “SALAS.BIN” con el contenido del primer archivo, pero en modo binario. 8.4.7 Utilizar los argumentos del método main() para dar entrada a dos cadenas; la primera representa una mascara. La segunda el nombre de un archivo de caracteres. El programa tiene que localizar las veces que ocurre la mascara en los archivos. 8.4.8 El archivo “numeros.dat” contiene enteros positivos y negativos. Escribir un programa que habrá un flujo DataInputStream para leer el archivo y determinar el número de enteros negativos. 8.4.9 Escribir un programa que elimine todos los archivos con la extensión *.txt directorio actual. Utilizar los métodos de la clase File. del 8.4.10 Escribir un programa que escriba por pantallas las líneas de caracteres de un archivo, numerando cada línea del mismo. Programación orientada a objetos en java 183 Resumen CONCLUSIONES Como ya se vio en este libro de texto, Java es un lenguaje orientado a objetos basado en C++ con atractivas características que lo convierten en uno de los más usados actualmente. Además, Gracias a su simplicidad y claridad en el manejo de conceptos orientados a objetos, Java ha crecido a tal magnitud que numerosas tecnologías y compañías han enfocado sus esfuerzos hacia este lenguaje. Tienes grandes ventajas: • • • • • • • • • • Simple. Java es simple dado que abstrae la complejidad intrínseca de C++ como el manejo de apuntadores y el reciclado de la memoria. Orientado a objetos. Java permite el diseño de aplicaciones basadas en objetos dado su mejor comprensión y definición en el diseño de interfaces y reusabilidad de componentes. Fácil uso en la red. Java facilita el acceso a la red por medio de TCP/IP con utilerías simples e intuitivas para comunicar por HTTP o FTP, por ejemplo. Robusto. Java pone énfasis en la detección de errores y bugs a temprana fase del desarrollo, específicamente en la compilación, que encontrarlos en tiempo de ejecución. Seguro. Java está orientado a la red y sistemas distribuidos, por lo que refuerza la seguridad en la transmisión por medios de comunicación por red. Neutral arquitectura. Debido a que Java está orientado a la red donde están involucrados diferentes CPU, diferentes sistemas operativos y arquitecturas, Java crea programas con un formato especial solo ejecutables por el ambiente Java. Portable. A diferencia de C++ donde no existe forma directa de migrar un programa de una plataforma a otra, cualquier programa en Java puede ser compilado y ejecutado en cualquier plataforma sin necesidad de modificar el código. Interpretado. Java crea programas en un formato especial conocido como bytecodes. Este formato debe ser ejecutado por un intérprete dependiente de la plataforma. Multihilado. Java permite la creación de procesos internos conocidos como hilos (threads) lo que permite la concurrencia de tareas y acceso a recursos. Dinámico. El lenguaje Java puede evolucionar o mejorarse liberando versiones que no impactan en los programas ya desarrollados por medio de la incrustación de librerías. En lo personal, creo que es un lenguaje con un enorme potencial y que en un futuro, si no es que ya esta pasando, dominara claramente el mercado de la programación. En hora buena creo hice muy bien al seleccionar este proyecto, ya que aprendí muchísimo elaborando este libro de texto, y además es muy gratificante saber que todo lo aprendido servirá como herramienta de apoyo para la materia de POO (Programación orientada a objetos). Programación orientada a objetos en java 184 Anexo I. Introducción al lenguaje Java ANEXO I. INTRODUCCIÓN AL LENGUAJE JAVA Programación orientada a objetos en java 185 Anexo I. Introducción al lenguaje Java ANEXO I. Introducción al Lenguaje Java 1.1 Introducción Hoy en día la informática invade más campos de la vida cotidiana, estando el ciudadano cada vez más familiarizado con términos del mundo informático, entre ellos, como los lenguajes de programación. A cualquier persona que haya empleado alguna vez un ordenador le resultará familiar alguno de estos nombres: C, Pascal, Cobol, Visual Basic, Java, Fortran y a una persona ya más introducida en ese mundillo posiblemente haya oído muchos otros: Oak, Prolog, Dbase, JavaScrip, Delphi, Simula, Smalltalk, Modula, Oberon, Ada, BCPL 77, Common LISP, Scheme. En la actualidad se podrían recopilar del orden de varios cientos de lenguajes de programación distintos, sino miles. Cabe hacerse una pregunta: ¿Para qué tanto lenguaje de programación?. Toda esta multitud de nombres puede confundir a cualquier no iniciado que haya decidido aprender un lenguaje, quien tras ver las posibles alternativas no sabe cual escoger, al menos entre los del primer grupo, que por ser más conocidos deben estar más extendidos. El motivo de esta disparidad de lenguajes es que cada uno ha sido creado para una determinada función, está especialmente diseñado para facilitar la programación de un determinado tipo de problemas, para garantizar seguridad de las aplicaciones, para obtener una mayor facilidad de programación, para conseguir un mayor aprovechamiento de los recursos del ordenador... Estos objetivos son muchos de ellos excluyentes: el adaptar un lenguaje a un tipo de problemas hará más complicado abordar mediante este lenguaje la programación de otros problemas distintos de aquellos para los que fue diseñado. El facilitar el aprendizaje al programador disminuye el rendimiento y aprovechamiento de los recursos del ordenador por parte de las aplicaciones programadas en este lenguaje. 1.2 Características de Java Estas preguntas ayudan a ver para que tipo de problemas está pensado Java: 1.2.1 Simple Es un lenguaje sencillo de aprender. Su sintaxis es la de C++ “simplificada”. Los creadores de Java partieron de la sintaxis de C++ y trataron de eliminar de este todo lo que resultase complicado o fuente de errores en este lenguaje. 77 Basic Combined Programming Language (Lenguaje de Programación Básico Combinado) Programación orientada a objetos en java 186 Anexo I. Introducción al lenguaje Java 1.2.2 Orientado a Objetos Posiblemente sea el lenguaje más orientado a objetos de todos los existentes; en Java todo, a excepción de los tipos fundamentales de variables (int, char, long...) es un objeto. 1.2.3 Distribuido Java está muy orientado al trabajo en red, soportando protocolos como TCP/IP, UDP, HTTP y FTP. Por otro lado el uso de estos protocolos es bastante sencillo comparándolo con otros lenguajes que los soportan. 1.2.4 Robusto El compilador Java detecta muchos errores que otros compiladores solo detectarían en tiempo de ejecución o incluso nunca. (ej: if(a=b) then ... el compilador Java no dejaría compilar este código. 1.2.5 Seguro Sobre todo un tipo de desarrollo: los Applet 78. Java garantiza que ningún Applet puede escribir o leer de un disco o mandar información del usuario que accede a la página a través de la red (por ejemplo, la dirección de correo electrónico). En general no permite realizar cualquier acción que pudiera dañar la máquina o violar la intimidad del que visita la página web. 1.2.6 Portable En Java no hay aspectos dependientes de la implementación, todas las implementaciones de Java siguen los mismos estándares en cuanto a tamaño y almacenamiento de los datos. Esto no ocurre así en C++, por ejemplo. En éste un entero, por ejemplo, puede tener un tamaño de 16, 32 o más bits, siendo la única limitación que el entero sea mayor que un short y menor que un long int. Así mismo C++ bajo UNIX almacena los datos en formato little endian, mientas que bajo Windows lo hace en big endian. Java lo hace siempre en little endian 79 para evitar confusiones. 1.2.7 Arquitectura Neutral Un applet es un componente de software que corre en el contexto de otro programa, por ejemplo un navegador web. 79 El término “Endian” se refiere a la forma en que los números binarios de bytes múltiples son guardados en la computadora. 78 Programación orientada a objetos en java 187 Anexo I. Introducción al lenguaje Java El código generado por el compilador Java es independiente de la arquitectura: podría ejecutarse en un entorno UNIX, Mac o Windows. El motivo de esto es que el que realmente ejecuta el código generado por el compilador no es el procesador del ordenador directamente, sino que este se ejecuta mediante una máquina virtual. Esto permite que los Applets de una web pueda ejecutarlos cualquier máquina que se conecte a ella independientemente de que sistema operativo emplee (siempre y cuando el ordenador en cuestión tenga instalada una máquina virtual de Java). 1.2.8 Rendimiento medio Actualmente la velocidad de procesado del código Java es semejante a la de C++, hay ciertos pruebas estándares de comparación80 en las que Java gana a C++ y viceversa. Esto es así gracias al uso de compiladores just in time, compiladores que traduce los bytecodes de Java en código para una determinada CPU 81, que no precisa de la máquina virtual para ser ejecutado, y guardan el resultado de dicha conversión, volviéndolo a llamar en caso de volverlo a necesitar, con lo que se evita la sobrecarga de trabajo asociada a la interpretación del bytecode. No obstante por norma general el programa Java consume bastante más memoria que el programa C++, ya que no sólo ha de cargar en memoria los recursos necesario para la ejecución del programa, sino que además debe simular un sistema operativo y hardware virtuales (la máquina virtual). Por otro lado la programación gráfica empleando las librerías Swing es más lenta que el uso de componentes nativos en las interfaces de usuario. En general en Java se ha sacrificado el rendimiento para facilitar la programación y sobre todo para conseguir la característica de neutralidad arquitectural, si bien es cierto que los avances en las máquinas virtuales remedian cada vez más estas decisiones de diseño. 1.2.9 Multithread Soporta de modo nativo los threads 82, sin necesidad del uso de librerías específicas (como es el caso de C++). Esto le permite además que cada Thread de una aplicación java pueda correr en una CPU distinta, si la aplicación se ejecuta en una máquina que posee varias CPU. Las aplicaciones de C++ no son capaces de distribuir, de modo transparente para el programador, la carga entre varias CPU. 1.3 Java frente a los demás lenguajes Java es un lenguaje relativamente moderno. Su primer uso en una “tarea seria” de programación fue la construcción del navegador HotJava por parte de la empresa Sun en mayo de 1995, y fue a principios de 1996 cuando Sun distribuye la primera versión de 80 Benchnarks. Unidad de Proceso Central. 82 Hilos. 81 Programación orientada a objetos en java 188 Anexo I. Introducción al lenguaje Java Java. Es esta corta edad lo que hace que Java esté más orientado al mundo web, que no existía cuando, por ejemplo, C fue desarrollado. También es esto lo que ha hecho que soporte de modo nativo (no mediante el uso de librerías, como C) los threads, siendo posible aprovechar las ventajas de los sistemas multiprocesadores. Las ventajas fundamentales de Java frente a otros lenguajes son el menor periodo de aprendizaje por parte del programador, llegando a ser un programador productivo en menos tiempo (sencillez) y siendo posible desarrollar aplicaciones más rápido que en otros lenguajes (sencillez y robustez), lo cual se traduce en el mundo empresarial en un ahorro de costos de desarrollo. Sus cualidades de distribuido, seguro e independencia de la plataforma lo hacen ideal para aplicaciones relacionadas con el mundo web; precisamente a esto es a lo que Java debe su gran difusión y fama. El hecho de que sea independiente de la máquina y del sistema operativo permite que distintas máquinas con distintos sistemas operativos se conecten a una misma página web y ejecuten los mismos applets. Además la seguridad que garantiza Java para los applets impiden que alguien trate de averiguar información sobre los usuarios que se conectan a la página web o intente dañar sus máquinas. En cuanto a su capacidad de soporte de threads y su capacidad de sacarle partido a sistemas multiprocesador lo convierten en un lenguaje más “orientado hacia el futuro “. Estas cualidades podrían dar pie a que algún día los rendimientos computacionales de Java sean comparables con los de C++ y otros lenguajes que hoy son computacionalmente más eficientes. 2 j2sdk, java 2 standard development kit Distribuido por Sun 83, jdk. Dicho entorno de programación es suministrado por Sun de forma gratuita, este se puede encontrar en la dirección web: http://Java.sun.com/j2se/. Es de consenso que el entorno jdk no es el más adecuado para el desarrollo de aplicaciones Java, debido a funcionar única y exclusivamente mediante comandos de consola, ya que hoy en día la programación se suele ayudar de entornos visuales, como JBuilder, JCreator o muchos otros, que facilitan enormemente la tarea. Sin embargo, puede ser un entorno bastante útil para aprender el lenguaje, ya que aunque los entornos visuales nos hagan mucho trabajo siempre es necesario ir al código para modificarlo y obtener el comportamiento deseado, lo cual quiere decir que se necesita dominar el lenguaje y es más fácil llegar a este dominio escribiendo códigos completos en un entorno “hostil” que ayuda, que simplemente remodelando códigos ya generados por entornos visuales. 83 Pagina Oficial de Java. Programación orientada a objetos en java 189 Anexo I. Introducción al lenguaje Java 1.4 Como Compilar y Ejecutar Programas en Java 1.4.1 Javac Es el comando compilador de Java. Su sintaxis es: Javac ejemplo.Java La entrada de este comando ha de ser necesariamente un archivo que contenga código escrito en lenguaje Java y con extensión .Java. El comando nos creará un archivo .class por cada clase que contenga el archivo Java. Los archivos .class contienen código bytecode, el código que es interpretado por la máquina virtual Java. 1.4.2 Java Es el intérprete de Java. Permite ejecutar aplicaciones que previamente hayan sido compiladas y transformadas en archivos .class. Su sintaxis es: Java ejemplo No es necesario aquí suministrar la extensión del archivo, ya que siempre ha de ser un archivo .class. 1.4.3 Appletviewer Se trata de un comando que verifica el comportamiento de un applet. La entrada del comando ha de ser una página web que contenga una referencia al applet que se desea probar. Susintaxis es: Appletviewer supagina.html El comando ignora todo el contenido de la página web que no sean applets y se limita a ejecutarlos. Un ejemplo de página web “mínima” para poder probar un applet llamado myapplet.class sería: <HTML> <TITLE>My Applet </TITLE> <BODY> <APPLET CODE=”myapplet.class” WIDTH=180 HEIGHT=180> </APPLET> </BODY> </HTML> Programación orientada a objetos en java 190 Anexo I. Introducción al lenguaje Java Javadoc Este útil comando permite generar documentación en formato html sobre el contenido de archivos con extensión .Java. Su sintaxis es: Javadoc ejemplo.Java En la documentación generada por este comando se puede ver que métodos y constructores posee una determinada clase, junto con comentarios sobre su uso, si posee inner 84 classes, la versión y el autor de la clase.... 1.5 Estructuras de Datos Básicas en Java En este tema se tratarán las estructuras básicas de Java de datos, sus tipos y las variables, operadores. Aquellos que estén familiarizados con C, o C++, no encontrarán prácticamente nada nuevo en este tema, ya que, como se ha dicho en el primer tema, Java hereda toda su sintaxis de C, pudiendo considerarse la sintaxis de Java una versión simplificada de la de C. Sólo las operaciones con Strings pueden resultar un poco novedosas. 1.5.1 Tipos de Datos En Java toda variable declarada ha de tener su tipo, y además antes de poder emplearla se ha de inicializar a un valor, si no el compilador se quejará y no generará los archivos .class. Esto por ejemplo en C no es necesario, siendo fuente de muchos errores al emplearse en operaciones variables que se olvidan de inicializar. A continuación se pasan a describir los tipos de datos: 1.5.2 Enteros Almacenan como su propio nombre indica números enteros, sin parte decimal. Cabe destacar, como ya se indicó en el primer tema, que por razones de portabilidad todos los datos en Java tienen el mismo tamaño y formato. En Java hay cuatro tipos de enteros: 84 Clases Embebidas. Programación orientada a objetos en java 191 Anexo I. Introducción al lenguaje Java 1.5.3 Tipos de Datos Enteros en Java Tipo Int Short Long Byte Tamaño (bytes) Rango 4 -2147483648 a 2147483647 2 -32768 a 32767 8 1 -9.223.372.036.854.775.808 a 9.223.372.036.854.775.807 -128 a 127 1.5.4 Tipos de Datos Reales en Java Almacenan número reales, es decir números con parte fraccionaria. Hay dos tipos: Tipo Tamaño (bytes) Rango 4 + 3.40282347E+38 8 + 179769313486231570E+308 Float Double 1.5.5 Caracteres En Java hay un único tipo de carácter: char. Cada carácter en Java esta codificado en un formato denominado Unicote 85, en este formato cada caracter ocupa dos bytes, frente a la codificación en ASCII, donde cada caracter ocupaba un solo byte. Unicode es una extensión de ASCII 86, ya que éste último al emplear un byte por caracter solo daba acogida a 256 símbolos distintos. Para poder aceptar todos los alfabetos (chino, japonés, ruso...) y una mayor cantidad de símbolos se creó el formato Unicode. Es un estándar industrial cuyo objetivo es proporcionar el medio por el cual un texto en cualquier forma o idioma puede ser codificado. 85 Programación orientada a objetos en java 192 Anexo I. Introducción al lenguaje Java En Java al igual que en C se distingue la representación de los datos char frente a las cadenas de caracteres. Los char van entre comillas simples: char ch = ‘a’, mientras que las cadenas de caracteres usan comillas dobles. 1.5.6 Boolean Se trata de un tipo de dato que solo puede tomar dos valores: true y false. Es un tipo de dato bastante útil a la hora de realizar chequeos sobre condiciones. En C no hay un dato equivalente y para suplir su ausencia muchas veces se emplean enteros con valor 1 si “true” y 0 si “false”. Otros lenguajes como Pascal sí tiene este tipo de dato. 1.5.7 Variables Al igual que C, Java requiere que se declaren los tipos de todas las variables empleadas. La sintaxis de iniciación es la misma que C: int i; Sin embargo, y a diferencia que en C, se requiere inicializar todas las variables antes de usarlas, si no el compilador genera un error y aborta la compilación. Se puede inicializar y asignar valor a una variable en una misma línea: int i = 0; Asignación e inicialización pueden hacerse en líneas diferentes: int i ; i = 0; Es posible iniciar varias variables en una línea: int i, j,k=10; Después de cada línea de código, bien sea de iniciación o de código, al igual que en C va un ;. En Java, al igual que en todo lenguaje de programación hay una serie de palabras reservadas que no pueden ser empleadas como nombres de variables (if, int, char, else, goto....); alguna de estas se emplean en la sintaxis del lenguaje, otras, como goto no se emplean en la actualidad pero se han reservado por motivos de compatibilidad por si se emplean en el futuro. Los caracteres aceptados en el nombre de una variable son los comprendidos entre “A-Z”, “az”, _, $ y cualquier carácter que sea una letra en algún idioma. 86 Acrónimo de American Standard Code for Information Interchange. Programación orientada a objetos en java 193 Anexo I. Introducción al lenguaje Java 1.6 Entornos de Desarrollo de Java El propósito de esta sección es informar al lector de algunas de los entornos de desarrollo IDE 87 existentes para el lenguaje Java. 1.6.1 Bluej http://www.bluej.org/ Es el IDE que, sin duda, se recomienda al lector para que empiece a dar sus primeros pasos en Java. Esta herramienta freeware ha sido diseñada para introducir al estudiante en la programación orientada a objetos. Es un IDE “ligera”, posee la funcionalidad básica de cualquier IDE: editor, compilador y depurador. Siempre visualiza el código del proyecto en UML 88, mostrando las clases con las relaciones de herencia y dependencias entre ellas. Posee ciertas capacidades para “jugar” con las clases permitiendo la creación de objetos, así como “jugar” con ellos invocando sus métodos, viendo el valor que toman sus variables… Estas cualidades carecen de valor para un programador experto, pero permiten a un programador novato familiarizarse con los conceptos de objeto, método, variable, herencia… a diferenciar entre objeto y clase… No sólo se recomienda esta IDE a los que empiecen a trabajar con java por su orientación didáctica; se hace además porque se considera que cualquier IDE “pesada”, no es apta para aprender a programar. Su elevada Complejidad, si bien es fácilmente compensada con las opciones que ofrecen a un programador veterano, no lo es en un programador novato, quien ya tiene bastante con pelearse con el lenguaje para además tener que hacerlo con el IDE. Por otro lado los asistentes y el elevado grado de automatización de algunas tareas en los IDE pesados hacen que el programador novato haga cosas sin saber realmente lo que está haciendo, con lo cual, si bien logra hacer pequeñas aplicaciones, su aprendizaje estará plagado de lagunas. Para un programador experto que domina las bases del lenguaje en unos días son más que suficientes para sentirse cómodo en un nuevo IDE. 1.6.2 Jcreator http://www.jcreator.com/ Se trata de una IDE ligera, no posee, por ejemplo, capacidades para desarrollo de aplicaciones de modo gráfico. Sin embargo es interesante ya que requiere máquinas poco potentes para ejecutarse, un Pentium 2 a 300 con 64 megas de RAM sería más que suficiente, mientras que las siguientes, e incluso BlueJ, requieren equipos bastante más potentes. No obstante, a diferencia de BlueJ este producto si es un IDE que puede ser empleado por un programador profesional (BlueJ pierde todo interés más allá de la docencia), con lo 87 88 Ambientes de Desarrollo Integrado (Integrated Development Environment). Lenguaje de Modelado Unificado. Programación orientada a objetos en java 194 Anexo I. Introducción al lenguaje Java cual los que empiecen directamente con ella podrán “aguantar” más que los que se decidan por BlueJ. Es un IDE comercial, con dos versiones: JCreator : gratis. JCreator Pro : Aproximadamente 69$ (licencia de estudiante). 1.6.3 Jbuilder Http://www.borland.com/jbuilder/ Desde el punto de vista de algunos expertos esta es la mejor IDE para Java del mercado. Requiere máquinas potentes, la última versión (llamada X por Borland) se arrastra bastante con un equipo inferior a un Pentium III a 800 con 512 megas de RAM. Posee capacidades de desarrollo visual de aplicaciones, así como múltiples asistentes y está integrada con una gran cantidad de herramientas, unas Opensource 89(Ant, CVS, Struts Tomcat…) y otras propietarias (Borland Aplication Server, JDataStore, ClearCase, Visual SouceSafe …). Nuevamente es comercial, y los precios de las versiones no gratuitas son bastante prohibitivos: JBuilder X Persona: gratis JBuilder X Developer: $ JBuilder X Enterprise : $ Existen versiones gratuitas de evaluación por 30 días de todos los productos. 1.6.4 Netbeans http://www.netbeans.org/ Ofrece una funcionalidad más limitada que JBuilder, y es bastante menos intuitivo de manejar, en especial para los programadores novatos. Sin embargo es la herramienta gratuita (Open Source) que más funcionalidad tiene. Permite el diseño de aplicaciones de modo visual. Esta herramienta Open Source está respaldada por Sun Microsystems, empresa que construye su IDE para java, Sun ONE Studio. Si el aprendiz no puede permitirse una herramienta comercial, como JBuilder, y quiere más de lo que da JCreator, esta es, la mejor alternativa en este momento. 1.6.5 Eclipse http://www.eclipse.org/ NetBeans es un proyecto de código abierto de gran éxito con una gran base de usuarios, una comunidad en constante crecimiento, Sun MicroSystems fundó el proyecto de código abierto NetBeans en junio 2000 y continúa siendo el patrocinador principal de los proyectos. A día de hoy hay disponibles dos productos: el NetBeans IDE y el NetBeans Plataform. 89 Código Abierto. Programación orientada a objetos en java 195 Anexo I. Introducción al lenguaje Java El NetBeans IDE es un entorno de desarrollo - una herramienta para programadores pensada para escribir, compilar, depurar y ejecutar programas. Está escrito en Java - pero puede servir para cualquier otro lenguaje de programación. Existe además un número importante de módulos para extender el IDE NetBeans. El IDE NetBeans es un producto libre y gratuito sin restricciones de uso. También disponible está el NetBeans Platform; una base modular y extensible usada como una estructura de integración para crear aplicaciones de escritorio grandes. Empresas independientes asociadas, especializadas en desarrollo de software, proporcionan extensiones adicionales que se integran fácilmente en la plataforma y que pueden también utilizarse para desarrollar sus propias herramientas y soluciones. Ambos productos son de código abierto y gratuito para el uso tanto comercial y como no comercial. El código fuente está disponible para su reutilización de acuerdo con el Desarrollo Cómun y Licencia de Distribución 90. Es una IDE extensible en base a plugins 91. Esta filosofía, junto con la actividad que muestra su comunidad convierten a esta herramienta en toda una promesa de futuro. Sin embargo, desde el punto de vista del autor, al día de hoy es una herramienta menos completa que NetBeans, no proporcionando por ejemplo capacidad para el desarrollo visual de aplicaciones. Por otro lado hay ciertos puntos, como la instalación, que están un poco descuidados, y su filosofía en base a plugins obliga al usuario a buscar y descargarse varios plugins o resignarse a carecer de bastante funcionalidad. No obstante esta herramienta posee algunas capacidades, como el refactoring, no presentes en NetBeans en el momento de escribir estas líneas. Además es más pedagógico “construir” su propia IDE en base a plugins que descargarse un paquete donde ya se da todo hecho. Common Development and Distribution License (CDDL). Son pequeños programas que se instalan sobre los navegadores y que confieren posibilidades multimedia al mismo. Identifican el archivo multimedia usando el código MIME: Multipurpose Internet Mail Extensions (Extensiones de correo Internet Multipropósito). 90 91 Programación orientada a objetos en java 196 Anexo II. El entorno de desarrollo Eclipse ANEXO II. EL ENTORNO DE DESARROLLO ECLIPSE 2.1. ¿Que es Eclipse? Eclipse es una plataforma de desarrollo open source basada en Java. Es un desarrollo de IBM cuyo código fuente fue puesto a disposición de los usuarios. En sı mismo Eclipse es un marco y un conjunto de servicios para construir un entorno de desarrollo a partir de componentes conectados (plug-in). Hay plug-ins para el desarrollo de Java (JDT Java Development Tools) así como para el desarrollo en C/C++, COBOL, etc. La versi´on instalada en el laboratorio incluye el plug-in JDT. 2.2. Trabajando con Eclipse Al ejecutar Eclipse aparece una ventana como la mostrada en la figura 17. Eclipse contiene una serie de perspectivas. Cada perspectiva proporciona una serie de funcionalidades para el desarrollo de un tipo específico de tarea. Por ejemplo la perspectiva Java combina un conjunto de views que permiten ver información útil cuando se esta escribiendo código fuente, mientras que la perspectiva de depuración contiene vistas que muestran información útil para la depuración de los programas Java. La barra de herramientas vertical (en la parte izquierda) muestra las perspectivas abiertas y permite pulsando sobre ellas cambiar de una a otra. La perspectiva activa se muestra en la barra del titulo en la parte superior de la ventana. 2.2.1. Creación de un proyecto Eclipse permite organizar los ficheros en forma de proyecto. Para crear un proyecto Java se procede del siguiente modo: 1. Seleccionando en el menú File − New − Project o pulsando con el botón derecho del Ratón sobre la vista Navigator en la perspectiva Resource y seleccionando New − Project. 2. Aparece una ventana en la que se puede seleccionar el tipo de proyecto. En este caso se pulsa sobre Java en la parte izquierda y Java project en la derecha. Pulsar sobre el botón Next . Ver la figura 18 3. A continuación se piden los datos sobre el proyecto (nombre y ruta donde se almacenarán los ficheros asociados al proyecto). Una vez introducidos se pulsa sobre finish. Véase la figura 19. 4. Eclipse abre automáticamente la perspectiva Java cuando se crea un proyecto Java. Se crea el directorio especificado con dos ficheros .project y .classpath que contienen información sobre el proyecto. Listado 1: Código generado utilizando el asistente Programación orientada a objetos en java 197 Anexo II. El entorno de desarrollo Eclipse /∗ ∗ Created on “fecha” ∗ ∗ To change the template for this generated f i l e go to ∗ Window>Preferences>Java>Code Generation>Code and Comments _/ /_∗ Clase ∗ @author juan ∗ ∗ Descripcion : _/ public class HolaMundo { public static void main( String [ ] args ) {} Perspectiva Java contienen un directorio con el nombre del proyecto. Como se comento anteriormente, se puede pasar de una perspectiva a otra pulsando sobre los botones de la barra vertical izquierda. 5. Hay una perspectiva mas asociada con Java, se puede abrir del siguiente modo: Window − Open perspective − Java browsing. En esta perspectiva aparecen vistas correspondientes al proyecto, a paquetes, a clases e interfaces y a los miembros de estas. 2.2.2. Creando clases. Las clases se pueden crear de dos formas diferentes: Utilizando un asistente. Se pulsa con el botón derecho sobre el proyecto − New − Class. Aparece una ventana como la que se muestra en la figura 20. Los campos interesan por ahora son: el nombre de la clase, el modificador, y si se quiere que esta clase tenga un método main(String[] args). Al pulsar sobre Finish (con los datos que se muestran en la figura 21 se crea un fichero HolaMundo.java con el código mostrado en el listado 1 (no será exactamente asi ya que yo he personalizado el comentario que aparece). Si se explora el contenido del directorio c:\tmp\prac1 se vera que además del fichero HolaMundo.java hay otro fichero HolaMundo.class, este fichero es el que contiene el código compilado a partir de HolaMundo.java (Eclipse compila el proyecto cada vez que se guarda). Escribiendo directamente toda la clase: se pulsa con el botón derecho sobre el proyecto − New − File . Se abre una ventana como la que se muestra en la figura 21 donde hay que poner el nombre del fichero. Al pulsar sobre Finish se crea un fichero vació con el nombre HolaMundo.java. No se tiene una preferencia por alguno de los dos métodos expuestos. Aunque, como recomendación, al principio es conveniente escribir toda la clase para saber lo que se esta haciendo. Programación orientada a objetos en java 198 Anexo II. El entorno de desarrollo Eclipse El asistente simplemente a creado un esqueleto ahora falta completar el código. Se añaden las líneas necesarias para completar el programa. El resultado se muestra en la figura 22. 2.2.3. Ejecutando el programa Los programas se pueden ejecutar dentro de Eclipse. Con la perspectiva Java abierta, seleccionar en el menu: Run − Run... En la ventana que se abre, pulsar 2 veces sobre Java Application . En el panel Main se rellena la información tal y como aparece en la figura 23. Puesto que en el ejemplo propuesto se necesitan argumentos se pulsa sobre el panel Arguments y se pone el argumento que se va a pasar al programa. Un ejemplo se muestra en la figura 24. Una vez proporcionada la información necesaria (hay más paneles pero por ahora no es necesario conocerlos) se pulsa sobre el botón Run. Este programa simplemente muestra un mensaje por consola. La consola esta integrada (es una vista más) dentro de Eclipse. Así tras ejecutar el programa, en la vista Consola se vera lo que se muestra en la figura 24. Cuando el programa no requiere ningún ajuste especial (por ejemplo, no se requiere el paso de argumentos) se puede hacer de forma más rápida pulsando sobre Run − Run as − Java Application . Hay barras de tareas para el acceso rápido a algunas funciones (entre ellas ejecutar). La figura 25 muestra algunas de ellas. 2.2.4. Depuración de programas. Dentro del entorno de desarrollo de Eclipse se pueden depurar programas desarrollados en Java. Window − Open perspective − Debug . Aparecen una serie de vistas similares a las de la figura 26. Una vez se ha abierto la perspectiva de depuración se puede parar la ejecución del programa en una determinada línea (poniendo un breakpoint) e inspeccionar las variables locales. Para poner un breakpoint, en la vista donde se encuentra el código fuente se seleccionas la línea donde se quiere que se detenga la ejecución y se selecciona en el menu Run − Add/remove Breakpoint . Se verá que se muestra un punto azul en la parte izquierda de la línea. Ahora ya se puede lanzar el depurador. Se selecciona en el menú Run − Debug . La ejecución del programa se detiene en el primer breakpoint. Una vez el programa esta detenido, en una de las vistas se puede ver el valor de las variables o ver los breakpoints que se ha definido. En la figura 27 se Programación orientada a objetos en java 199 Anexo II. El entorno de desarrollo Eclipse muestra el programa detenido en una línea y se muestra la vista Variables con el contenido de una variable local. Una vez inspeccionado el código donde esta el problema se puede optar por ejecutar el programa hasta que termine ( Run − Resume ) o terminar el programa inmediatamente (Run − Terminate ) 2.2.5. Otras herramientas interesantes. El editor de Java ofrece correcciones a problemas encontrados mientras se escribe el código y tras compilar. El editor muestra que existen propuestas para la corrección de un problema o aviso mediante una bombilla visible en la parte izquierda del editor. Si se pulsa con el botón izquierdo sobre esta bombilla (o también mediante ( Edit − Quick Fix ) se muestran las propuestas para el problema en la posición del cursor. Si se desean importar recursos a un proyecto se puede realizar del siguiente modo: en la vista Navigator se pulsa sobre el botón derecho y aparece un menú, se selecciona Import y aparece una ventana desde la que se puede seleccionar el directorio donde están los recursos y cuales se desean incorporar al proyecto. Desde un proyecto se pueden exportar todos o algunos de los ficheros que lo conforman. Para ello en la vista Navigator se pulsa sobre el botón derecho y aparece un menú, se selecciona Export y aparece una ventana en la que podemos indicar como se va a exportar (un fichero zip, tal cual aparecen en el proyecto, ...). La siguiente ventana sirve para seleccionar los ficheros que se desean exportar y a donde. Si se coloca el ratón sobre un método (sin pulsar) se muestra la declaración del método (que devuelve y que parámetros acepta). Si se coloca el ratón (sin pulsar) sobre una variable aparece información sobre el tipo de la variable. Al escribir código se puede pulsar Ctrl + espacio y aparece un menú con posibles formas de finalizar la sentencia que se esta escribiendo. Un ejemplo se muestra en la figura 28. Figura 17: Primera pantalla de Eclipse Programación orientada a objetos en java 200 Anexo II. El entorno de desarrollo Eclipse Figura 18: Selección del tipo de proyecto en Eclipse Figura 19: Datos sobre el proyecto en Eclipse Programación orientada a objetos en java 201 Anexo II. El entorno de desarrollo Eclipse Figura 20: Creación de una clase en Eclipse Figura 21: Creación de un fichero vació en Eclipse. Programación orientada a objetos en java 202 Anexo II. El entorno de desarrollo Eclipse Figura 22: La perspectiva Java con las vistas mostrando una clase en Eclipse Figura 23: Configuración para ejecutar la aplicación: proyecto y clase con el main en Eclipse. Programación orientada a objetos en java 203 Anexo II. El entorno de desarrollo Eclipse Figura 24: Argumentos para enviar al programa y argumentos para enviar a la Maquina Virtual en Eclipse Figura 25: Consola con el resultado de la ejecución en Eclipse. Programación orientada a objetos en java 204 Anexo II. El entorno de desarrollo Eclipse Figura 26: Barra de Herramientas de Eclipse. Figura 27: La perspectiva debug en Eclipse. Programación orientada a objetos en java 205 Anexo II. El entorno de desarrollo Eclipse Figura 28: La perspectiva debug en acción en Elipse. Figura 29: Asistente de código (pulsando Ctrl + espacio) en Eclipse. Programación orientada a objetos en java 206 Referencias bibliográficas BIBLIOGRAFIA 1. Taylor David. Object Orient informations systems, planning and implementations. Ed. Ed. Wiley, Canada, 1992. 2. Larman Craig. UML y patrones introducción al análisis y diseño orientado a objetos. Ed. Pretince Hall, México, 1999. 3. Winblad, Ann L. Edwards, Samuel R. Software orientado a objetos. Ed. Addison. Wesley/ Díaz Santos USA, 1993. 4. Deitel & Deitel. Java how to program. Ed. Prentice Hall. 5. Fco. Javier Ceballos. Java 2 Curso de Programación. Ed. Alfaomega. 6. Agustín Froufe. Java 2 Manual de usuario y tutorial. Ed. Alfaomega. 7. Laura Lemay, Rogers Cadenhead. Aprendiendo JAVA 2 en 21 días. Ed. Prentice Hall. 8. Herbert Schildt. Fundamentos de Programación en Java 2. Ed. McGrawHil. 9. J Deitel y Deitel. Como programar en Java. Ed. Prentice Hall. 10. Stephen R. Davis. Aprenda Java Ya. Ed. McGrawHill. 11. Kris Jamsa Ph D.. ¡Java Ahora! Ed. McGrawHill.. 12. Ken Arnold, James Gosling, David colmes Programación orientada a objetos en java 207 Referencias bibliográficas “El Lenguaje de Programación Java” Tercera Edción Pearson Educación, Madrid. 13. Abraham Otero Java 2 http://www.javahispano.org/licencias/ 14. Javier García de Jalón,José Ignacio Rodríguez,Iñigo Mingo,Aitor Imaz,Alfonso Brazales, Alberto Larzabal, Jesús Calleja,Jon García Aprenda Java Como si Fuera en Primero Editada por Javier García de Jalón ([email protected] y [email protected]) 15. Philip Heller, Simon Roberts Complete Java® 2 Certification: Study Guide Fifth Edition 16. La Biblia del Java 2 – Español Anaya Multimedia 16. http:// www.javasoft.com 17. http:// www.javaworld.com 18. http:// www.prenhall.com/deitel 19. http://www.javahispano.org/ 20. http://www.java.net/ Programación orientada a objetos en java 208