Download Apuntes de Java
Document related concepts
no text concepts found
Transcript
Apuntes de Java Departamento de Ciencia de la Computación e Inteligencia Artificial Escuela Politécnica Superior Universidad de Alicante 4 de octubre de 2001 2 Índice General Índice General i Prólogo iii 1 Introducción 1.1 Tipos de datos 1.2 Constantes 1.3 Variables 1.4 Comentarios 1 1 2 2 2 2 Estructuras de control 2.1 Condicionales 2.1.1 Sentencia if 2.1.2 Sentencia switch 2.2 Bucles 2.2.1 Sentencia while 2.2.2 Sentencia do .. while 2.2.3 Sentencia for 2.3 Sentencias try .. catch 3 3 3 3 4 4 4 4 5 3 Clases y Objetos 3.1 Clases 3.1.1 Métodos 3.1.2 Variables y métodos de clase 3.2 Control de acceso a los miembros de una clase 3.2.1 Acceso predeterminado 3.2.2 Acceso público 3.2.3 Acceso privado 3.2.4 Acceso protegido 3.3 Objetos 3.3.1 Asignación 3.3.2 Igualdad 3.4 Herencia 3.4.1 Subclases 3.4.2 Clases abstractas 7 7 8 8 9 9 9 9 9 10 10 10 11 11 12 4 Packages 4.1 Introducción 4.2 Crear un paquete 4.3 Referenciar un paquete 4.4 Los paquetes y la visibilidad de variables y métodos 13 13 13 14 14 i ii Índice General 5 Tratamiento de errores 5.1 Excepciones y errores 5.2 Manejo de excepciones 5.2.1 Capturar excepciones 5.2.2 Lanzar excepciones 5.2.3 Generar excepciones 5.2.4 Definir nuevas excepciones 5.3 El requisito "capturar o lanzar" 17 17 17 18 18 19 19 19 6 Librerías estándar de Java 6.1 Operaciones matemáticas: java.lang.Math 6.2 Operaciones de entrada/salida: java.io 6.2.1 E/S de bytes y caracteres 6.2.2 E/S de más alto nivel 6.2.3 Entrada y salida estándar 21 21 21 21 22 22 7 Uso del JDK 7.1 Uso del classpath 7.2 El compilador javac 7.3 El intérprete java 23 23 24 24 8 Bibliografía 25 Prólogo A ctualmente Java es el lenguaje de programación más popular en Internet. Eso no nos tiene que hacer olvidar que está disponible para el desarrollo de programas de uso general. A pesar de que su popularidad se deba a la gran expectación que implica cualquier cosa relacionada con Internet, no hay que olvidar sus ventajas: • Es multiplataforma (funciona en muchas máquinas). • Es un lenguaje orientado a objetos bastante simple. • Permite que un programador principiante produzca programas con un interfaz de usuario sofisticado: botones, listas, barras de desplazamiento. • Está integrado con la web (applets) • Es adecuado para crear aplicaciones de bases de datos. En el mercado hay muchas herramientas de programación Java como Forte de Sun o Visual Café de Symantec. De todas formas, la mejor forma de introducirse en Java es a través del kit de desarrollo de Java (JDK). iii iv Prólogo Capítulo 1 Introducción 1.1 Tipos de datos Los tipos de datos son portátiles entre todas las plataformas que reconocen Java. Se clasifican en tipos primitivos y tipos referenciados. Tipos primitivos Hay ocho tipos que se pueden clasificar en tipos numéricos y el tipo boolean. Los tipos numéricos se pueden dividir en tipos enteros y tipos reales. Se les llama tipos primitivos porque están integrados en el sistema y no son objetos, esto hace que su uso sea más eficiente. De todas formas, la biblioteca de Java proporciona las clases: Byte, Character, Short, Integer, Long, Float, Double y Boolean para encapsular cada uno de los tipos. • Byte: datos enteros comprendidos entre -128 y +127. • Short: datos enteros comprendidos entre -32678 y +32367. • Int: datos enteros comprendidos entre -2147483648 y +2147483647. • Long: datos enteros comprendidos entre -9223372036854775808 y +9223372036854775807. • Char: se utiliza para declarar datos enteros en el rango \u0000 .. \uF F F F en Unicode (0 a 65535). Los valores de 0 a 127 se corresponden con los caracteres ASCII del mismo código. • Float: datos en coma flotante de 32 bits. Para especificar que una constante es de tipo float hay que añadir al final de su valor la letra ’f’ o ’F’. • Double: datos en coma flotante de 64 bits. Una constante es de tipo double a no ser que se especifique lo contrario. • Boolean: se utiliza para indicar si el resultado de la evaluación de una expresión booleana es verdadero o falso. Los dos posibles valores son true y false. Tipos referenciados Hay tres categorías: clases, interfaces y arrays. 1 2 Capítulo 1. Introducción 1.2 Constantes Hay que indicar al compilador Java el nombre de la constante y su valor. Esto se hace utilizando el calificador final y/o el static. final static int cte=3.14159F; La constante puede estar definida en el cuerpo de la clase o dentro de un método. En el primer caso la constante puede estar calificada además de con final, con static. En el segundo caso, no se puede utilizar static, la constante sólo será visible dentro del método, es local al método. Una constante local no puede ser declarada static. 1.3 Variables Una variable representa un espacio de memoria para almacenar un valor de un determinado tipo. La declaración de una variable consiste en definir el nombre de la misma y asignarle un tipo: String nombre; Cuando la variable está declarada en el cuerpo de la clase pero fuera de cualquier método se denomina variable miembro de la clase. Caso de que esté declarada dentro de un método es local a dicho método. Las variables miembro de una clase se inicializan por defecto para cada objeto que se declare de la misma: las variable numéricas a 0, los caracteres con el valor ’\0’ y las referencias a otros objetos con null. Las variables locales no se inicializan por defecto con lo que hay que asignarles un valor inicial. 1.4 Comentarios Java soporta tres tipos de comentarios: • Comentario tradicional: empieza con los caracteres /* y finaliza con los caracteres */. Estos comentarios pueden ocupar más de una línea. • Comentario de una sola línea: comienza con una doble barra y se extiende hasta el final de la línea. • Comentario de documentación: comienza con /** y termina con */. Son comentarios especiales que javadoc utiliza para generar la documentación acerca del programa. Capítulo 2 Estructuras de control 2.1 2.1.1 Condicionales Sentencia if Permite a un programa tomar una decisión para ejecutar una acción u otra basándose en el resultado verdadero o falso de una expresión. La sintaxis es: if (condición) sentencia 1; else sentencia 2; Como consecuencia de las sentencias if anidadas aparece la sentencia else if: if (condición 1) sentencia 1; else if (condición 2) sentencia 2; else if (condición 3) sentencia 3; else sentencia n; 2.1.2 Sentencia switch Permite ejecutar una de varias acciones en función del valor de una expresión. Es una sentencia para decisiones múltiples. La sintaxis es: switch (expresión) { case expresión-constante 1: sentencia 1; case expresión-constante 2: sentencia 2; default: sentencia n; } 3 4 Capítulo 2. Estructuras de control donde expresión es una expresión de tipo char, byte, short o int. La sentencia switch evalúa la expresión entre paréntesis y compara su valor con las constantes de cada case. La ejecución de las sentencias comienza en el case cuya constante coincide con el valor de la expresión y continúa hasta el final de la estructura switch o hasta una sentencia que transfiera el control fuera del bloque, por ejemplo la sentencia break. En una sentencia switch se pueden hacer declaraciones en el bloque de cada case pero no antes del primer case. switch (n) { case 1: System.out.println("Positivo"); break; case -1: System.out.println("Negativo"); break; default: System.out.println("Desconocido"); } 2.2 2.2.1 Bucles Sentencia while Ejecuta una sentencia, simple o compuesta, cero o más veces, dependiendo del valor de una expresión booleana. Su sintaxis es: while (condición) sentencia; ta. donde condición es cualquier expresión booleana y sentencia puede ser simple o compues- 2.2.2 Sentencia do .. while Ejecuta una sentencia, simple o compuesta, una o más veces, dependiendo del valor de una expresión booleana. Su sintaxis es: do sentencia; while (condición); 2.2.3 Sentencia for Permite ejecutar una sentencia simple o compuesta un número de veces conocido. Su sintaxis es: for (v1=e1, v2=e2;condición;progreso-condición) sentencia; donde v1, v2, ... representan variables de control que serán iniciadas con las expresiones e1, e2, ... 2.3. Sentencias try .. catch 2.3 5 Sentencias try .. catch Cuando durante la ejecución de un programa ocurre un error, Java lanza una excepción que visualiza un mensaje acerca de lo ocurrido y detiene la ejecución. Si no queremos que se detenga la ejecución podemos utilizar un try para alertar a la aplicación acerca del código que puede lanzar una excepción y emplear catch para capturar y manejar la excepción. try { bloque de código que puede causar una excepción; } catch(Exception e) { bloque de código para manejar la excepción ; } 6 Capítulo 2. Estructuras de control Capítulo 3 Clases y Objetos 3.1 Clases Una clase es un tipo definido por el usuario que describe los atributos y los métodos de los objetos que se crearán a partir de ella. El estado del objeto viene determinado por los atributos y los métodos son las operaciones que definen su comportamiento. Dentro de los métodos se encuentran los constructores que permiten crear un objeto y los destructores que permiten destruirlo. Los atributos y los métodos se denominan en general miembros de la clase. La definición de una clase consta de dos partes: el nombre de la clase precedido por la palabra reservada class y el cuerpo de la clase entre llaves: class nombre-clase { cuerpo de la clase } Dentro del cuerpo de la clase podemos encontrar atributos y métodos. Ejemplo: class Circunferencia { private double x,y, radio; public Circunferencia() { } public Circunferencia(double cx, double cy, double r) { x=cx; y=cy; radio=r; } public void ponRadio(double r) { radio=r; } public double longitud() { return 2*Math.PI*radio; } } 7 8 Capítulo 3. Clases y Objetos En este ejemplo se define la clase Circunferencia, puede ser usada dentro de una programa de la misma manera que cualquier otro tipo. Un objeto de esta clase tiene tres atributos (coordenadas del centro y valor del radio), dos constructores y un método. Los constructores se distinguen fácilmente porque tienen el mismo nombre que la clase. Los atributos se declaran de la misma manera que cualquier variable. En una clase, cada atributo debe tener un nombre único. Siguiendo las recomendaciones de la programación orientada a objetos, cada clase se debe implementar en un fichero .java, de esta manera es más sencillo modificar la clase. Una aplicación es un programa Java que consiste de una o más clases. Una de las clases tiene que ser una clase aplicación (clase que incluye el método main). La definición del método main es la siguiente: public static void main(String[] args) { cuerpo del método } El método main es público, estático, no devuelve nada y tiene un argumento de tipo String que almacena los argumentos pasados en la línea de órdenes cuando se invoca a la aplicación para su ejecución. Cuando se compile el fichero que contiene la aplicación, el compilador Java genera un fichero .class por cada una de las clases que la componen. Cada fichero generado tiene el mismo nombre que la clase que lo contiene. 3.1.1 Métodos Los métodos forman lo que se denomina interfaz de los objetos, definen las operaciones que se pueden realizar con los atributos. Desde el punto de vista de la Programación Orientada a Objetos, el conjunto de métodos se corresponde con el conjunto de mensajes que los objetos de una clase pueden responder. Los métodos permiten al programador modularizar sus programas. Todas las variables declaradas en las definiciones de métodos son variables locales: sólo se conocen en el método que las define. Casi todos los métodos tienen una lista de parámetros que permiten comunicar información. La definición de un método indica el tipo de datos del resultado que devuelve public double longitud(). Para métodos que no devuelven nada se utiliza la palabra reservada void. 3.1.2 Variables y métodos de clase Cada objeto de una clase tiene su propia copia de todas las variables de la clase. En ciertos casos, resulta adecuado tener una sola copia de una variable en concreto para que sea compartida por todos los objetos de esa clase. Para ello se utilizan las variables de tipo static. Para acceder a un miembro static hay que anteponer el nombre de la clase y el operador de punto al nombre del miembro de la clase. Los miembros static de una clase son accesibles aunque no existan objetos creados de esa clase. Es por ello que el método main es static: se le llama sin necesidad de que haya un objeto ya creado de la clase que lo contiene. class Cuadrado { static int anchoLinea=2; 3.2. Control de acceso a los miembros de una clase 9 static void ponAnchoLinea(int ancho) { anchoLinea=ancho; } } ... Cuadrado.ponAnchoLinea(4); Aunque los miembros estáticos son accesibles a través de un objeto de la clase (de la forma habitual, nombreObjeto.miembro) se considera más adecuado acceder a ellos mediante el nombre de la clase. Un ejemplo clásico es la constante Math.PI. 3.2 Control de acceso a los miembros de una clase El concepto de clase hace referencia a la idea de la ocultación de datos. El usuario de una clase generalmente no tiene acceso a los atributos de esa clase directamente sino que hay que hacerlo a través de los métodos públicos de la clase (métodos de acceso). Para controlar el acceso, Java proporciona una serie de palabras reservadas que indican el tipo de acceso permitido. 3.2.1 Acceso predeterminado Cuando no se especifica ningún modificador para indicar el tipo de acceso, un miembro de la clase puede ser accedido por cualquier clase perteneciente al mismo paquete. Ninguna otra clase fuera de este paquete puede tener acceso a estos miembros. Con este tipo de control de acceso no se tiene mucho efecto sobre cómo va a ser utilizada nuestra clase por otras clases. Para evitar esto habrá que utilizar los modificadores de acceso. 3.2.2 Acceso público Un miembro declarado public (público) está accesible para cualquier clase que quiera utilizarlo. Los atributos static de una clase generalmente se declaran como públicos. Un ejemplo es el atributo PI de la clase Math (public static final double PI). 3.2.3 Acceso privado Un miembro declarado private sólo es accesible por los métodos de su propia clase. 3.2.4 Acceso protegido Un miembro declarado protected se comporta igual que un miembro privado para los métodos de las otras clases excepto para los métodos de las clases del mismo paquete o de sus subclases, para las que se comporta como un miembro público. 10 Capítulo 3. Clases y Objetos 3.3 Objetos Un objeto consta de una estructura interna (los atributos) y de una interfaz que permite acceder y manipular dicha estructura (los métodos). Para construir un objeto de una clase cualquiera hay que llamar a un método de iniciación, el constructor. Para ello se utiliza el operador new. Circunferencia circ = new Circunferencia(); La función del constructor es iniciar nuevos objetos de su clase. Cuando se crea un objeto, Java hace lo siguiente: • Asignar memoria al objeto por medio del operador new. • Iniciar los atributos de ese objeto, sus valores iniciales o los valores predeterminados por el sistema: los atributos numéricos a cero, los alfanuméricos a nulos y las referencias a objetos a null. • Llamar al constructor de la clase. Si no hay suficiente memoria para ubicar el objeto, el operador new lanza una excepción OutOfMemoryError. Para acceder desde un método de una clase a un miembro de un objeto de otra clase diferente se utiliza la sintaxis: objeto.miembro. Cuando el miembro accedido es un método se entiende que el objeto ha recibido un mensaje, el especificado por el nombre del método y responde ejecutando ese método.Los objetos se destruyen con un recolector de basura, de eso se ocupa Java. 3.3.1 Asignación Analicemos el siguiente ejemplo: Circunferencia c1 = new Circunferencia(0,0,15); Circunferencia c2 = c1; c1.ponRadio(25); System.out.println(c2.radio); Una vez el objeto está creado, tenemos una referencia a ese objeto, la variable c1 apunta a un objeto de la clase Circunferencia. Debido a la asignación, la variable c2 apunta al mismo objeto. Después modificamos el valor del radio del objeto Circunferencia apuntado por c1. Por último, el valor que se visualiza es 25 (System.out.println(c2.radio)). 3.3.2 Igualdad Creamos dos objetos iguales (con los mismos valores de sus variables miembro), al preguntar si las variables que apuntan a esos objetos son iguales nos devolverá false. Circunferencia c1 = new Circunferencia(0,0,15); Circunferencia c2 = new Circunferencia(0,0,15); if (c1==c2) 3.4. Herencia 3.4 11 Herencia La herencia es la manera más sencilla de especificar una forma de acceder a una clase ya existente. También sirve para definir una nueva clase que añada nuevas características a una clase ya definida. Esta nueva clase se denomina subclase o clase derivada y la clase ya existente superclase o clase base. Con la herencia todas las clases están clasificadas según una jerarquía. Cada clase tiene su superclase y además puede tener una o más subclases. Se dice que las clases que están en la parte inferior de la jerarquía heredan de las clases que están en la parte superior. La clase Object es la clase raíz de la jerarquía de clases de Java: es una clase que sólo define los atributos y el comportamiento comunes a todas las clases. 3.4.1 Subclases Cuando queremos definir una clase que tenga las mismas capacidades que otra pero añadiendo algunas características lo hacemos definiendo una subclase. Para ello utilizamos la palabra clave extends. Si no se especifica la claúsula extends con el nombre de la superclase se entiende que la superclase es la clase Object. public class CuentaCredito extends Cuenta { // Cuentacredito hereda los miembros de Cuenta } Java sólo permite la herencia simple, no permite que una clase herede de dos o más clases. Una subclase hereda todos los miembros de su superclase, excepto los constructores. De todas formas eso no quiere decir que tenga acceso directo a todos los miembros (depende de cómo se haya definido el control de acceso de su superclase). Atributos con el mismo nombre Es posible definir en la subclase un atributo con el mismo que en la superclase. Para distinguir a que atributo nos referimos, utilizamos las claúsulas super y this. class ClaseBase { public int valor = 1; } class ClaseDerivada extends ClaseBase { int valor = 5; public int metodoA() { return 200 + super.valor; \* atributo valor de la superclase *\ } public int metodoB() { return this.valor; \* atributo valor de la subclase *\ } 12 Capítulo 3. Clases y Objetos } Redefinir métodos de la superclase Cuando se invoca a un método en respuesta a un mensaje recibido por un objeto, Java busca su definición en la clase del objeto. La definición del método que allí se encuentra puede pertenecer a la propia clase o puede haber sido heredada de alguna de sus superclases. Si Java no encuentra la definición, sigue buscando hacia arriba en la jerarquía de clases hasta que la encuentre. Hay ocasiones en las que deseamos que un objeto de una subclase responda al mismo método heredado de la superclase pero con un comportamiento diferente. Eso implica redefinir en la subclase el método heredado de la superclase. Redefinir un método implica volver a escribirlo en la subclase con el mismo nombre, la misma lista de parámetros y el mismo tipo del valor retornado que tenía en la superclase. Cuando en una subclase se redefine un método de una superclase, se oculta el método de la superclase. Si el método se redefine en la subclase con distinto tipo o número de parámetros, el método de la superclase no se oculta, se comporta como una sobrecarga de ese método. Para acceder a un método de la superclase que ha sido redefinido en la subclase se utiliza la palabra reservada super. 3.4.2 Clases abstractas Cuando se diseña una clase para ser genérica, lo más probable es que no necesitemos crear objetos de ella; lo que interesa es proporcionar los atributos y comportamientos que serán compartidos por todas sus subclases. Una clase con estas características se denomina clase abstracta y se define con el calificativo abstract public abstract class Entidad { // Cuerpo de la clase } Una clase abstracta puede contener el mismo tipo de miembros que una clase que no lo sea y además puede contener métodos abstractos. Si un método heredado por una subclase es abstracto, es obligatorio redefinirlo, de lo contrario la subclase también debería ser declarada abstracta. Capítulo 4 Packages Los paquetes (packages) de Java desempeñan un papel equivalente al de las librerías de otros lenguajes. De hecho, las librerías estándar que incorpora el JDK están definidas como packages. Aquí veremos para qué sirve un package, cómo se crea y cómo se referencian las clases definidas en él. 4.1 Introducción Los packages de Java tienen básicamente tres utilidades: 1. Organizar y agrupar clases relacionadas entre sí. 2. Evitar conflictos en los nombres de las clases. Dos clases definidas por el usuario pueden tener el mismo nombre siempre y cuando residan en paquetes distintos. 3. Permitir el acceso a las variables y métodos definidos en una clase. Por defecto, las variables y métodos son accesibles entre clases del mismo paquete. Físicamente, un package será un conjunto de ficheros .class que residen dentro de un mismo directorio o bien un único fichero .jar que los contiene a todos (éste no es más que un fichero .zip al que se le puede añadir una firma digital e información adicional sobre su contenido). 4.2 Crear un paquete Para especificar que las clases definidas en un fichero pertenecen a un determinado paquete, hay que emplear la sentencia package nombrePaquete; Esta sentencia debe ser la primera que aparezca en el fichero. En caso de que no aparezca la sentencia package, se asume que las clases definidas pertenecen a un paquete "sin nombre". Para ponerle nombre a un paquete se sigue un estilo jerárquico similar al Sistema de Nombres de Dominio empleado en Internet. Así, por ejemplo, podríamos tener un fichero esfera.java, perteneciente a una hipotética librería gráfica y especificar que pertenece al mismo paquete en el que definimos los restantes objetos 3D de la siguiente manera: 13 14 Capítulo 4. Packages package graficos.3D.objetos; class Esfera { public void Esfera(float x, float y, float z, float radio) { ... } ... } La máquina virtual Java asumirá que el fichero reside en una estructura de directorios que refleja el nombre del paquete. Es decir, debe existir un directorio graficos, dentro de él debe haber otro directorio 3D y dentro de este último otro directorio objetos, en el que está almacenado nuestro fichero. Para que el compilador y el intérprete Java puedan localizar las clases del paquete, toda esta estructura de directorios debe residir en un directorio incluido en el classpath (ver apartado 7.1). 4.3 Referenciar un paquete Para referenciar una clase definida en un paquete distinto al actual, podemos hacerlo mediante el nombre de la clase precedido por el nombre del paquete. Por ejemplo, para crear un objeto de la clase Esfera definida en el ejemplo anterior se haría: Esfera e = new graficos.3D.objetos.Esfera(0.0, 0.0, 0.0, 1.0); Evidentemente, esta forma de referenciar una clase es bastante engorrosa. Para evitarla, Java proporciona la sentencia import, que nos permite referirnos a las clases definidas en un paquete sin necesidad de poner el nombre del mismo, por ejemplo: import graficos.3D.objetos.Esfera; Esfera e = new Esfera(0.0, 0.0, 0.0, 1.0); No obstante, en caso de que se tengan dos clases con el mismo nombre dentro de dos paquetes distintos, para referenciarlas será necesario utilizar el nombre de la clase precedido del paquete. El import del ejemplo anterior importaría únicamente la clase Esfera. Para importar todas las clases de un paquete, hay que utilizar el símbolo *, por ejemplo: import graficos.3D.objetos.*; Hay que destacar que con una sola sentencia import se puede importar una clase o bien todas, pero no se pueden importar algunas sí y otras no. Para facilitar la programación, la máquina virtual Java importa automáticamente tres paquetes: el paquete "sin nombre" (clases no metidas dentro de ningún paquete concreto), el paquete java.lang y el paquete al que pertenece la clase actual. 4.4 Los paquetes y la visibilidad de variables y métodos Por defecto, las variables y métodos definidos en una clase perteneciente a un paquete son accesibles desde cualquier otra clase del mismo paquete. El modificador protected hace que 4.4. Los paquetes y la visibilidad de variables y métodos 15 la visibilidad se amplíe además a las clases derivadas. Finalmente, el modificador private hace que la visibilidad quede restringida únicamente a instancias de la misma clase. Como se ha comentado anteriormente, en caso de que una clase no se especifique como perteneciente a un paquete determinado, la máquina virtual Java la coloca automáticamente dentro del paquete "por defecto", por lo que entre todas estas clases habrá visibilidad de variables y métodos. 16 Capítulo 4. Packages Capítulo 5 Tratamiento de errores Java utiliza un mecanismo de gestión de errores bastante distinto al de la mayoría de lenguajes imperativos "clásicos". En lenguajes como C, cada vez que se llama a una función susceptible de producir un error es responsabilidad del programador comprobar que todo ha ido correctamente (generalmente a través del valor de retorno de la función). En Java (y otros lenguajes, como C++), se puede especificar que cuando se produzca un error dentro de un bloque de código, el flujo de ejecución salte a otro bloque encargado de manejar los errores. Como se verá, este mecanismo de gestión de errores es mucho más potente que el tradicional y genera código "más limpio". 5.1 Excepciones y errores Una excepción es cualquier evento que interrumpe el flujo de ejecución normal de un programa. La causa de una excepción puede ser muy variada: desde errores de hardware hasta fallos de programación, como un índice de matriz fuera de rango. Las excepciones son clases Java, todas descendientes de la clase Exception definida en la librería estándar. Para saber cuál es la jerarquía de clases definida para las excepciones, lo mejor es consultar la documentación del API Java que viene con el JDK. En realidad, hay otra jerarquía de clases, descendientes de error, pero normalmente el programador puede ignorarlas, ya que representan errores graves, que suelen hacer que la máquina virtual imprima un mensaje y finalice inmediatamente la ejecución del programa. 5.2 Manejo de excepciones Ante una excepción, el programador puede realizar tres acciones distintas: 1. Ignorarla, dejando que la gestione el propio intérprete Java. Esto es lo que se suele hacer con los errores representados por las clases que descienden de error o de RuntimeException, ya que suelen ser errores que provocan el paro inmediato del programa. 2. Capturar la excepción mediante las sentencias try y catch. De esta forma se puede especificar qué es lo que debe hacer el programa si se produce una excepción dentro de un bloque de código determinado. 17 18 Capítulo 5. Tratamiento de errores 3. "Lanzar" la excepción, es decir, forzar a que se encargue de gestionarla el método que haya llamado al método actual. De esta forma, la excepción va viajando por la pila de llamadas a métodos hasta que alguno se encarga de ella. La acción a tomar dependerá del tipo de excepción que se haya producido. Java distingue entre excepciones comprobadas, que son excepciones que no pueden ignorarse (o se capturan o se lanzan), y excepciones sin comprobar, que sí pueden pasarse por alto. 5.2.1 Capturar excepciones Para gestionar explícitamente lo que debe hacer un programa cuando se produzca una determinada excepción se utilizan las sentencias try y catch. La sentencia try debe envolver al bloque en el que se quiere comprobar si ha habido alguna excepción, mientras que el catch representa el código que se ejecutará en caso de que ésta se produzca. Por ejemplo: try { ... z=x/y; ... } catch(ArithmeticException e) { System.out.println("error aritmetico: } " + e); Como puede verse, la sentencia catch actúa como si fuera un método, aceptando como parámetro el tipo de excepción que se desea capturar. Para capturar cualquier excepción, habrá que utilizar la clase base Exception. En el ejemplo se capturan únicamente las excepciones aritméticas (clase ArithmeticException), cuyo ejemplo más típico es la división por cero, en cuyo caso se imprime un mensaje de error por la salida estándar. Hay que tener en cuenta que una sentencia try puede tener asociadas varias catch, de manera que cada una de ellas capture un tipo distinto de excepción. Por ejemplo: try { leerFichero("entrada.txt"); ... } catch(FileNotFoundException e) { \\ Código a ejecutar en caso de que el fichero no exista ... } catch(IOException e) { \\ Código a ejecutar en caso de que haya error de lectura ... } 5.2.2 Lanzar excepciones En caso de que se produzca una excepción en un trozo de código que no está envuelto en un try, ésta pasa al método desde el que se llamó al método actual. De este modo, va ascendiendo 5.3. El requisito "capturar o lanzar" 19 por la pila de llamadas a métodos hasta que es recogida mediante un catch o bien hasta que llega al nivel superior y pasa al control del intérprete Java. Para que este mecanismo funcione, es necesario especificar en la cabecera del método que éste lanza la excepción. Esto se hace mediante el modificador throws. Por ejemplo: void leerFichero(String nombre) throws IOException, FileNotFoundException La cabecera anterior indica que en el caso de que se produzca una excepción del tipo IOException o FileNotFoundException dentro del método leerFichero, ésta debe pasar al método que lo llamó. 5.2.3 Generar excepciones Podemos producir una excepción mediante la sentencia throw. Esta sentencia detiene el flujo de control y lo pasa al try/catch más cercano. Por ejemplo: throw new Exception("Se ha producido un error"); Normalmente, esta sentencia se usará en conjunción con clases de excepciones definidas por el usuario. Cómo definirlas se explica a continuación. 5.2.4 Definir nuevas excepciones Para definir nuevas clases de excepciones basta con crearlas como subclases de alguna de la jerarquía de excepciones incluida en Java. Por ejemplo: class miExcepcion extends Exception { miExcepcion(String mens) { super(mens); } } 5.3 El requisito "capturar o lanzar" Como se ha comentado antes, en el caso de lo que Java llama excepciones comprobadas es necesario, o bien capturar la excepción (mediante try y catch) o bien declarar que el método la lanza (poniendo el throws en la cabecera). Por ello, si llamamos a un método que puede lanzar una excepción comprobada (porque así lo declara en su cabecera) será necesario lanzarla a nuestra vez o bien capturarla. En caso contrario el compilador generará un mensaje de error, negándose a compilar nuestro código. Esto es lo que ocurre con muchos métodos Java de la librería estándar. Por ejemplo, la clase FileReader sirve para leer de un fichero. El constructor de FileReader, que acepta como argumento el nombre del fichero a leer, puede lanzar una excepción del tipo FileNotFoundException, en caso de que éste no exista. Por tanto, si creamos un objeto de la clase FileReader habrá que envolver su instanciación en un try/catch o bien incluir un throws FileNotFoundException en la cabecera del método en que se instancia. 20 Capítulo 5. Tratamiento de errores Capítulo 6 Librerías estándar de Java Las librerías estándar de Java incorporan un gran número de packages que facilitan la programación en muchas áreas distintas. Así, tenemos por ejemplo clases para programación en red (java.net), entrada/salida (java.io), estructuras de datos como vectores y colecciones (java.util), programación de applets (java.applet) o de interfaces gráficos (java.awt). Aquí veremos solo algunas de las librerías más básicas. Para el resto, lo mejor es consultar la documentación sobre el API de Java que se distribuye junto son el SDK. 6.1 Operaciones matemáticas: java.lang.Math La clase java.lang.Math encapsula las constantes y operaciones matemáticas más comunes. Todos los miembros y métodos de esta clase son de tipo static. Por ello, no es necesario instanciar ningún objeto de la clase Math, sino que los métodos se usan directamente con el nombre de la clase. Así, por ejemplo, podemos llamar en nuestro código a Math.cos(angulo) o a Math.abs(valor). Además, tenemos constantes predefinidas como Math.PI o Math.E. 6.2 Operaciones de entrada/salida: java.io La jerarquía de clases definidas en java.io para realizar operaciones de entrada/salida es bastante amplia. En este apartado únicamente comentaremos las clases básicas. La referencia completa la proporciona, por supuesto, la documentación que incluye el Java SDK. 6.2.1 E/S de bytes y caracteres Según si la operación está orientada a bytes o caracteres, tenemos dos jerarquías de clases distintas 1 . Todas las clases que se ocupan de E/S de bytes reciben un nombre del tipo xxxInputStream (las de entrada) o xxxOutputStream (salida). Las que hacen lo propio con caracteres son las xxxReader y xxxWriter respectivamente. Así, por ejemplo, la clase que sirve para leer bytes de un array es ByteArrayInputStream, y la clase empleada para escribir caracteres en un fichero, FileWriter. Las clases que realizan operaciones de entrada (tanto de bytes como de caracteres) comparten el método read. Versiones sobrecargadas de read sirven para leer secuencias de bytes 1 Hay que tener en cuenta que Java usa el juego de caracteres Unicode, que emplea dos bytes por carácter, con lo que ambos no son equivalentes, al contrario de lo que ocurre en, por ejemplo, C. 21 22 Capítulo 6. Librerías estándar de Java o caracteres y almacenarlos en un array. Lo mismo ocurre con las clases que implementan operaciones de salida y el método write. Así, el siguiente fragmento de código serviría para copiar los contenidos de un fichero en otro: FileReader in = new FileReader("datos.txt"); FileWriter out = new FileWriter("copia.txt"); int c; while ((c = in.read()) != -1) out.write(c); in.close(); out.close(); En el código anterior, se utilizan las clases FileReader y FileWriter que, como es de suponer, implementan lectura y escritura de caracteres en un fichero, respectivamente. 6.2.2 E/S de más alto nivel En el paquete java.io se definen una serie de clases que incorporan funcionalidades más complejas que la simple e/s de bytes o caracteres. Así, por ejemplo hay clases que incorporan un buffer para hacer más eficientes las operaciones, u otras que realizan lectura de tipos de datos como int o double. A la hora de utilizar estas clases, la idea es instanciar primero un objeto que nos dé la funcionalidad básica (por ejemplo, un FileInputStream, lectura de bytes de un fichero) y utilizar éste como base para instanciar la clase compleja. Por ejemplo: FileInputStream f = new FileInputStream("datos"); DataInputStream d = new DataInputStream(f); d.readInt(); d.readDouble(); d.readChar(); Como puede observarse, el constructor de la clase DataInputStream acepta como parámetro un objeto de la clase básica FileInputStream. Además,DataInputStream incorpora una serie de métodos para leer datos de tipo int, double o char, entre otros. 6.2.3 Entrada y salida estándar La entrada y salida estándar en Java están representadas por los objetos System.in y System.out, respectivamente. El primero es un objeto de la clase inputStream. La salida estándar es un objeto de la clase PrintStream, que define los métodos print y println y que sirven para imprimir cualquier tipo de dato (println imprime además un retorno de carro final). Capítulo 7 Uso del JDK Aunque existen diversos compiladores y máquinas virtuales Java, tanto comerciales como libres, el kit estándar para el desarrollo de programas en Java es el JDK, desarrollado por Sun y distribuido de forma gratuita. En el momento de redactar estas líneas, el JDK es accesible a través de http://java.sun.com/j2se. Este kit incluye diversas herramientas, como un compilador (javac), una máquina virtual (java), un depurador (jdb), una herramienta para documentar automáticamente el código (javadoc), y otras. 7.1 Uso del classpath Para que el compilador o el intérprete java puedan localizar las clases empleadas en un programa que no sean parte de las librerías estándar, es necesario indicar en qué directorios pueden estar almacenadas. Esto se puede hacer mediante la variable de entorno CLASSPATH. Esta variable actúa de una manera similar a la variable PATH de DOS o de UNIX, pero en lugar de con ejecutables, con ficheros Java. Según cómo estén almacenadas las clases a referenciar habrá que hacerlo de una forma u otra: 1. Para clases almacenadas en un fichero .jar o .zip, el classpath debe referenciar el nombre del fichero (con la trayectoria para llegar hasta él). Por ejemplo, para emplear la librería graficos.jar que está en el directorio c:\java habrá que añadir c:\java\graficos.jar al classpath 2. Para clases en un fichero .class que no estén en ningún paquete el classpath debe referenciar el directorio donde están almacenadas. 3. Para clases en un fichero .class que estén en un paquete, el classpath debe referenciar el directorio del que cuelga la estructura de directorios que refleja el nombre del paquete. Por ejemplo, supongamos que se desea utilizar una clase Esfera, definida en el fichero Esfera.class y que se encuentra en el paquete graficos.3D.objetos. Como se explicó en el apartado 4.2, el fichero Esfera.class debería estar en un directorio graficos\3D\objetos. En el classpath habrá que añadir, por tanto, la trayectoria para llegar hasta el directorio graficos (sin el nombre de este último). Las referencias del classpath se separan mediante el símbolo ; en DOS o : en UNIX, al igual que en el caso de la variable PATH. Una alternativa a cambiar el valor de la variable de entorno CLASSPATH es emplear la opción -classpath que aceptan todas las herramientas del Java SDK. Así se puede especificar un classpath distinto para cada llamada a la herramienta. Por ejemplo: java -classpath c:\java\graficos.jar miPrograma 23 24 7.2 Capítulo 7. Uso del JDK El compilador javac Esta herramienta convierte los ficheros fuente .java en ficheros .class, que pueden ser interpretados por la máquina virtual. Su uso básico es: javac fuente.java En caso de que en el fichero fuente se haga referencia a otras clases, el compilador se encargará de localizarlas y compilarlas a su vez en caso de que sea necesario. Es decir, el compìlador asume funciones que en otros lenguajes se confían a utilidades adicionales, como make. Por ello, a la hora de compilar un fichero Java hay que seguir una serie de normas: 1. Cada fichero .java debería definir una única clase pública. Aunque hay otros compiladores que aceptarán ficheros que no cumplan esta norma, se considera una buena práctica de programación. 2. El nombre del fichero debe ser el mismo que el de la clase pública definida en él, incluyendo mayúsculas y minúsculas. 3. La clase principal de nuestro programa debe definir un método main, que será el que se ejecute cuando llamemos al intérprete Java. Este método debe tener una signatura especial: public static void main(String[] args) 4. Como se describe en el apartado 7.1, las clases empleadas que no formen parte de la distribución estándar de Java deben ser localizables a través del classpath. 7.3 El intérprete java Es la herramienta que permite ejecutar los programas compilados (ficheros .class). Su uso básico es: java clasePrincipal donde clasePrincipal es la clase que define el método main. Es importante tener en cuenta que no se debe especificar la extensión .class del fichero que contiene la clase, ya que de lo contrario el intérprete dará un error de ejecución. Al igual que en el caso del javac, si se hace referencia a alguna clase que no forma parte de la distribución estándar de Java, será necesario referenciarla en el classpath. Capítulo 8 Bibliografía [Arnow and Weiss, 2000] D. Arnow and G. Weiss. Introducción a la programación con Java. Addison Wesley, 2000. [Ceballos, 2000] Fco. Ceballos. Java 2. Curso de programación. Ra-Ma, 2000. [Deitel and Deitel, 1998] H. Deitel and P. Deitel. Cómo programar en Java. Prentice Hall, 1998. [Niemeyer and Knudsen, 2000] P. Niemeyer and J. Knudsen. Curso de Java. Anaya y O’Reilly, 2000. 25