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