programación en lenguaje java

105
Programación en Lenguaje Java Índice 1 Colecciones de datos.................................................................................................... 4 1.1 Colecciones.............................................................................................................. 4 1.2 Polimorfismo e interfaces...................................................................................... 14 1.3 Tipos de datos........................................................................................................ 16 2 Ejercicios de colecciones............................................................................................ 19 2.1 Esquema UML....................................................................................................... 19 2.2 Centro cultural....................................................................................................... 20 2.3 Agencia inmobiliaria (*)........................................................................................ 21 3 Excepciones e hilos.................................................................................................... 22 3.1 Excepciones........................................................................................................... 22 3.2 Hilos....................................................................................................................... 27 4 Ejercicios de Excepciones e hilos.............................................................................. 34 4.1 Captura de excepciones..........................................................................................34 4.2 Lanzamiento de excepciones (*)............................................................................ 34 4.3 Terminación de hilos..............................................................................................36 4.4 Carrera de hilos (*)................................................................................................ 36 4.5 Productor y consumidor (*)................................................................................... 37 5 Flujos de E/S y Red.................................................................................................... 39 5.1 Entrada/Salida........................................................................................................ 39 5.2 Red......................................................................................................................... 44 6 Ejercicios de Flujos de E/S y Red.............................................................................. 50 6.1 Leer un fichero de texto......................................................................................... 50 6.2 Copia de ficheros (*).............................................................................................. 50 6.3 Lectura de una URL............................................................................................... 50 6.4 Foro de Internet (*)................................................................................................ 51 6.5 Gestión de productos (*)........................................................................................ 51 Copyright © 2008-2009 Depto. CCIA All rights reserved.

Upload: others

Post on 30-Jun-2022

6 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Programación en Lenguaje Java

Programación en Lenguaje Java

Índice

1 Colecciones de datos.................................................................................................... 4

1.1 Colecciones.............................................................................................................. 4

1.2 Polimorfismo e interfaces...................................................................................... 14

1.3 Tipos de datos........................................................................................................ 16

2 Ejercicios de colecciones............................................................................................19

2.1 Esquema UML....................................................................................................... 19

2.2 Centro cultural....................................................................................................... 20

2.3 Agencia inmobiliaria (*)........................................................................................ 21

3 Excepciones e hilos.................................................................................................... 22

3.1 Excepciones........................................................................................................... 22

3.2 Hilos.......................................................................................................................27

4 Ejercicios de Excepciones e hilos.............................................................................. 34

4.1 Captura de excepciones..........................................................................................34

4.2 Lanzamiento de excepciones (*)............................................................................34

4.3 Terminación de hilos..............................................................................................36

4.4 Carrera de hilos (*)................................................................................................ 36

4.5 Productor y consumidor (*)................................................................................... 37

5 Flujos de E/S y Red.................................................................................................... 39

5.1 Entrada/Salida........................................................................................................ 39

5.2 Red......................................................................................................................... 44

6 Ejercicios de Flujos de E/S y Red.............................................................................. 50

6.1 Leer un fichero de texto......................................................................................... 50

6.2 Copia de ficheros (*)..............................................................................................50

6.3 Lectura de una URL...............................................................................................50

6.4 Foro de Internet (*)................................................................................................ 51

6.5 Gestión de productos (*)........................................................................................51

Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 2: Programación en Lenguaje Java

7 Introducción a los clientes ricos................................................................................. 53

7.1 Introducción a AWT y Swing................................................................................ 53

7.2 Gestores de disposición..........................................................................................57

7.3 Modelo de eventos en Java.................................................................................... 63

7.4 Apariencia.............................................................................................................. 69

8 Ejercicios de introducción a AWT y Swing...............................................................72

8.1 Calculadora sencilla en AWT................................................................................ 72

8.2 Calculadora sencilla en Swing............................................................................... 72

8.3 Formatos de texto (*)............................................................................................. 73

9 Consultas a una BD con JDBC...................................................................................75

9.1 Introducción a JDBC..............................................................................................75

9.2 Consulta a una base de datos con JDBC................................................................79

9.3 Restricciones y movimientos en el ResultSet........................................................ 82

9.4 Sentencias de actualización....................................................................................84

9.5 Otras llamadas a la BD.......................................................................................... 85

10 Ejercicios de introducción a JDBC...........................................................................87

10.1 Base de datos a usar............................................................................................. 87

10.2 Conexión con la base de datos y consulta simple................................................ 88

10.3 Movimiento por el ResultSet y actualización...................................................... 89

10.4 Actualización de la base de datos.........................................................................89

10.5 Optativos.............................................................................................................. 89

11 Transacciones, optimización de sentencias y patrones de datos...............................90

11.1 Optimización de sentencias..................................................................................90

11.2 Transacciones....................................................................................................... 91

11.3 Data Access Object (DAO).................................................................................. 94

12 Ejercicios de transacciones, optimización, y DAO................................................ 101

12.1 Transacciones..................................................................................................... 101

12.2 Sentencias preparadas........................................................................................ 101

12.3 Creación de patrones.......................................................................................... 101

13 Roadmap................................................................................................................. 102

13.1 Aspectos destacados...........................................................................................102

13.2 Certificación Sun................................................................................................102

Programación en Lenguaje Java

2Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 3: Programación en Lenguaje Java

13.3 Recursos adicionales.......................................................................................... 104

Programación en Lenguaje Java

3Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 4: Programación en Lenguaje Java

1. Colecciones de datos

La plataforma Java nos proporciona un amplio conjunto de clases dentro del que podemosencontrar tipos de datos que nos resultarán muy útiles para realizar la programación deaplicaciones en Java. Estos tipos de datos nos ayudarán a generar código más limpio deuna forma sencilla.

Se proporcionan una serie de operadores para acceder a los elementos de estos tipos dedatos. Decimos que dichos operadores son polimórficos, ya que un mismo operador sepuede emplear para acceder a distintos tipos de datos. Por ejemplo, un operador addutilizado para añadir un elemento, podrá ser empleado tanto si estamos trabajando conuna lista enlazada, con un array, o con un conjunto por ejemplo.

Este polimorfismo se debe a la definición de interfaces que deben implementar losdistintos tipos de datos. Siempre que el tipo de datos contenga una colección deelementos, implementará la interfaz Collection. Esta interfaz proporciona métodos paraacceder a la colección de elementos, que podremos utilizar para cualquier tipo de datosque sea una colección de elementos, independientemente de su implementación concreta.

Podemos encontrar los siguientes elementos dentro del marco de colecciones de Java:

• Interfaces para distintos tipos de datos: Definirán las operaciones que se puedenrealizar con dichos tipos de datos. Podemos encontrar aquí la interfaz para cualquiercolección de datos, y de manera más concreta para listas (secuencias) de datos,conjuntos, etc.

• Implementaciones de tipos de datos reutilizables: Son clases que implementan tiposde datos concretos que podremos utilizar para nuestras aplicaciones, implementandoalgunas de las interfaces anteriores para acceder a los elementos de dicho tipo dedatos. Por ejemplo, dentro de las listas de elementos, podremos encontrar distintasimplementaciones de la lista como puede ser listas enlazadas, o bien arrays decapacidad variable, pero al implementar la misma interfaz podremos acceder a suselementos mediante las mismas operaciones (polimorfismo).

• Algoritmos para trabajar con dichos tipos de datos, que nos permitan realizar unaordenación de los elementos de una lista, o diversos tipos de búsqueda de undeterminado elemento por ejemplo.

1.1. Colecciones

Las colecciones representan grupos de objetos, denominados elementos. Podemosencontrar diversos tipos de colecciones, según si sus elementos están ordenados, o sipermitimos repetición de elementos o no.

Es el tipo más genérico en cuanto a que se refiere a cualquier tipo que contenga un grupode elementos. Viene definido por la interfaz Collection, de la cual heredará cada subtipo

Programación en Lenguaje Java

4Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 5: Programación en Lenguaje Java

específico. En esta interfaz encontramos una serie de métodos que nos servirán paraacceder a los elementos de cualquier colección de datos, sea del tipo que sea. Estosmétodos generales son:

boolean add(Object o)

Añade un elemento (objeto) a la colección. Nos devuelve true si tras añadir el elemento lacolección ha cambiado, es decir, el elemento se ha añadido correctamente, o false en casocontrario.

void clear()

Elimina todos los elementos de la colección.

boolean contains(Object o)

Indica si la colección contiene el elemento (objeto) indicado.

boolean isEmpty()

Indica si la colección está vacía (no tiene ningún elemento).

Iterator iterator()

Proporciona un iterador para acceder a los elementos de la colección.

boolean remove(Object o)

Elimina un determinado elemento (objeto) de la colección, devolviendo true si dichoelemento estaba contenido en la colección, y false en caso contrario.

int size()

Nos devuelve el número de elementos que contiene la colección.

Object [] toArray()

Nos devuelve la colección de elementos como un array de objetos. Si sabemos deantemano que los objetos de la colección son todos de un determinado tipo (como porejemplo de tipo String) podremos obtenerlos en un array del tipo adecuado, en lugar deusar un array de objetos genéricos. En este caso NO podremos hacer una conversión castdescendente de array de objetos a array de un tipo más concreto, ya que el array se habráinstanciado simplemente como array de objetos:

// Esto no se puede hacer!!!String [] cadenas = (String []) coleccion.toArray();

Lo que si podemos hacer es instanciar nosotros un array del tipo adecuado y hacer unaconversión cast ascendente (de tipo concreto a array de objetos), y utilizar el siguientemétodo:

String [] cadenas = new String[coleccion.size()];coleccion.toArray(cadenas); // Esto si que funcionará

Programación en Lenguaje Java

5Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 6: Programación en Lenguaje Java

Esta interfaz es muy genérica, y por lo tanto no hay ningún tipo de datos que laimplemente directamente, sino que implementarán subtipos de ellas. A continuaciónveremos los subtipos más comunes.

1.1.1. Listas de elementos

Este tipo de colección se refiere a listas en las que los elementos de la colección tienen unorden, existe una secuencia de elementos. En ellas cada elemento estará en unadeterminada posición (índice) de la lista.

Las listas vienen definidas en la interfaz List, que además de los métodos generales de lascolecciones, nos ofrece los siguientes para trabajar con los índices:

void add(int indice, Object obj)

Inserta un elemento (objeto) en la posición de la lista dada por el índice indicado.

Object get(int indice)

Obtiene el elemento (objeto) de la posición de la lista dada por el índice indicado.

int indexOf(Object obj)

Nos dice cual es el índice de dicho elemento (objeto) dentro de la lista. Nos devuelve -1 siel objeto no se encuentra en la lista.

Object remove(int indice)

Elimina el elemento que se encuentre en la posición de la lista indicada mediante dichoíndice, devolviéndonos el objeto eliminado.

Object set(int indice, Object obj)

Establece el elemento de la lista en la posición dada por el índice al objeto indicado,sobrescribiendo el objeto que hubiera anteriormente en dicha posición. Nos devolverá elelemento que había previamente en dicha posición.

Podemos encontrar diferentes implementaciones de listas de elementos en Java:

ArrayList

Implementa una lista de elementos mediante un array de tamaño variable. Conforme seañaden elementos el tamaño del array irá creciendo si es necesario. El array tendrá unacapacidad inicial, y en el momento en el que se rebase dicha capacidad, se aumentará eltamaño del array.

Las operaciones de añadir un elemento al final del array (add), y de establecer u obtenerel elemento en una determinada posición (get/set) tienen un coste temporal constante. Lasinserciones y borrados tienen un coste lineal O(n), donde n es el número de elementos delarray.

Programación en Lenguaje Java

6Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 7: Programación en Lenguaje Java

Hemos de destacar que la implementación de ArrayList no está sincronizada, es decir, simúltiples hilos acceden a un mismo ArrayList concurrentemente podriamos tenerproblemas en la consistencia de los datos. Por lo tanto, deberemos tener en cuenta cuandousemos este tipo de datos que debemos controlar la concurrencia de acceso. Tambiénpodemos hacer que sea sincronizado como veremos más adelante.

Vector

El Vector es una implementación similar al ArrayList, con la diferencia de que elVector si que está sincronizado. Este es un caso especial, ya que la implementaciónbásica del resto de tipos de datos no está sincronizada.

Esta clase existe desde las primeras versiones de Java, en las que no existía el marco delas colecciones descrito anteriormente. En las últimas versiones el Vector se haacomodado a este marco implementando la interfaz List.

Sin embargo, si trabajamos con versiones previas de JDK, hemos de tener en cuenta quedicha interfaz no existía, y por lo tanto esta versión previa del vector no contará con losmétodos definidos en ella. Los métodos propios del vector para acceder a su contenido,que han existido desde las primeras versiones, son los siguientes:

void addElement(Object obj)

Añade un elemento al final del vector.

Object elementAt(int indice)

Devuelve el elemento de la posición del vector indicada por el índice.

void insertElementAt(Object obj, int indice)

Inserta un elemento en la posición indicada.

boolean removeElement(Object obj)

Elimina el elemento indicado del vector, devolviendo true si dicho elemento estabacontenido en el vector, y false en caso contrario.

void removeElementAt(int indice)

Elimina el elemento de la posición indicada en el índice.

void setElementAt(Object obj, int indice)

Sobrescribe el elemento de la posición indicada con el objeto especificado.

int size()

Devuelve el número de elementos del vector.

Por lo tanto, si programamos para versiones antiguas de la máquina virtual Java, serárecomendable utilizar estos métodos para asegurarnos de que nuestro programa funcione.

Programación en Lenguaje Java

7Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 8: Programación en Lenguaje Java

Esto será importante en la programación de Applets, ya que la máquina virtual incluida enmuchos navegadores corresponde a versiones antiguas.

Sobre el vector se construye el tipo pila (Stack), que apoyándose en el tipo vector ofrecemétodos para trabajar con dicho vector como si se tratase de una pila, apilando ydesapilando elementos (operaciones push y pop respectivamente). La clase Stack heredade Vector, por lo que en realidad será un vector que ofrece métodos adicionales paratrabajar con él como si fuese una pila.

LinkedList

En este caso se implementa la lista mediante una lista doblemente enlazada. Por lo tanto,el coste temporal de las operaciones será el de este tipo de listas. Cuando realicemosinserciones, borrados o lecturas en los extremos inicial o final de la lista el tiempo seráconstante, mientras que para cualquier operación en la que necesitemos localizar undeterminado índice dentro de la lista deberemos recorrer la lista de inicio a fin, por lo queel coste será lineal con el tamaño de la lista O(n), siendo n el tamaño de la lista.

Para aprovechar las ventajas que tenemos en el coste temporal al trabajar con losextremos de la lista, se proporcionan métodos propios para acceder a ellos en tiempoconstante:

void addFirst(Object obj) / void addLast(Object obj)

Añade el objeto indicado al principio / final de la lista respectivamente.

Object getFirst() / Object getLast()

Obtiene el primer / último objeto de la lista respectivamente.

Object removeFirst() / Object removeLast()

Extrae el primer / último elemento de la lista respectivamente, devolviéndonos dichoobjeto y eliminándolo de la lista.

Hemos de destacar que estos métodos nos permitirán trabajar con la lista como si setratase de una pila o de una cola. En el caso de la pila realizaremos la inserción y laextracción de elementos por el mismo extremo, mientras que para la cola insertaremospor un extremo y extraeremos por el otro.

1.1.2. Conjuntos

Los conjuntos son grupos de elementos en los que no encontramos ningún elementorepetido. Consideramos que un elemento está repetido si tenemos dos objetos o1 y o2iguales, comparandolos mediante el operador o1.equals(o2). De esta forma, si el objeto ainsertar en el conjunto estuviese repetido, no nos dejará insertarlo. Recordemos que elmétodo add devolvía un valor booleano, que servirá para este caso, devolviendonos truesi el elemento a añadir no estaba en el conjunto y ha sido añadido, o false si el elemento

Programación en Lenguaje Java

8Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 9: Programación en Lenguaje Java

ya se encontraba dentro del conjunto. Un conjunto podrá contener a lo sumo un elementonull.

Los conjuntos se definen en la interfaz Set, a partir de la cuál se construyen diferentesimplementaciones:

HashSet

Los objetos se almacenan en una tabla de dispersión (hash). El coste de las operacionesbásicas (inserción, borrado, búsqueda) se realizan en tiempo constante siempre que loselementos se hayan dispersado de forma adecuada. La iteración a través de sus elementoses más costosa, ya que necesitará recorrer todas las entradas de la tabla de dispersión, loque hará que el coste esté en función tanto del número de elementos insertados en elconjunto como del número de entradas de la tabla. El orden de iteración puede diferir delorden en el que se insertaron los elementos.

LinkedHashSet

Es similar a la anterior pero la tabla de dispersión es doblemente enlazada. Los elementosque se inserten tendrán enlaces entre ellos. Por lo tanto, las operaciones básicas seguiránteniendo coste constante, con la carga adicional que supone tener que gestionar losenlaces. Sin embargo habrá una mejora en la iteración, ya que al establecerse enlacesentre los elementos no tendremos que recorrer todas las entradas de la tabla, el coste sóloestará en función del número de elementos insertados. En este caso, al haber enlaces entrelos elementos, estos enlaces definirán el orden en el que se insertaron en el conjunto, porlo que el orden de iteración será el mismo orden en el que se insertaron.

TreeSet

Utiliza un árbol para el almacenamiento de los elementos. Por lo tanto, el coste pararealizar las operaciones básicas será logarítmico con el número de elementos que tenga elconjunto O(log n).

1.1.3. Mapas

Aunque muchas veces se hable de los mapas como una colección, en realidad no lo son,ya que no heredan de la interfaz Collection.

Los mapas se definen en la interfaz Map. Un mapa es un objeto que relaciona una clave(key) con un valor. Contendrá un conjunto de claves, y a cada clave se le asociará undeterminado valor. En versiones anteriores este mapeado entre claves y valores lo hacía laclase Dictionary, que ha quedado obsoleta. Tanto la clave como el valor puede sercualquier objeto.

Los métodos básicos para trabajar con estos elementos son los siguientes:

Object get(Object clave)

Programación en Lenguaje Java

9Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 10: Programación en Lenguaje Java

Nos devuelve el valor asociado a la clave indicada

Object put(Object clave, Object valor)

Inserta una nueva clave con el valor especificado. Nos devuelve el valor que tenía antesdicha clave, o null si la clave no estaba en la tabla todavía.

Object remove(Object clave)

Elimina una clave, devolviendonos el valor que tenía dicha clave.

Set keySet()

Nos devuelve el conjunto de claves registradas

int size()

Nos devuelve el número de parejas (clave,valor) registradas.

Encontramos distintas implementaciones de los mapas:

HashMap

Utiliza una tabla de dispersión para almacenar la información del mapa. Las operacionesbásicas (get y put) se harán en tiempo constante siempre que se dispersen adecuadamentelos elementos. Es coste de la iteración dependerá del número de entradas de la tabla y delnúmero de elementos del mapa. No se garantiza que se respete el orden de las claves.

TreeMap

Utiliza un árbol rojo-negro para implementar el mapa. El coste de las operaciones básicasserá logarítmico con el número de elementos del mapa O(log n). En este caso loselementos se encontrarán ordenados por orden ascendente de clave.

Hashtable

Es una implementación similar a HashMap, pero con alguna diferencia. Mientras lasanteriores implementaciones no están sincronizadas, esta si que lo está. Además en estaimplementación, al contrario que las anteriores, no se permitirán claves nulas (null). Esteobjeto extiende la obsoleta clase Dictionary, ya que viene de versiones más antiguas deJDK. Ofrece otros métodos además de los anteriores, como por ejemplo el siguiente:

Enumeration keys()

Este método nos devolverá una enumeración de todas las claves registradas en la tabla.

1.1.4. Wrappers

La clase Collections aporta una serie métodos para cambiar ciertas propiedades de laslistas. Estos métodos nos proporcionan los denominados wrappers de los distintos tiposde colecciones. Estos wrappers son objetos que 'envuelven' al objeto de nuestra

Programación en Lenguaje Java

10Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 11: Programación en Lenguaje Java

colección, pudiendo de esta forma hacer que la colección esté sincronizada, o que lacolección pase a ser de solo lectura.

Como dijimos anteriormente, todos los tipos de colecciones no están sincronizados,excepto el Vector que es un caso especial. Al no estar sincronizados, si múltiples hilosutilizan la colección concurrentemente, podrán estar ejecutándose simultáneamente variosmétodos de una misma colección que realicen diferentes operaciones sobre ella. Estopuede provocar inconsistencias en los datos. A continuación veremos un posible ejemplode inconsistencia que se podría producir:

1. Tenemos un ArrayList de nombre letras formada por los siguiente elementos: [ "A","B", "C", "D" ]

2. Imaginemos que un hilo de baja prioridad desea eliminar el objeto "C". Para ello haráuna llamada al método letras.remove("C").

3. Dentro de este método primero deberá determinar cuál es el índice de dicho objetodentro del array, para después pasar a eliminarlo.

4. Se encuentra el objeto "C" en el índice 2 del array (recordemos que se empieza anumerar desde 0).

5. El problema viene en este momento. Imaginemos que justo en este momento se leasigna el procesador a un hilo de mayor prioridad, que se encarga de eliminar elelemento "A" del array, quedándose el array de la siguiente forma: [ "B", "C", "D" ]

6. Ahora el hilo de mayor prioridad es sacado del procesador y nuestro hilo sigueejecutándose desde el punto en el que se quedó.

7. Ahora nuestro hilo lo único que tiene que hacer es eliminar el elemento del índice quehabía determinado, que resulta ser ¡el índice 2!. Ahora el índice 2 está ocupado por elobjeto "D", y por lo tanto será dicho objeto el que se elimine.

Podemos ver que haciendo una llamada a letras.remove("C"), al final se ha eliminado elobjeto "D", lo cual produce una inconsistencia de los datos con las operacionesrealizadas, debido al acceso concurrente.

Este problema lo evitaremos sincronizando la colección. Cuando una colección estásincronizada, hasta que no termine de realizarse una operación (inserciones, borrados,etc), no se podrá ejecutar otra, lo cual evitará estos problemas.

Podemos conseguir que las operaciones se ejecuten de forma sincronizada envolviendonuestro objeto de la colección con un wrapper, que será un objeto que utiliceinternamente nuestra colección encargándose de realizar la sincronización cuandollamemos a sus métodos. Para obtener estos wrappers utilizaremos los siguientes métodosestáticos de Collections:

Collection synchronizedCollection(Collection c)List synchronizedList(List l)Set synchronizedSet(Set s)Map synchronizedMap(Map m)SortedSet synchronizedSortedSet(SortedSet ss)SortedMap synchronizedSortedMap(SortedMap sm)

Como vemos tenemos un método para envolver cada tipo de datos. Nos devolverá un

Programación en Lenguaje Java

11Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 12: Programación en Lenguaje Java

objeto con la misma interfaz, por lo que podremos trabajar con él de la misma forma, sinembargo la implementación interna estará sincronizada.

Podemos encontrar también una serie de wrappers para obtener versiones de sólo lecturade nuestras colecciones. Se obtienen con los siguientes métodos:

Collection unmodifiableCollection(Collection c)List unmodifiableList(List l)Set unmodifiableSet(Set s)Map unmodifiableMap(Map m)SortedSet unmodifiableSortedSet(SortedSet ss)SortedMap unmodifiableSortedMap(SortedMap sm)

1.1.5. Genéricos

Podemos tener colecciones de tipos concretos de datos, lo que permite asegurar que losdatos que se van a almacenar van a ser compatibles con un determinado tipo o tipos. Porejemplo, podemos crear un ArrayList que sólo almacene Strings, o una HashMap quetome como claves Integers y como valores ArrayLists. Además, con esto nosahorramos las conversiones cast al tipo que deseemos, puesto que la colección ya seasume que será de dicho tipo.

Ejemplo

// Vector de cadenasArrayList<String> a = new ArrayList<String>();a.add("Hola");String s = a.get(0);a.add(new Integer(20)); // Daría error!!

// HashMap con claves enteras y valores de vectoresHashMap<Integer, ArrayList> hm = new HashMap<Integer, ArrayList>();hm.put(1, a);ArrayList a2 = hm.get(1);

A partir de JDK 1.5 deberemos utilizar genéricos siempre que sea posible. Si creamos unacolección sin especificar el tipo de datos que contendrá normalmente obtendremos unwarning.

Los genéricos no son una característica exclusiva de las colecciones, sino que se puedenutilizar en muchas otras clases, incluso podemos parametrizar de esta forma nuestraspropias clases.

1.1.6. Recorrer las colecciones

Vamos a ver ahora como podemos iterar por los elementos de una colección de formaeficiente y segura, evitando salirnos del rango de datos. Dos elementos utilizadoscomunmente para ello son las enumeraciones y los iteradores.

Las enumeraciones, definidas mediante la interfaz Enumeration, nos permiten consultar

Programación en Lenguaje Java

12Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 13: Programación en Lenguaje Java

los elementos que contiene una colección de datos. Muchos métodos de clases Java quedeben devolver múltiples valores, lo que hacen es devolvernos una enumeración quepodremos consultar mediante los métodos que ofrece dicha interfaz.

La enumeración irá recorriendo secuencialmente los elementos de la colección. Para leercada elemento de la enumeración deberemos llamar al método:

Object item = enum.nextElement();

Que nos proporcionará en cada momento el siguiente elemento de la enumeración a leer.Además necesitaremos saber si quedan elementos por leer, para ello tenemos el método:

enum.hasMoreElements()

Normalmente, el bucle para la lectura de una enumeración será el siguiente:

while (enum.hasMoreElements()) {Object item = enum.nextElement();// Hacer algo con el item leido

}

Vemos como en este bucle se van leyendo y procesando elementos de la enumeración unoa uno mientras queden elementos por leer en ella.

Otro elemento para acceder a los datos de una colección son los iteradores. La diferenciaestá en que los iteradores además de leer los datos nos permitirán eliminarlos de lacolección. Los iteradores se definen mediante la interfaz Iterator, que proporciona deforma análoga a la enumeración el método:

Object item = iter.next();

Que nos devuelve el siguiente elemento a leer por el iterador, y para saber si quedan máselementos que leer tenemos el método:

iter.hasNext()

Además, podemos borrar el último elemento que hayamos leido. Para ello tendremos elmétodo:

iter.remove();

Por ejemplo, podemos recorrer todos los elementos de una colección utilizando uniterador y eliminar aquellos que cumplan ciertas condiciones:

while (iter.hasNext()){

Object item = iter.next();if(condicion_borrado(item))

iter.remove();}

Las enumeraciones y los iteradores no son tipos de datos, sino elementos que nos serviránpara acceder a los elementos dentro de los diferentes tipos de colecciones.

Programación en Lenguaje Java

13Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 14: Programación en Lenguaje Java

A partir de JDK 1.5 podemos recorrer colecciones y arrays sin necesidad de acceder a susiteradores, previniendo índices fuera de rango.

Ejemplo

// Recorre e imprime todos los elementos de un arrayint[] arrayInt = {1, 20, 30, 2, 3, 5};for(int elemento: arrayInt)

System.out.println (elemento);

// Recorre e imprime todos los elementos de un ArrayListArrayList<String> a = new ArrayList<String>();for(String cadena: a)

System.out.println (cadena);

1.2. Polimorfismo e interfaces

En Java podemos conseguir tener objetos polimórficos mediante la implementación deinterfaces. Un claro ejemplo está en las colecciones vistas anteriormente. Por ejemplo,todos los tipos de listas implementan la interfaz List. De esta forma, en un método queacepte como entrada un objeto de tipo List podremos utilizar cualquier tipo queimplemente esta interfaz, independientemente del tipo concreto del que se trate.

Es por lo tanto recomendable hacer referencia siempre a estos objetos mediante la interfazque implementa, y no por su tipo concreto. De esta forma posteriormente podríamoscambiar la implementación del tipo de datos sin que afecte al resto del programa. Loúnico que tendremos que cambiar es el momento en el que se instancia.

Por ejemplo, si tenemos una clase Cliente que contiene una serie de cuentas, tendremosalgo como:

public class Cliente {String nombre;List<Cuenta> cuentas;

public Cliente(String nombre) {this.nombre = nombre;this.cuentas = new ArrayList<Cuenta>();

}

public List<Cuenta> getCuentas() {return cuentas;

}

public void setCuentas(List<Cuenta> cuentas) {this.cuentas = cuentas;

}

public void addCuenta(Cuenta cuenta) {this.cuentas.add(cuenta);

}}

Programación en Lenguaje Java

14Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 15: Programación en Lenguaje Java

Si posteriormente queremos cambiar la implementación de la lista a LinkedList porejemplo, sólo tendremos que cambiar la línea del constructor en la que se hace lainstanciación.

Como ejemplo de la utilidad que tiene el polimorfismo podemos ver los algoritmospredefinidos con los que contamos en el marco de colecciones.

1.2.1. Ejemplo: Algoritmos

Como hemos comentado anteriormente, además de las interfaces y las implementacionesde los tipos de datos descritos en los apartados previos, el marco de colecciones nosofrece una serie de algoritmos utiles cuando trabajamos con estos tipos de datos,especialmente para las listas.

Estos algoritmos los podemos encontrar implementados como métodos estáticos en laclase Collections. En ella encontramos métodos para la ordenación de listas (sort), para labúsqueda binaria de elementos dentro de una lista (binarySearch) y otras operaciones quenos serán de gran utilidad cuando trabajemos con colecciones de elementos.

Estos métodos tienen como parámetro de entrada un objeto de tipo List. De esta forma,podremos utilizar estos algoritmos para cualquier tipo de lista.

1.2.2. Patrón factoría

Hemos comentado que el único lugar en el que tendremos que hacer referencia al tipoconcreto de datos utilizado es en el momento de su instanciación. Sin embargo, podemosconseguir aislar nuestro programa de esta instanciación concreta utilizando el patrónfactoría.

Esto se conseguirá creando una clase factoría que será la que se encargue de crearinstancias de nuestro objeto. Es evidente que en ese caso la instancia concreta tendrá quecrearse dentro de la factoría, pero al menos hemos independizado el resto del código denuestra aplicación de esta tarea. En cualquier lugar en el que se necesite instanciar nuestrotipo de objeto se recurrirá a la factoría. De esta forma, si queremos cambiar la forma deinstanciar este objeto simplemente tendremos que modificar el código de la factoría.

Por ejemplo, en el caso del ejemplo del cliente y las cuentas podríamos crearnos unafactoría como la siguiente:

public class FactoriaCuentas {private static FactoriaCuentas me = new FactoriaCuentas();

private FactoriaCuentas() {}

public static FactoriaCuentas getInstance() {return me;

}

Programación en Lenguaje Java

15Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 16: Programación en Lenguaje Java

public List<Cuenta> crearListaCuentas() {return new ArrayList<Cuenta>();

}}

De esta forma podremos crear la lista de cuentas desde diferentes lugares del código,incluso desde clases distintas, con independencia de la instancia concreta que vaya ausarse. Por ejemplo, podremos tener varios constructores de nuestra clase Cliente querecurran a la factoría para crear la instancia de la lista:

public Cliente() {this.nombre = "John Doe";this.cuentas = FactoriaCuentas.getInstance().crearListaCuentas();

}

public Cliente(String nombre) {this.nombre = nombre;this.cuentas = FactoriaCuentas.getInstance().crearListaCuentas();

}

public Cliente(String nombre, List<Cuenta> cuentas) {this.nombre = nombre;this.cuentas = FactoriaCuentas.getInstance().crearListaCuentas();this.cuentas.addAll(cuentas);

}

1.3. Tipos de datos

1.3.1. Wrappers de tipos básicos

Hemos visto que en Java cualquier tipo de datos es un objeto, excepto los tipos de datosbásicos: boolean, int, long, float, double, byte, short, char.

Cuando trabajamos con colecciones de datos los elementos que contienen éstas sonsiempre objetos, por lo que en un principio no podríamos insertar elementos de estos tiposbásicos. Para hacer esto posible tenemos una serie de objetos que se encargarán deenvolver a estos tipos básicos, permitiéndonos tratarlos como objetos y por lo tantoinsertarlos como elementos de colecciones. Estos objetos son los llamados wrappers, y lasclases en las que se definen tienen nombre similares al del tipo básico que encapsulan,con la diferencia de que comienzan con mayúscula: Boolean, Integer, Long, Float,Double, Byte, Short, Character.

Estas clases, además de servirnos para encapsular estos datos básicos en forma de objetos,nos proporcionan una serie de métodos e información útiles para trabajar con estos datos.Nos proporcionarán métodos por ejemplo para convertir cadenas a datos numéricos dedistintos tipos y viceversa, así como información acerca del valor mínimo y máximo quese puede representar con cada tipo numérico.

Programación en Lenguaje Java

16Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 17: Programación en Lenguaje Java

1.3.2. Autoboxing

Esta característica aparecida en JDK 1.5 evita al programador tener que establecercorrespondencias manuales entre los tipos simples (int, double, etc) y suscorrespondientes wrappers o tipos complejos (Integer, Double, etc). Podremosutilizar un int donde se espere un objeto complejo (Integer), y viceversa.

Ejemplo

ArrayList<Integer> a = new ArrayList<Integer>();a.add(30);Integer n = v.get(0);n = n+1;int num = n;

1.3.3. Estructuras de datos

En nuestras aplicaciones normalmente trabajamos con diversos conjuntos de atributos queson siempre utilizados de forma conjunta (por ejemplo, los datos de un punto en un mapa:coordenada x, coordenada y, descripcion). Estos datos se deberán ir pasando entre lasdiferentes capas de la aplicación.

Podemos utilizar el patrón Transfer Object para encapsular estos datos en un objeto, ytratarlos así de forma eficiente. Este objeto tendrá como campos los datos que encapsula.En el caso de que estos campos sean privados, nos deberá proporcionar métodos paraacceder a ellos. Estos métodos son conocidos como getters y setters, y nos permitiránconsultar o modificar su valor respectivamente. Una vez escritos los campos privados,Eclipse puede generar los getters y setters de forma automática pinchando sobre el códigofuente con el botón derecho del ratón y seleccionando la opción Source > GenerateGetters and Setters.... Por ejemplo, si creamos una clase como la siguiente:

public class Punto2D {private int x;private int y;private String descripcion;

}

Al generar los getters y setters con Eclipse aparecerán los siguientes métodos:

public String getDescripcion() {return descripcion;

}public void setDescripcion(String descripcion) {

this.descripcion = descripcion;}public int getX() {

return x;}public void setX(int x) {

this.x = x;}

Programación en Lenguaje Java

17Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 18: Programación en Lenguaje Java

public int getY() {return y;

}public void setY(int y) {

this.y = y;}

Con Eclipse también podremos generar diferentes tipos de constructores para estosobjetos. Por ejemplo, con la opción Source > Generate Constructor Using Fields...generará un constructor que tomará como entrada los campos del objeto que leindiquemos.

Programación en Lenguaje Java

18Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 19: Programación en Lenguaje Java

2. Ejercicios de colecciones

2.1. Esquema UML

Vamos a escribir el esqueleto (nombre de la clase, campos y nombre de los métodos) quepodría tener una aplicación Java con el siguiente esquema:

Figura 1. Diagrama de la aplicación

Se pide:

a) Crear todas estas clases en un paquete es.ua.jtech.plj.sesion01.arbol, dentro delproyecto creado para los ejercicios de la sesión (plj-sesion01). En principio crearemosúnicamente las clases y sus campos (dejaremos los constructores y los métodos para lossiguientes puntos).

b) Utilizar Eclipse para generar automáticamente los constructores necesarios.

c) Utilizar Eclipse para generar automáticamente los getters y setters que figuran en eldiagrama.

d) Modificar la clase Arbol para que la lista de nodos utilice genéricos.

e) Vamos a utilizar el patrón factoría para instanciar las claves. Crearemos unaFactoriaClave que nos devuelva instancias de dicha clase proporcionando un nombre.Si el nombre que proporcionamos empieza por mayúscula se entenderá que es un nombrepropio, y por lo tanto una persona. En caso contrario, entenderemos que es una cosa.¿Cómo podríamos evitar que desde el resto del código de la aplicación (suponemos queestará en paquetes distintos) se puedan instanciar las clases Cosa y Persona sin pasar por

Programación en Lenguaje Java

19Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 20: Programación en Lenguaje Java

la factoría?

f) Añadir a la clase Clave un método abstracto getTipo que nos devuelva un String

indicando el tipo de clave que es: "cosa" o "persona".

g) Introducir en la clase Arbol el código para instanciar la lista de nodos como un objetoArrayList, y el código del método addNodo. Añadir además un método getNodos paraobtener la lista ordenada de Nodos. Utilizar para ello los algoritmos que tenemosdisponibles en el marco de colecciones.

h) Crear un programa principal para utilizar esta librería de clases. Crearemos un Arbol alque añadiremos una serie de nodos. Obtener la lista ordenada de nodos e imprimirlos porla consola, con su nombre, tipo y valor.

2.2. Centro cultural

Un centro cultural se dedica al préstamo de dos tipos de materiales de préstamo: discos ylibros. Para los dos se guarda información general, como su código identificativo, el títuloy el autor. En el caso de los libros, almacenamos también su número de páginas, y paralos discos el nombre de la discográfica.

Al centro cultural acuden una serie de clientes (de los que se guarda su DNI y nombre),que realizan una serie de peticiones de discos o libros (como mucho hasta 5 peticiones).Para cada petición se guarda la fecha de inicio y fin del préstamo.

Se pide:

a) Escribir el esqueleto de las clases que estimes apropiadas para el supuesto planteadoanteriormente. Crear todas estas clases en un paquetees.ua.jtech.plj.sesion01.prestamos, dentro del proyecto creado para los ejerciciosde la sesión. Genera automáticamente con Eclipse todos los componentes que sea posible.

NotaPara todos los campos que contengan una colección de datos deberán utilizarse genéricos.

b) Introducir en la clase correspondiente a los clientes el código para insertar nuevaspeticiones, evitando que puedan producirse más de 5. Esta lista debe haberse instanciadopreviamente como un objeto de tipo ArrayList.

c) Crear un programa principal en el que se cree un cliente y se añadan una serie depeticiones. Comprobar que funciona correctamente.

d) Dado que la lista de peticiones siempre se recorre de forma iterativa (nunca se accede aun índice concreto directamente), y que sólo hacemos inserciones en uno de sus extremos,¿qué tipo de datos crees que resultaría más eficiente para implementarla? Sustituye laimplementación actual por la que resulte más adecuada. Volver a probar el código

Programación en Lenguaje Java

20Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 21: Programación en Lenguaje Java

desarrollado en el punto anterior para comprobar que todo sigue funcionandocorrectamente.

2.3. Agencia inmobiliaria (*)

Una agencia inmobiliaria dispone de una serie de pisos en oferta. Estos pisos pueden serviviendas de protección oficial, o pisos libres. En cualquier caso, se guarda la direccióndel piso, el número de metros cuadrados, una descripción sobre su contenido y estado, yel precio. En el caso de viviendas libres, también almacenamos el porcentaje de beneficioque se lleva la inmobiliaria sobre su precio original. Deberemos poder obtener el preciofinal de las viviendas, en el que se contabilice este beneficio, si procede.

Los clientes que solicitan pisos a la inmobiliaria dejan su DNI y nombre, y por cada visitaque soliciten, se almacena la fecha y hora de la visita, y la impresión del cliente sobre elpiso.

Una vez se ha encontrado un piso adecuado, la propia inmobiliaria ofrece al cliente unpréstamo hipotecario, en el que se indica la cantidad solicitada, si se dispone de aval o no(por defecto, no), el tipo de interés aplicado, y el período de tiempo por el que estarávigente la hipoteca, en años.

Escribir el esqueleto de las clases que estimes apropiadas para el supuesto planteadoanteriormente. Crear todas estas clases en un paquetees.ua.jtech.plj.sesion01.inmobiliaria, dentro del proyecto creado para losejercicios de la sesión.

Programación en Lenguaje Java

21Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 22: Programación en Lenguaje Java

3. Excepciones e hilos

3.1. Excepciones

Las excepciones son eventos que ocurren durante la ejecución de un programa y hacenque éste salga de su flujo normal de instrucciones. Este mecanismo permite tratar loserrores de una forma elegante, ya que separa el código para el tratamiento de errores delcódigo normal del programa. Se dice que una excepción es lanzada cuando se produce unerror, y esta excepción puede ser capturada para tratar dicho error.

3.1.1. Tipos de excepciones

Tenemos diferentes tipos de excepciones dependiendo del tipo de error que representen.Todas ellas descienden de la clase Throwable, la cual tiene dos descendientes directos:

• Error: Se refiere a errores graves en la máquina virtual de Java, como por ejemplofallos al enlazar con alguna librería. Normalmente en los programas Java no setratarán este tipo de errores.

• Exception: Representa errores que no son críticos y por lo tanto pueden ser tratados ycontinuar la ejecución de la aplicación. La mayoría de los programas Java utilizanestas excepciones para el tratamiento de los errores que puedan ocurrir durante laejecución del código.

Dentro de Exception, cabe destacar una subclase especial de excepciones denominadaRuntimeException, de la cual derivarán todas aquellas excepciones referidas a loserrores que comúnmente se pueden producir dentro de cualquier fragmento de código,como por ejemplo hacer una referencia a un puntero null, o acceder fuera de los límitesde un array.

Estas RuntimeException se diferencian del resto de excepciones en que no son de tipochecked. Una excepción de tipo checked debe ser capturada o bien especificar que puedeser lanzada de forma obligatoria, y si no lo hacemos obtendremos un error decompilación. Dado que las RuntimeException pueden producirse en cualquier fragmentode código, sería impensable tener que añadir manejadores de excepciones y declarar queéstas pueden ser lanzadas en todo nuestro código. Deberemos:

• Utilizar excepciones unchecked (no predecibles) para indicar errores graves en lalógica del programa, que normalmente no deberían ocurrir. Se utilizarán paracomprobar la consistencia interna del programa.

• Utilizar excepciones checked para mostrar errores que pueden ocurrir durante laejecución de la aplicación, normalmente debidos a factores externos como porejemplo la lectura de un fichero con formato incorrecto, un fallo en la conexión, o laentrada de datos por parte del usuario.

Programación en Lenguaje Java

22Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 23: Programación en Lenguaje Java

Tipos de excepciones

Dentro de estos grupos principales de excepciones podremos encontrar tipos concretos deexcepciones o bien otros grupos que a su vez pueden contener más subgrupos deexcepciones, hasta llegar a tipos concretos de ellas. Cada tipo de excepción guardaráinformación relativa al tipo de error al que se refiera, además de la información común atodas las excepciones. Por ejemplo, una ParseException se suele utilizar al procesar unfichero. Además de almacenar un mensaje de error, guardará la línea en la que el parserencontró el error.

3.1.2. Captura de excepciones

Cuando un fragmento de código sea susceptible de lanzar una excepción y queramostratar el error producido o bien por ser una excepción de tipo checked debamos capturarla,podremos hacerlo mediante la estructura try-catch-finally, que consta de tres bloquesde código:

• Bloque try: Contiene el código regular de nuestro programa que puede producir unaexcepción en caso de error.

• Bloque catch: Contiene el código con el que trataremos el error en caso deproducirse.

• Bloque finally: Este bloque contiene el código que se ejecutará al final tanto si se haproducido una excepción como si no lo ha hecho. Este bloque se utiliza para, porejemplo, cerrar algún fichero que haya podido ser abierto dentro del código regulardel programa, de manera que nos aseguremos que tanto si se ha producido un errorcomo si no este fichero se cierre. El bloque finally no es obligatorio ponerlo.

Para el bloque catch además deberemos especificar el tipo o grupo de excepciones quetratamos en dicho bloque, pudiendo incluir varios bloques catch, cada uno de ellos paraun tipo/grupo de excepciones distinto. La forma de hacer esto será la siguiente:

try {// Código regular del programa// Puede producir excepciones

} catch(TipoDeExcepcion1 e1) {

Programación en Lenguaje Java

23Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 24: Programación en Lenguaje Java

// Código que trata las excepciones de tipo// TipoDeExcepcion1 o subclases de ella.// Los datos sobre la excepción los encontraremos// en el objeto e1.

} catch(TipoDeExcepcion2 e2) {// Código que trata las excepciones de tipo// TipoDeExcepcion2 o subclases de ella.// Los datos sobre la excepción los encontraremos// en el objeto e2.

...} catch(TipoDeExcepcionN eN) {

// Código que trata las excepciones de tipo// TipoDeExcepcionN o subclases de ella.// Los datos sobre la excepción los encontraremos// en el objeto eN.

} finally {// Código de finalización (opcional)

}

Si como tipo de excepción especificamos un grupo de excepciones este bloque seencargará de la captura de todos los subtipos de excepciones de este grupo. Por lo tanto, siespecificamos Exception capturaremos cualquier excepción, ya que está es la superclasecomún de todas las excepciones.

En el bloque catch pueden ser útiles algunos métodos de la excepción (que podemos veren la API de la clase padre Exception):

String getMessage()void printStackTrace()

con getMessage obtenemos una cadena descriptiva del error (si la hay). ConprintStackTrace se muestra por la salida estándar la traza de errores que se hanproducido (en ocasiones la traza es muy larga y no puede seguirse toda en pantalla conalgunos sistemas operativos).

Un ejemplo de uso:

try {... // Aqui va el codigo que puede lanzar una excepcion

} catch (Exception e) {System.out.println ("El error es: " + e.getMessage());e.printStackTrace();

}

Nunca deberemos dejar vacío el cuerpo del catch, porque si se produce el error, nadie seva a dar cuenta de que se ha producido. En especial, cuando estemos con excepcionesno-checked.

3.1.3. Lanzamiento de excepciones

Hemos visto cómo capturar excepciones que se produzcan en el código, pero en lugar decapturarlas también podemos hacer que se propaguen al método de nivel superior (desdeel cual se ha llamado al método actual). Para esto, en el método donde se vaya a lanzar la

Programación en Lenguaje Java

24Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 25: Programación en Lenguaje Java

excepción, se siguen 2 pasos:

• Indicar en el método que determinados tipos de excepciones o grupos de ellas puedenser lanzados, cosa que haremos de la siguiente forma, por ejemplo:

public void lee_fichero()throws IOException, FileNotFoundException

{// Cuerpo de la función

}

Podremos indicar tantos tipos de excepciones como queramos en la claúsula throws. Sialguna de estas clases de excepciones tiene subclases, también se considerará que puedelanzar todas estas subclases.

• Para lanzar la excepción utilizamos la instrucción throw, proporcionándole un objetocorrespondiente al tipo de excepción que deseamos lanzar. Por ejemplo:

throw new IOException(mensaje_error);

• Juntando estos dos pasos:

public void lee_fichero()throws IOException, FileNotFoundException

{...throw new IOException(mensaje_error);...

}

Podremos lanzar así excepciones en nuestras funciones para indicar que algo no es comodebiera ser a las funciones llamadoras. Por ejemplo, si estamos procesando un fichero quedebe tener un determinado formato, sería buena idea lanzar excepciones de tipoParseException en caso de que la sintaxis del fichero de entrada no sea correcta.

NOTA: para las excepciones que no son de tipo checked no hará falta la cláusula throwsen la declaración del método, pero seguirán el mismo comportamiento que el resto, si noson capturadas pasarán al método de nivel superior, y seguirán así hasta llegar a lafunción principal, momento en el que si no se captura provocará la salida de nuestroprograma mostrando el error correspondiente.

3.1.4. Creación de nuevas excepciones

Además de utilizar los tipos de excepciones contenidos en la distribución de Java,podremos crear nuevos tipos que se adapten a nuestros problemas.

Para crear un nuevo tipo de excepciones simplemente deberemos crear una clase queherede de Exception o cualquier otro subgrupo de excepciones existente. En esta clasepodremos añadir métodos y propiedades para almacenar información relativa a nuestrotipo de error. Por ejemplo:

Programación en Lenguaje Java

25Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 26: Programación en Lenguaje Java

public class MiExcepcion extends Exception{

public MiExcepcion (String mensaje){

super(mensaje);}

}

Además podremos crear subclases de nuestro nuevo tipo de excepción, creando de estaforma grupos de excepciones. Para utilizar estas excepciones (capturarlas y/o lanzarlas)hacemos lo mismo que lo explicado antes para las excepciones que se tienen definidas enJava.

3.1.5. Nested exceptions

Cuando dentro de un método de una librería se produce una excepción, normalmente sepropagará dicha excepción al llamador en lugar de gestionar el error dentro de la librería,para que de esta forma el llamador tenga constancia de que se ha producido undeterminado error y pueda tomar las medidas que crea oportunas en cada momento. Parapasar esta excepción al nivel superior puede optar por propagar la misma excepción quele ha llegado, o bien crear y lanzar una nueva excepción. En este segundo caso la nuevaexcepción deberá contener la excepción anterior, ya que de no ser así perderíamos lainformación sobre la causa que ha producido el error dentro de la librería, que podríasernos de utilidad para depurar la aplicación. Para hacer esto deberemos proporcionar laexcepción que ha causado el error como parámetro del constructor de nuestra nuevaexcepción:

public class MiExcepcion extends Exception{

public MiExcepcion (String mensaje, Throwable causa){

super(mensaje, causa);}

}

En el método de nuestra librería en el que se produzca el error deberemos capturar laexcepción que ha causado el error y lanzar nuestra propia excepción al llamador:

try {...

} catch(IOException e) {throw new MiExcepcion("Mensaje de error", e);

}

Cuando capturemos una excepción, podemos consultar la excepción previa que la hacausado (si existe) con el método:

Exception causa = (Exception)e.getCause();

Las nested exceptions son útiles para:

• Encadenar errores producidos en la secuencia de métodos a los que se ha llamado.

Programación en Lenguaje Java

26Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 27: Programación en Lenguaje Java

• Facilitan la depuración de la aplicación, ya que nos permite conocer de dónde viene elerror y por qué métodos ha pasado.

• El lanzar una excepción propia de cada método permite ofrecer información másdetallada que si utilizásemos una única excepción genérica. Por ejemplo, aunque envarios casos el origen del error puede ser una IOException, nos será de utilidad sabersi ésta se ha producido al guardar un fichero de datos, al guardar datos de laconfiguración de la aplicación, al intentar obtener datos de la red, etc.

• Aislar al llamador de la implementación concreta de una librería. Por ejemplo, cuandoutilicemos los objetos de acceso a datos de nuestra aplicación, en caso de errorrecibiremos una excepción propia de nuestra capa de acceso a datos, en lugar de unaexcepción propia de la implementación concreta de esta capa, como pudiera serSQLException si estamos utilizando una BD SQL o IOException si estamosaccediendo a ficheros.

3.2. Hilos

Un hilo es un flujo de control dentro de un programa. Creando varios hilos podremosrealizar varias tareas simultáneamente. Cada hilo tendrá sólo un contexto de ejecución(contador de programa, pila de ejecución). Es decir, a diferencia de los procesos UNIX,no tienen su propio espacio de memoria sino que acceden todos al mismo espacio dememoria común, por lo que será importante su sincronización cuando tengamos varioshilos accediendo a los mismos objetos.

3.2.1. Creación de hilos

En Java los hilos están encapsulados en la clase Thread. Para crear un hilo tenemos dosposibilidades:

• Heredar de Thread redefiniendo el método run.• Crear una clase que implemente la interfaz Runnable que nos obliga a definir el

método run.

En ambos casos debemos definir un método run que será el que contenga el código delhilo. Desde dentro de este método podremos llamar a cualquier otro método de cualquierobjeto, pero este método run será el método que se invoque cuando iniciemos laejecución de un hilo. El hilo terminará su ejecución cuando termine de ejecutarse estemétodo run.

Para crear nuestro hilo mediante herencia haremos lo siguiente:

public class EjemploHilo extends Thread{

public void run() {// Código del hilo

}}

Programación en Lenguaje Java

27Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 28: Programación en Lenguaje Java

Una vez definida la clase de nuestro hilo deberemos instanciarlo y ejecutarlo de lasiguiente forma:

Thread t = new EjemploHilo();t.start();

Crear un hilo heredando de Thread tiene el problema de que al no haber herenciamúltiple en Java, si heredamos de Thread no podremos heredar de ninguna otra clase, ypor lo tanto un hilo no podría heredar de ninguna otra clase.

Este problema desaparece si utilizamos la interfaz Runnable para crear el hilo, ya que unaclase puede implementar varios interfaces. Definiremos la clase que contenga el hilocomo se muestra a continuación:

public class EjemploHilo implements Runnable{

public void run() {// Código del hilo

}}

Para instanciar y ejecutar un hilo de este tipo deberemos hacer lo siguiente:

Thread t = new Thread(new EjemploHilo());t.start();

Esto es así debido a que en este caso EjemploHilo no deriva de una clase Thread, por loque no se puede considerar un hilo, lo único que estamos haciendo implementando lainterfaz es asegurar que vamos a tener definido el método run. Con esto lo que haremosserá proporcionar esta clase al constructor de la clase Thread, para que el objeto Thread

que creemos llame al método run de la clase que hemos definido al iniciarse la ejecucióndel hilo, ya que implementando la interfaz le aseguramos que esta función existe.

Deberemos utilizar sólo los hilos necesarios. Se utilizarán:

• Cuando la aplicación debe responder a varios eventos simultáneamente.• Para aumentar la capacidad de respuesta (que se pueda responder al usuario incluso

cuando la aplicación esté realizando otras tareas).• Para aprovechar las máquinas con múltiples procesadores.

3.2.2. Estado y propiedades de los hilos

Un hilo pasará por varios estados durante su ciclo de vida.

Thread t = new Thread(this);

Una vez se ha instanciado el objeto del hilo, diremos que está en estado de Nuevo hilo.

t.start();

Cuando invoquemos su método start el hilo pasará a ser un hilo vivo, comenzándose a

Programación en Lenguaje Java

28Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 29: Programación en Lenguaje Java

ejecutar su método run. Una vez haya salido de este método pasará a ser un hilo muerto.

La única forma de parar un hilo es hacer que salga del método run de forma natural.Podremos conseguir esto haciendo que se cumpla la condición de salida del bucleprincipal definido dentro del run. Las funciones para parar, pausar y reanudar hilos estásdesaprobadas en las versiones actuales de Java.

Mientras el hilo esté vivo, podrá encontrarse en dos estados: Ejecutable y No ejecutable.El hilo pasará de Ejecutable a No ejecutable en los siguientes casos:

• Cuando se encuentre dormido por haberse llamado al método sleep, permanecerá Noejecutable hasta haber transcurrido el número de milisegundos especificados.

• Cuando se encuentre bloqueado en una llamada al método wait esperando que otrohilo lo desbloquee llamando a notify o notifyAll.

• Cuando se encuentre bloqueado en una petición de E/S, hasta que se complete laoperación de E/S.

Ciclo de vida de los hilos

Lo único que podremos saber es si un hilo se encuentra vivo o no, llamando a su métodoisAlive.

Además, una propiedad importante de los hilos será su prioridad. Mientras el hilo seencuentre vivo, el scheduler de la máquina virtual Java le asignará o lo sacará de la CPU,coordinando así el uso de la CPU por parte de todos los hilos activos basándose en suprioridad. Se puede forzar la salida de un hilo de la CPU llamando a su método yield.También se sacará un hilo de la CPU cuando un hilo de mayor prioridad se hagaEjecutable, o cuando el tiempo que se le haya asignado expire.

Para cambiar la prioridad de un hilo se utiliza el método setPriority, al que deberemosproporcionar un valor de prioridad entre MIN_PRIORITY y MAX_PRIORITY.

3.2.3. Sincronización de hilos

Muchas veces los hilos deberán trabajar de forma coordinada, por lo que es necesario unmecanismo de sincronización entre ellos.

Sección crítica

Hablamos de sección crítica para referirnos al fragmento de código que no debe serejecutado por más de un hilo de forma concurrente. Un primer mecanismo de

Programación en Lenguaje Java

29Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 30: Programación en Lenguaje Java

sincronización es la variable cerrojo incluida en todo objeto Object, que permitirá evitarque más de un hilo entre en la sección crítica. Los métodos declarados comosynchronized utilizan el cerrojo del objeto al que pertenecen evitando que más de unhilo entre en ellos al mismo tiempo.

public synchronized void seccion_critica(){

// Código sección crítica}

También podemos utilizar cualquier otro objeto para la sincronización dentro de nuestrométodo de la siguiente forma:

synchronized (objeto_con_cerrojo){

// Código sección crítica}

De esta forma estaremos utilizando el cerrojo del objeto especificado. Si dos hilosintentan entrar de forma concurrente en un bloque de código sincronizado con un mismoobjeto, el segundo de ellos quedará bloqueado hasta que el primero libere el cerrojo (salgade dicho bloque de código).

Deberemos:

• Utilizar sincronización sólo cuando sea realmente necesario.• No sincronizar clases que proporcionen datos fundamentales, dejar que el usuario

decida cuándo sincronizarlas en sus propias clases• No sincronizar un método si contiene un número elevado de operaciones que no

necesitan sincronización (reorganizar el método en varios, en ese caso). Se trata deminimizar el tiempo de bloqueo de hilos a lo estrictamente necesario

• Las asignaciones simples de tipos de datos se garantiza que serán atómicas (o secompletan o se quedan como al principio). No es necesario sincronizarlas, sólo sesincronizarán cuando haya varias asignaciones seguidas que sean interdependientes

Espera y notificación

Además podemos hacer que un hilo quede bloqueado a la espera de que otro hilo lodesbloquee cuando suceda un determinado evento. Para bloquear un hilo usaremos lafunción wait, para lo cual el hilo que llama a esta función debe estar en posesión delmonitor, cosa que ocurre dentro de un método synchronized, por lo que sólo podremosbloquear a un proceso dentro de estos métodos.

Para desbloquear a los hilos que haya bloqueados se utilizará notifyAll, o bien notify

para desbloquear sólo uno de ellos aleatoriamente. Para invocar estos métodos ocurrirá lomismo, el hilo deberá estar en posesión del monitor.

Cuando un hilo queda bloqueado liberará el cerrojo para que otro hilo pueda entrar en lasección crítica y desbloquearlo.

Programación en Lenguaje Java

30Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 31: Programación en Lenguaje Java

Deberemos utilizar notifyAll sólo cuando los hilos estén esperando por variascondiciones, o cuando varios hilos puedan seguir ejecutando a partir de dicho notifyAll.Para lo demás, utilizar notify, porque es más eficiente.

Esperar la finalización de un hilo

Por último, puede ser necesario esperar a que un determinado hilo haya finalizado sutarea para continuar. Esto lo podremos hacer llamando al método join de dicho hilo, quenos bloqueará hasta que el hilo haya finalizado.

3.2.4. Grupos de hilos

Los grupos de hilos nos permitirán crear una serie de hilos y manejarlos todos a la vezcomo un único objeto. Si al crear un hilo no se especifica ningún grupo de hilos, el hilocreado pertenecerá al grupo de hilos por defecto.

Podemos crearnos nuestro propio grupo de hilos instanciando un objeto de la claseThreadGroup. Para crear hilos dentro de este grupo deberemos pasar este grupo alconstructor de los hilos que creemos.

ThreadGroup grupo = new ThreadGroup("Grupo de hilos");Thread t = new Thread(grupo,new EjemploHilo());

3.2.5. Temporizadores

Los temporizadores nos permitirán planificar tareas para ser ejecutadas por un hilo ensegundo plano en un determinado instante de tiempo. Estos temporizadores serán deespecial interés cuando necesitemos ejecutar una tarea periódicamente. Para trabajar contemporizadores tenemos las clases Timer y TimerTask.

Lo primero que deberemos hacer es crear las tareas que queramos planificar. Para crearuna tarea crearemos una clase que herede de TimerTask, y que defina un método run

donde incluiremos el código que implemente la tarea.

public class MiTarea extends TimerTask {public void run() {

// Código de la tarea}

}

Una vez definida la tarea, utilizaremos un objeto Timer para planificarla. Para ellodeberemos establecer el tiempo de comienzo de dicha tarea, cosa que puede hacerse dedos formas diferentes:

• Retardo (delay): Nos permitirá planificar la tarea para que comience a ejecutarsetranscurrido un tiempo dado. Por ejemplo, podemos hacer que una determinada tareacomience a ejecutarse dentro de 10 segundos.

Programación en Lenguaje Java

31Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 32: Programación en Lenguaje Java

• Fecha y hora: Podemos hacer que la tarea comience a una determinada hora y fechadada en tiempo absoluto. Por ejemplo, podemos hacer que a las 8:00 se ejecute unatarea que haga de despertador.

Tenemos diferentes formas de planificación de tareas, según el número de veces y laperiodicidad con la que se ejecutan:

• Una sola ejecución: Se ejecuta en el tiempo de inicio especificado y no se vuelve aejecutar a no ser que la volvamos a planificar.

• Repetida con retardo fijo: Se ejecuta repetidas veces, con un determinado retardoentre cada dos ejecuciones consecutivas. Este retardo podremos especificarlonosotros. La tarea se volverá a ejecutar siempre transcurrido este tiempo desde laúltima vez que se ejecutó, hasta que detengamos el temporizador.

• Repetida con frecuencia constante: Se ejecuta repetidas veces con una frecuenciadada. Deberemos especificar el retardo que queremos entre dos ejecucionesconsecutivas. A diferencia del caso anterior, no se toma como referencia el tiempo deejecución de la tarea anterior, sino el tiempo de inicio de la primera ejecución. De estaforma, si una ejecución se retrasa por alguna razón, como por ejemplo por tenerdemasiada carga el procesador, la siguiente tarea comenzará transcurrido un tiempomenor, para mantener la frecuencia deseada.

Deberemos como primer paso crear el temporizador y la tarea que vamos a planificar:

Timer t = new Timer();TimerTask tarea = new MiTarea();

Ahora podemos planificarla para comenzar con un retardo, o bien a una determinadafecha y hora. Si vamos a hacerlo por retardo, utilizaremos uno de los siguientes métodos,según la periodicidad:

t.schedule(tarea, retardo); // Una vezt.schedule(tarea, retardo, periodo); // Retardo fijot.scheduleAtFixedRate(tarea, retardo, periodo); // Frecuenciaconstante

Si queremos comenzar a una determinada fecha y hora, deberemos utilizar un objeto Date

para especificar este tiempo de comienzo:

Calendar calendario = Calendar.getInstance();calendario.set(Calendar.HOUR_OF_DAY, 8);calendario.set(Calendar.MINUTE, 0);calendario.set(Calendar.SECOND, 0);calendario.set(Calendar.MONTH, Calendar.SEPTEMBER);calendario.set(Calendar.DAY_OF_MONTH, 22);Date fecha = calendario.getTime();

Una vez obtenido este objeto con la fecha a la que queremos comenzar la tarea (ennuestro ejemplo el día 22 de septiembre a las 8:00), podemos planificarla con eltemporizador igual que en el caso anterior:

t.schedule(tarea, fecha); // Una vez

Programación en Lenguaje Java

32Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 33: Programación en Lenguaje Java

t.schedule(tarea, fecha, periodo); // Retardo fijot.scheduleAtFixedRate(tarea, fecha, periodo); // Frecuenciaconstante

Los temporizadores nos serán útiles en las aplicaciones móviles para realizar aplicacionescomo por ejemplo agendas o alarmas. La planificación por retardo nos permitirá mostrarventanas de transición en nuestras aplicaciones durante un número determinado desegundos.

Si queremos que un temporizador no vuelva a ejecutar la tarea planificada, utilizaremossu método cancel para cancelarlo.

t.cancel();

Una vez cancelado el temporizador, no podrá volverse a poner en marcha de nuevo. Siqueremos volver a planificar la tarea deberemos crear un temporizador nuevo e instanciarde nuevo la tarea.

Programación en Lenguaje Java

33Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 34: Programación en Lenguaje Java

4. Ejercicios de Excepciones e hilos

4.1. Captura de excepciones

El fichero Ej1.java es un programa que toma un número como parámetro, y como salidamuestra el logaritmo de dicho número. Sin embargo, en ningún momento comprueba si seha proporcionado algún parámetro, ni si ese parámetro es un número. Se pide:

a) Compilar el programa y ejecutadlo de tres formas distintas:

• Sin parámetros

java Ej1

• Poniendo un parámetro no numérico

java Ej1 pepe

• Poniendo un parámetro numérico

java Ej1 30

Anotad las excepciones que se lanzan en cada caso (si se lanzan)

b) Modificar el código de main para que capture las excepciones producidas y muestrelos errores correspondientes en cada caso:

• Para comprobar si no hay parámetros se capturará una excepción de tipoArrayIndexOutOfBoundsException (para ver si el array de String que se pasa en elmain tiene algún elemento).

• Para comprobar si el parámetro es numérico, se capturará una excepción de tipoNumberFormatException.

Así, tendremos en el main algo como:

try{

// Tomar parámetro y asignarlo a un double} catch (ArrayIndexOutOfBoundsException e1) {

// Codigo a realizar si no hay parametros} catch (NumberFormatException e2) {

// Codigo a realizar con parametro no numerico}

Probad de nuevo el programa igual que en el caso anterior comprobando que lasexcepciones son capturadas y tratadas.

4.2. Lanzamiento de excepciones (*)

El fichero Ej2.java es similar al anterior, aunque ahora no vamos a tratar las

Programación en Lenguaje Java

34Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 35: Programación en Lenguaje Java

excepciones del main, sino las del método logaritmo: en la función que calcula ellogaritmo se comprueba si el valor introducido es menor o igual que 0, ya que para estosvalores la función logaritmo no está definida. Se pide:

a) Buscar entre las excepciones de Java la más adecuada para lanzar en este caso, queindique que a un método se le ha pasado un argumento ilegal. (Pista: Buscar entre lasclases derivadas de Exception. En este caso la más adecuada se encuentra entre lasderivadas de RuntimeException).

b) Una vez elegida la excepción adecuada, añadir código (en el método logaritmo) paraque en el caso de haber introducido un parámetro incorrecto se lance dicha excepción.

throw new ... // excepcion elegida

Probar el programa para comprobar el efecto que tiene el lanzamiento de la excepción.

c) Al no ser una excepción del tipo checked no hará falta que la capturemos ni quedeclaremos que puede ser lanzada. Vamos a crear nuestro propio tipo de excepciónderivada de Exception (de tipo checked) para ser lanzada en caso de introducir un valorno válido como parámetro. La excepción se llamará WrongParameterException y tendrála siguiente forma:

public class WrongParameterException extends Exception{

public WrongParameterException(String msg) {super(msg);

}}

Deberemos lanzarla en lugar de la escogida en el punto anterior.

throw new WrongParameterException(...);

Intentar compilar el programa y observar los errores que aparecen. ¿Por qué ocurre esto?Añadir los elementos necesarios al código para que compile y probarlo.

d) Por el momento controlamos que no se pase un número negativo como entrada. ¿Peroqué ocurre si la entrada no es un número válido? En ese caso se producirá una excepciónal convertir el valor de entrada y esa excepción se propagará automáticamente al nivelsuperior. Ya que tenemos una excepción que indica cuando el parámetro de entrada denuestra función es incorrecto, sería conveniente que siempre que esto ocurra se lancedicha excepción, independientemente de si ha sido causada por un número negativo o poralgo que no es un número, pero siempre conservando la información sobre la causa queprodujo el error. Utilizar nested exceptions para realizar esto.

AyudaDeberemos añadir un nuevo constructor a WrongParameterException en el que seproporcione la excepción que causó el error. En la función logaritmo capturaremos cualquierexcepción que se produzca al convertir la cadena a número, y lanzaremos una excepciónWrongParameterException que incluya la excepción causante.

Programación en Lenguaje Java

35Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 36: Programación en Lenguaje Java

4.3. Terminación de hilos

En la clase Ej3 se crean hilos utilizando la interfaz Runnable. De esta forma podremoscrear múltiples hilos que ejecuten el mismo método run de esta clase accediendo almismo espacio de memoria de este objeto. Ejecutar el programa, ver lo que hace,consultar el código fuente y contestar a las siguientes preguntas:

a) Explica que es lo que hace la condición de terminación del bucle while en el métodorun. ¿Qué utilidad le ves?

b) ¿Puede haber en algún momento dos hilos ejecutando simultáneamente el método run?

c) ¿Si hubiese dos hilos ejecutando el run, podría haber conflictos en el acceso a lavariable ini? Es decir, que un hilo sobrescriba el valor que había escrito en ella el otrohilo ¿Por qué?

d) ¿Cómo podemos parar este hilo sin crear uno nuevo?

e) Escribir un programa Ej3b que realice una función similar pero utilizandotemporizadores en lugar de hilos. No es necesario que sea exactamente igual al anteriorprograma, simplemente debe ser similar.

AyudaSe deberá crear una TimerTask que guarde en un campo privado el instante de tiempo en elque fue creada. Cada vez que se ejecute esta tarea deberá imprimir ese instante. Desde elprograma principal crearemos la tarea, y la programaremos en un temporizador que la ejecuteperiódicamente. Transcurridos 5 segundos, el programa principal deberá cancelar eltemporizador.

4.4. Carrera de hilos (*)

En la clase Ej4 tenemos un programa que muestre una carrera entre tres hilos de distintasprioridades. Se pide:

a) Cada hilo tiene su propio contador que se va incrementando cada iteración de dichohilo. En este caso, ¿podrá haber conflicto en el acceso al contador del hilo entre losdistintos hilos? ¿Por qué?

b) Fíjate en el bucle principal en el que se imprime el contador de cada hilo, ¿cuál es sucondición de terminación?

c) En cada iteración los hilos hacen una operación costosa. En este caso estamos forzandoa que llamen al colector de basura (garbage collector) con la instrucción System.gc().Sustituir esta instrucción por dormir durante 100ms y probar. ¿Qué ocurre en este caso?¿Por qué? Volver a dejar el programa como antes, con la llamada al colector de basura.

Programación en Lenguaje Java

36Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 37: Programación en Lenguaje Java

d) En el método run de Hilo, añade la instrucción necesaria para que en el caso de que elhilo t sea distinto de null se quede bloqueado hasta que dicho hilo termine su ejecución.Probar el programa y ver lo que ocurre en este caso.

e) Prueba cambiando las prioridades de los hilos. Cuando tenemos hilos de alta prioridade hilos de baja prioridad, cuando los de alta prioridad terminan, ¿qué ocurre con los debaja prioridad? ¿por qué?

4.5. Productor y consumidor (*)

En este ejercicio vamos a resolver el problema de los productores y los consumidores.Vamos a definir 3 clases: el hilo Productor, el hilo Consumidor, y el objeto Recipiente

donde el productor deposita el valor producido, y de donde el consumidor extrae losdatos.

• El productor se ejecuta durante 10 iteraciones y en cada una de ellas deposita en elrecipiente el número de la iteración actual. Entre iteración e iteración se quedarádurmiendo durante un tiempo aleatorio entre 1 y 2 segundos.

• El consumidor se ejecuta el mismo número de iteraciones que el productor, pero encada una de ellas saca el valor almacenado en el recipiente y lo muestra por pantalla.Entre cada iteración duerme también un tiempo aleatorio entre 1 y 2 segundos.

• El recipiente proporciona los métodos produce y consume para depositar un dato enél y para sacarlo de él respectivamente.

El programa mostrará cuando el productor produce un valor y cuando el consumidor loconsume. El funcionamiento correcto debería ser que el consumidor consumaexactamente los mismos valores que el productor ha producido, sin saltarse ninguno nirepetirlos. Se pide:

a) Compilar y probar el programa. ¿Funciona correctamente? ¿Por qué? Ejecutar variasveces y explicar lo que pasa. ¿Qué tendremos que hacer para que funcione correctamente?

b) Vamos a añadir el código necesario en los métodos produce y consume parasincronizar el acceso a ellos. El comportamiento debería ser el siguiente:

• Si queremos producir y todavía hay datos disponibles en el recipiente, esperaremoshasta que se saquen, si no produciremos y avisamos a posibles consumidores queestén a la espera.

• Si queremos consumir y no hay datos disponibles en el recipiente, esperaremos hastaque se produzcan, si no consumimos el valor disponible y avisamos a posiblesproductores que estén a la espera.

¿Qué métodos utilizaremos para la sincronización? Insertar el código necesario ycompilar. Probar el programa, ¿da alguna excepción? En caso afirmativo, ¿por qué? ¿seránecesario añadir algo más en el encabezado de los métodos produce y consume? Hacerlas modificaciones necesarias.

Programación en Lenguaje Java

37Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 38: Programación en Lenguaje Java

Compilar y comprobar que el programa funciona correctamente.

Programación en Lenguaje Java

38Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 39: Programación en Lenguaje Java

5. Flujos de E/S y Red

5.1. Entrada/Salida

Los programas muy a menudo necesitan enviar datos a un determinado destino, o bienleerlos de una determinada fuente externa, como por ejemplo puede ser un fichero paraalmacenar datos de forma permanente, o bien enviar datos a través de la red, a memoria, oa otros programas. Esta entrada/salida de datos en Java la realizaremos por medio deflujos (streams) de datos, a través de los cuales un programa podrá recibir o enviar datosen serie.

5.1.1. Flujos de datos de entrada/salida

Existen varios objetos que hacen de flujos de datos, y que se distinguen por la finalidaddel flujo de datos y por el tipo de datos que viajen a través de ellos. Según el tipo dedatos que transporten podemos distinguir:

• Flujos de caracteres• Flujos de bytes

Dentro de cada uno de estos grupos tenemos varios pares de objetos, de los cuales unonos servirá para leer del flujo y el otro para escribir en él. Cada par de objetos seráutilizado para comunicarse con distintos elementos (memoria, ficheros, red u otrosprogramas). Estas clases, según sean de entrada o salida y según sean de caracteres o debytes llevarán distintos sufijos, según se muestra en la siguiente tabla:

Flujo de entrada / lector Flujo de salida / escritor

Caractéres _Reader _Writer

Bytes _InputStream _OutputStream

Donde el prefijo se referirá a la fuente o sumidero de los datos que puede tomar valorescomo los que se muestran a continuación:

File_ Acceso a ficheros

Piped_ Comunicación entre programas mediante tuberías (pipes)

String_ Acceso a una cadena en memoria (solo caracteres)

CharArray_ Acceso a un array de caracteres en memoria (solo caracteres)

ByteArray_ Acceso a un array de bytes en memoria (solo bytes)

Además podemos distinguir los flujos de datos según su propósito, pudiendo ser:

Programación en Lenguaje Java

39Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 40: Programación en Lenguaje Java

• Canales de datos, simplemente para leer o escribir datos directamente en una fuente osumidero externo.

• Flujos de procesamiento, que además de enviar o recibir datos realizan algúnprocesamiento con ellos. Tenemos por ejemplo flujos que realizan un filtrado de losdatos que viajan a través de ellos (con prefijo Filter), conversores datos (con prefijoData), bufferes de datos (con prefijo Buffered), preparados para la impresión deelementos (con prefijo Print), etc.

Un tipo de filtros de procesamiento a destacar son aquellos que nos permiten convertir unflujo de bytes a flujo de caracteres. Estos objetos son InputStreamReader yOutputStreamWriter. Como podemos ver en su sufijo, son flujos de caracteres, pero seconstruyen a partir de flujos de bytes, permitiendo de esta manera acceder a nuestro flujode bytes como si fuese un flujo de caracteres.

Para cada uno de los tipos básicos de flujo que hemos visto existe una superclase, de laque heredaran todos sus subtipos, y que contienen una serie de métodos que seráncomunes a todos ellos. Entre estos métodos encontramos los métodos básicos para leer oescribir caracteres o bytes en el flujo a bajo nivel. En la siguiente tabla se muestran losmétodos más importantes de cada objeto:

InputStream read(), reset(), available(), close()

OutputStream write(int b), flush(), close()

Reader read(), reset(), close()

Writer write(int c), flush(), close()

A parte de estos métodos podemos encontrar variantes de los métodos de lectura yescritura, otros métodos, y además cada tipo específico de flujo contendrá sus propiosmétodos. Todas estas clases se encuentran en el paquete java.io. Para más detalles sobreellas se puede consultar la especificación de la API de Java.

5.1.2. Entrada, salida y salida de error estándar

Al igual que en C, en Java también existen los conceptos de entrada, salida, y salida deerror estándar. La entrada estándar normalmente se refiere a lo que el usuario escribe enla consola, aunque el sistema operativo puede hacer que se tome de otra fuente. De lamisma forma la salida y la salida de error estándar lo que hacen normalmente es mostrarlos mensajes y los errores del programa respectivamente en la consola, aunque el sistemaoperativo también podrá redirigirlas a otro destino.

En Java esta entrada, salida y salida de error estándar se tratan de la misma forma quecualquier otro flujo de datos, estando estos tres elementos encapsulados en tres objetos deflujo de datos que se encuentran como propiedades estáticas de la clase System:

Tipo Objeto

Programación en Lenguaje Java

40Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 41: Programación en Lenguaje Java

Entrada estándar InputStream System.in

Salida estándar PrintStream System.out

Salida de error estándar PrintStream System.err

Para la entrada estándar vemos que se utiliza un objeto InputStream básico, sin embargopara la salida se utilizan objetos PrintWriter que facilitan la impresión de textoofreciendo a parte del método común de bajo nivel write para escribir bytes, dosmétodos más: print y println. Estas funciones nos permitirán escribir cualquier cadena,tipo básico, o bien cualquier objeto que defina el método toString que devuelva unarepresentación del objeto en forma de cadena. La única diferencia entre los dos métodoses que el segundo añade automáticamente un salto de línea al final del texto impreso,mientras que en el primero deberemos especificar explícitamente este salto.

Para escribir texto en la consola normalmente utilizaremos:

System.out.println("Hola mundo");

En el caso de la impresión de errores por la salida de error de estándar, deberemosutilizar:

System.err.println("Error: Se ha producido un error");

Además la clase System nos permite sustituir estos flujos por defecto por otros flujos,cambiando de esta forma la entrada, salida y salida de error estándar.

TrucoPodemos ahorrar tiempo si en Eclipse en lugar de escribir System.out.println escribimossimplemente sysout y tras esto pulsamos Ctrl + Espacio.

5.1.3. Acceso a ficheros

Podremos acceder a ficheros bien por caracteres, o bien de forma binaria (por bytes). Lasclases que utilizaremos en cada caso son:

Lectura Escritura

Caracteres FileReader FileWriter

Binarios FileInputStream FileOutputStream

Para crear un lector o escritor de ficheros deberemos proporcionar al constructor elfichero del que queremos leer o en el que queramos escribir. Podremos proporcionar estainformación bien como una cadena de texto con el nombre del fichero, o bienconstruyendo un objeto File representando al fichero al que queremos acceder. Esteobjeto nos permitirá obtener información adicional sobre el fichero, a parte de permitirnosrealizar operaciones sobre el sistema de ficheros.

Programación en Lenguaje Java

41Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 42: Programación en Lenguaje Java

A continuación vemos un ejemplo simple de la copia de un fichero carácter a carácter:

public void copia_fichero() {int c;try {FileReader in = new FileReader("fuente.txt");FileWriter out = new FileWriter("destino.txt");

while( (c = in.read()) != -1) {out.write(c);

}

in.close();out.close();

} catch(FileNotFoundException e1) {System.err.println("Error: No se encuentra el fichero");

} catch(IOException e2) {System.err.println("Error leyendo/escribiendo fichero");

}}

En el ejemplo podemos ver que para el acceso a un fichero es necesario capturar dosexcepciones, para el caso de que no exista el fichero al que queramos acceder y por si seproduce un error en la E/S.

Para la escritura podemos utilizar el método anterior, aunque muchas veces nos resultarámucho más cómodo utilizar un objeto PrintWriter con el que podamos escribirdirectamente líneas de texto:

public void escribe_fichero() {FileWriter out = null;PrintWriter p_out = null;

try {out = new FileWriter("result.txt");p_out = new PrintWriter(out);p_out.println("Este texto será escrito en el fichero de salida");

} catch(IOException e) {System.err.println("Error al escribir en el fichero");

} finally {p_out.close();

}}

5.1.4. Acceso a los recursos

Hemos visto como leer y escribir ficheros, pero cuando ejecutamos una aplicacióncontenida en un fichero JAR, puede que necesitemos leer recursos contenidos dentro deeste JAR.

Para acceder a estos recursos deberemos abrir un flujo de entrada que se encargue de leer

Programación en Lenguaje Java

42Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 43: Programación en Lenguaje Java

su contenido. Para ello utilizaremos el método getResourceAsStream de la clase Class:

InputStream in = getClass().getResourceAsStream("/datos.txt");

De esta forma podremos utilizar el flujo de entrada obtenido para leer el contenido delfichero que hayamos indicado. Este fichero deberá estar contenido en el JAR de laaplicación.

Especificamos el carácter '/' delante del nombre del recurso para referenciarlo de formarelativa al directorio raíz del JAR. Si no lo especificásemos de esta forma se buscaría deforma relativa al directorio correspondiente al paquete de la clase actual.

5.1.5. Codificación de datos

Si queremos guardar datos en un fichero binario deberemos codificar estos datos en formade array de bytes. Los flujos de procesamiento DataInputStream y DataOutputStream

nos permitirán codificar y descodificar respectivamente los tipos de datos simples enforma de array de bytes para ser enviados a través de un flujo de datos.

Por ejemplo, podemos codificar datos en un array en memoria(ByteArrayOutputStream) de la siguiente forma:

String nombre = "Jose";String edad = 25;ByteArrayOutputStream baos = new ByteArrayOutputStream();DataOutputStream dos = new DataOutputStream(baos);

dos.writeUTF(nombre);dos.writeInt(edad);dos.close();baos.close();

byte [] datos = baos.toByteArray();

Podremos descodificar este array de bytes realizando el procedimiento inverso, con unflujo que lea un array de bytes de memoria (ByteArrayInputStream):

ByteArrayInputStream bais = new ByteArrayInputStream(datos);DataInputStream dis = new DataInputStream(bais);String nombre = dis.readUTF();int edad = dis.readInt();

Si en lugar de almacenar estos datos codificados en una array en memoria queremosguardarlos codificados en un fichero, haremos lo mismo simplemente sustituyendo elflujo canal de datos ByteArrayOutputStream por un FileOutputStream. De esta formapodremos utilizar cualquier canal de datos para enviar estos datos codificados a través deél.

5.1.6. Serialización de objetos

Programación en Lenguaje Java

43Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 44: Programación en Lenguaje Java

Si queremos enviar un objeto a través de un flujo de datos, deberemos convertirlo en unaserie de bytes. Esto es lo que se conoce como serialización de objetos, que nos permitiráleer y escribir objetos.

Para leer o escribir objetos podemos utilizar los objetos ObjectInputStream yObjectOutputStream que incorporan los métodos readObject y writeObject

respectivamente. Los objetos que escribamos en dicho flujo deben tener la capacidad deser serializables.

Serán serializables aquellos objetos que implementan la interfaz Serializable. Cuandoqueramos hacer que una clase definida por nosotros sea serializable deberemosimplementar dicho interfaz, que no define ninguna función, sólo se utiliza para identificarlas clases que son serializables. Para que nuestra clase pueda ser serializable, todas suspropiedades deberán ser de tipos de datos básicos o bien objetos que también seanserializables.

Un uso común de la serialización se realiza en los Transfer Objetcs. Este tipo de objetosdeben ser serializables para así poderse intercambiar entre todas las capas de laaplicación, aunque se encuentren en máquinas diferentes.

5.2. Red

En este apartado veremos como establecer una comunicación en red mediante URLs. EnJava también se pueden establecer conexiones de red a más bajo nivel mediante sockets(protocolos TCP y UDP), pero muchas veces los puertos a los que accedemos seencuentran cortados por firewalls intermedios, por lo que estos tipos de conexiones noserán adecuados para acceder a servidores en Internet.

Además las APIs de Java nos ofrecen numerosas facilidades para trabajar con URLs quesigan protocolos estándar (como por ejemplo HTTP), tanto en el lado del cliente como enel servidor, por lo que será recomendable establecer la comunicación entre nuestro clientey nuestro servidor utilizando estos protocolos.

Java nos permite además utilizar mecanismos de comunicación por red de más alto nivelcomo por ejemplo RMI que nos permite invocar métodos de objetos remotos. Esto loveremos con más detalle en módulos posteriores.

5.2.1. Acceso a URLs

Una URL (Uniform Resource Locator) es una cadena utilizada para localizar un recursoen Internet. Dentro de la URL podemos distinguir varias componentes:

protocolo://servidor[:puerto]/recurso

Por ejemplo, en el caso de la dirección http://www.ua.es/es/index.html lo que sehará será acceder al servidor www.ua.es mediante protocolo HTTP y solicitar el recurso

Programación en Lenguaje Java

44Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 45: Programación en Lenguaje Java

/es/index.html. El puerto por defecto es el 80, pero si el servidor Web atendiese en unpuerto distinto a este deberíamos especificarlo también en la URL.

En Java tenemos el objeto URL que se encargará de representar las URLs. Podemosconstruir un objeto URL a partir del nombre completo de la URL:

URL url = new URL("http://www.ua.es/es/index.html");

Dado que muchas veces se especifican links relativos, será de ayuda contar con unsegundo constructor que nos permita crear URLs a partir de la dirección base donde nosencontremos y de la dirección relativa solicitada:

URL url = new URL(direccion_base, direccion_relativa);

Aquí la dirección relativa puede referirse a un recurso alojado en el servidor donde nosencontremos o bien a un destino dentro de la web donde estamos, referenciado mediante#nombre_destino.

Existen más constructores de esta clase, permitiéndonos por ejemplo construir una URLdando cada elemento (protocolo, servidor, puerto, recurso) por separado. Siempre quecreemos una URL deberemos capturar la excepción MalformedURLException que seproducirá en el caso de estar mal construida.

try {URL url = new URL("http://www.ua.es/es/index.html");

} catch(MalformedURLException e) {System.err.println("Error: URL mal construida");

}

La clase URL proporciona métodos para obtener información sobre la URL querepresenta.

5.2.2. Lectura del contenido

Para leer desde la dirección URL representada por el objeto deberemos obtener un flujode entrada provinente de ella. Para obtener este flujo utilizaremos el método openStream

del objeto URL.

InputStream in = url.openStream();

Una vez obtenido este flujo de entrada podremos leer de él o bien transformarlo a otrotipo de flujo como por ejemplo a un flujo de caracteres o de procesamiento.

Esto puede ser suficiente en muchos casos, en los que sólo necesitemos leer el contenidode un recurso localizado mediante dicha URL. Por ejemplo, si la URL corresponde a unapágina web, al leer de ella obtendremos el código de dicha página web.

Sin embargo, muchas veces necesitaremos poder enviar información al servidor y accedera una serie de propiedades sobre la respuesta que hemos obtenido del mismo. Para haceresto deberemos crear una conexión con la URL, y utilizar esta conexión para enviar o

Programación en Lenguaje Java

45Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 46: Programación en Lenguaje Java

recibir información.

5.2.3. Conexión con la URL

Podemos crear una conexión con la URL utilizando el método openConnection delobjeto URL que nos devolverá un objeto del tipo URLConnection. Estableciendo unaconexión podremos leer o escribir datos en la URL.

La clase URLConnection es una clase abstracta, que define de forma genérica unaconexión con una URL cualquiera. En realidad el objeto que habremos obtenido será unasubclase de URLConnection especializada en el protocolo con el que estemos trabajando.Por ejemplo si trabajamos con HTTP, en realidad habremos obtenido un objetoHttpURLConnection.

Cuando accedamos a una URL, primero se enviará un mensaje de petición al servidor alque hace referencia la URL para solicitar la información deseada. En este mensaje depetición se podrán incluir una serie de propiedades con información y un bloque decontenido que queramos enviar al servidor. En estas propiedades que incluimos comocabecera podremos especificar por ejemplo información sobre el agente de usuario, comoel tipo de cliente que está accediendo al servidor y el idioma de este cliente. Ademáspodemos incluir cualquier contenido que queramos al cuerpo del mensaje.

Una vez se envíe el mensaje de petición al servidor, éste nos devolverá un mensaje derespuesta. En esta respuesta podremos encontrar también una serie de cabeceras coninformación sobre la misma, y un bloque de contenido que anteriormente hemos vistocómo leer.

Todo este proceso de composición del mensaje de petición, envío del mensaje, recepcióndel mensaje de respuesta, y análisis del mismo lo realiza internamente la claseURLConnection. Nosotros podremos utilizar esta clase para configurar el mensaje depetición y obtener la información del mensaje de respuesta de forma sencilla.

Una vez creada la conexión, ésta pasará por tres estados:

• Configuración: No se ha establecido la conexión, todavía no se ha enviado elmensaje de petición. Este será el momento en el que deberemos añadir la informaciónnecesaria a las cabeceras del mensaje de petición.

• Conectada: El mensaje de petición ya se ha enviado, y se espera recibir unarespuesta. En este momento podremos leer las cabeceras o el contenido de larespuesta.

• Cerrada: La conexión se ha cerrado y ya no podemos hacer nada con ella.

La conexión nada más crearse se encuentra en estado de configuración. Pasaráautomáticamente a estado conectada cuando solicitemos cualquier información sobre larespuesta.

Enviar o recibir datos

Programación en Lenguaje Java

46Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 47: Programación en Lenguaje Java

Para enviar o recibir el contenido de los mensajes de petición y respuesta respectivamentedeberemos hacer lo siguiente:

• Crear la conexión

URLConnection con = url.openConnection();

• Si vamos a enviar datos a través de ella, establecer la capacidad de salida con:

con.setDoOutput(true);

• Si vamos a escribir en la petición, obtener el flujo de salida con:

OutputStream out = con.getOutputStream();

• Si vamos a leer la respuesta, obtener el flujo de entrada con:

InputStream in = con.getInputStream();

• Leer o escribir en los flujos de entrada y de salida obtenidos como vimos en capítulosanteriores.

Al obtener los flujos para escribir o leer en la conexión estaremos haciendo que pase aestado conectado, por lo que no podremos continuar configurando las propiedades de lapetición después de hacer esto. Estas propiedades de configuración las deberemosestablecer previamente a la apertura de estos flujos, como veremos a continuación.

Mensaje de petición

En fase de configuración podremos añadir una serie de propiedades al mensaje depetición para configurarlo. Estas propiedades serán parejas <clave, valor>. Lasañadiremos con el siguiente método:

con.setRequestProperty(nombre, valor);

Por ejemplo, podemos mandar las siguiente cabeceras:

con.setRequestProperty("IF-Modified-Since","22 Sep 2002 08:00:00 GMT");

con.setRequestProperty("Content-Language", "es-ES");

Con esto estaremos diciendo al servidor que queremos que nos devuelva una respuestasólo si ha sido modificada desde la fecha indicada, y además le estamos comunicandodatos sobre el cliente, como que el lenguaje del cliente es español de España.

Cabeceras de la respuesta

Una vez hayamos terminado de configurar el mensaje de petición, podemos pasar aestado conectado. En este estado, además de leer el contenido del mensaje de respuesta,podremos obtener información sobre esta respuesta consultando una serie de cabecerasincluidas en este mensaje.

Programación en Lenguaje Java

47Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 48: Programación en Lenguaje Java

También podemos utilizar este objeto para leer las cabeceras que nos ha devuelto larespuesta. Nos ofrece métodos para leer una serie de cabeceras estándar de HTTP comolos siguientes:

getLength content-length Longitud del contenido, o -1 si la longitud esdesconocida

getType content-type Tipo MIME del contenido devuelto

getEncoding content-encoding Codificación del contenido

getExpiration expires Fecha de expiración del recurso

getDate date Fecha de envío del recurso

getLastModified last-modified Fecha de última modificación del recurso

Puede ser que queramos obtener otras cabeceras, como por ejemplo cabeceras propias noestándar. Para ello tendremos una serie de métodos que obtendrán las cabecerasdirectamente por su nombre:

String valor = con.getHeaderField(nombre);int valor = con.getHeaderFieldInt(nombre, default);long valor = con.getHeaderFieldDate(nombre, default);

De esta forma podemos obtener el valor de la cabecera o bien como una cadena, o en losdatos que sean de tipo fecha (valor long) o enteros también podremos obtener su valordirectamente en estos tipos de datos.

Podremos acceder a las cabeceras también a partir de su índice:

String valor = con.getHeaderField(int indice);String nombre = con.getHeaderFieldKey(int indice);

Podemos obtener de esta forma tanto el nombre como el valor de la cabecera que ocupaun determinado índice.

Tanto los métodos que obtienen un flujo para leer o escribir en la conexión, como estosmétodos que acabamos de ver para obtener información sobre la respuesta producirán unatransición al estado conectado.

Ejemplo

Vamos a ver un ejemplo de como enviar datos al servidor, y posteriormente leer larespuesta. Cuando enviemos datos será conveniente proporcionar el tamaño y el tipoMIME de este contenido para que pueda ser interpretado correctamente. Estos datos seincluirán como propiedades de la petición.

// Creamos la URLURL url = new URL("http://jtech.ua.es/chat/enviar");

// Creamos la conexion

Programación en Lenguaje Java

48Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 49: Programación en Lenguaje Java

URLConnection con = url.openConnection();

// Activamos la salida de datos en la conexióncon.setDoOutput(true);

// Escribimos los datos en un buffer en memoriaByteArrayOutputStream baos = new ByteArrayOutputStream();DataOutputStream dos = new DataOutputStream(baos);dos.writeUTF(nick);dos.writeUTF(msg);dos.close();

// Establecemos las propiedades de tipo y tamaño del contenidocon.setRequestProperty("Content-Length",String.valueOf(baos.size()));con.setRequestProperty("Content-Type", "application/octet-stream");

// Abrimos el flujo de salida para enviar los datos al servidorOutputStream out = con.getOutputStream();baos.writeTo(out);

// Abrimos el flujo de entrada para leer la respuesta obtenidaInputStream in = con.getInputStream();

Programación en Lenguaje Java

49Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 50: Programación en Lenguaje Java

6. Ejercicios de Flujos de E/S y Red

6.1. Leer un fichero de texto

Vamos a realizar un programa que lea un fichero de texto ASCII, y lo vaya mostrando porpantalla. El esqueleto del programa se encuentra en el fichero Ej1.java. Se pide:

a) ¿Qué tipo de flujo de datos utilizaremos para leer el fichero? Añadir al programa lacreación del flujo de datos adecuado (variable in), compilar y comprobar su correctofuncionamiento.

b) Podemos utilizar un flujo de procesamiento llamado BufferedReader que mantendráun buffer de los caracteres leídos y nos permitirá leer el fichero línea a línea además deutilizar los métodos de lectura a más bajo nivel que estamos usando en el ejemplo.Consultar la documentación de la clase BufferedReader y aplicar la transformaciónsobre el flujo de entrada (ahora in deberá ser un objeto BufferedReader). Compilar ycomprobar que sigue funcionando correctamente el método de lectura implementado.

c) Ahora vamos a cambiar la forma de leer el fichero y lo vamos a hacer línea a líneaaprovechando el BufferedReader. ¿Qué método de este objeto nos permite leer líneas dela entrada? ¿Qué nos devolverá este método cuando se haya llegado al final el fichero?Implementar un bucle que vaya leyendo estas líneas y las vaya imprimiendo, hasta llegaral final del fichero. Compilar y comprobar que sigue funcionando de la misma forma.

6.2. Copia de ficheros (*)

En el fichero Ej2.java tenemos un programa que realizará una copia de ficheros en Java.Se pide:

a) Intentar compilar el programa y ver que errores obtenemos. Añadir la captura de lasexcepciones necesarias para que el programa compile (no capturar Exception engeneral).

b) Probar el programa copiando un fichero de texto. Comprobar que la copia se ha hechocorrectamente.

c) Ahora probar a copiar un fichero ejecutable (binario) con nuestro programa. Intentarejecutar la copia y comprobar que falla. ¿Por qué ocurre esto? ¿Cómo podemossolucionarlo? Cambiar el tipo de flujo de datos para solucionar este problema ycomprobar que la nueva versión realiza un copia correcta del ejecutable .

6.3. Lectura de una URL

El fichero Ej3.java es un programa que tomará una URL como parámetro, accederá a

Programación en Lenguaje Java

50Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 51: Programación en Lenguaje Java

ella y leerá su contenido mostrándolo por pantalla. Deberemos añadir código para:

a) Crear un objeto URL para la url especificada en el método creaURL, capturando lasposibles excepciones que se pueden producir si está mal formada y mostrando el mensajede error correspondiente por la salida de error. Compilar y comprobar que ocurre al pasarURLs correctas e incorrectas.

b) Abrir un flujo de entrada desde la URL indicada en el método leeURL. Deberemosobtener un InputStreamde la URL, y convertirlo a un objeto BufferedReader,aplicando las transformaciones intermedias necesarias, para poder leer de la URL loscaracteres línea a línea. Comprobar que lee correctamente algunas URLs conocidas.Descomentar el bloque de código que realiza la lectura de la URL.

6.4. Foro de Internet (*)

Vamos a realizar una aplicación que nos permita acceder a un foro en Internet. Partiremosde la plantilla que tenemos en el fichero Ej4.java. Se pide:

a) Introducir en el método leeMensajes el código necesario para leer los mensajespublicados en el foro. Para ello accederemos a la URL definida en la constanteURL_LISTA y leeremos el contenido que nos proporcione mediante un objetoDataInputStream. Esta información nos la devolverá como una serie de cadenas (UTF)cada una de las cuales correspondientes a uno de los mensajes.

<mensaje1:UTF><mensaje2:UTF>...<mensajeN:UTF>

Leeremos mensajes hasta llegar al final del flujo (cuando se produzca una excepciónEOFException), e imprimiremos en la consola cada mensaje leído de la siguiente forma:

DataInputStream dis = new DataInputStream(in);try {while(true) {String msg = dis.readUTF();System.out.println(msg);

}} catch(EOFException e) {}

b) Ahora vamos a permitir enviar un mensaje al foro. Para ello introduciremos el códigonecesario en el método enviaMensaje, en el que utilizaremos un objetoDataOutputStream para enviar los datos a la URL definida en URL_ENVIAR. Deberemosenviar nuestro nick y el mensaje de la siguiente forma:

DataOutputStream dos = new DataOutputStream(out);dos.writeUTF(nick);dos.writeUTF(msg);

6.5. Gestión de productos (*)

Programación en Lenguaje Java

51Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 52: Programación en Lenguaje Java

Vamos a hacer una aplicación para gestionar una lista de productos que vende nuestraempresa. Escribiremos la información de estos productos en un fichero, paraalmacenarlos de forma persistente. Se pide:

a) Introducir el código necesario en el método almacenar de la clase GestionProductos

para guardar la información de los productos en el fichero definido en la constanteFICHERO_DATOS. Guardaremos esta información codificada en un fichero binario.Deberemos codificar los datos de cada producto (titulo, autor, precio y disponibilidad)utilizando un objeto DataOutputStream.

b) Introducir en el método recuperar el código para cargar la información de estefichero. Para hacer esto deberemos realizar el procedimiento inverso, utilizando un objetoDataInputStream para leer los datos de los productos almacenados. Leeremos productoshasta llegar al final del fichero, cuando esto ocurra se producirá una excepción del tipoEOFException que podremos utilizar como criterio de parada.

c) Modificar el código anterior para, en lugar de codificar manualmente los datos en elfichero, utilizar la serialización de objetos para almacenar y recuperar objetos Productodel fichero.

Programación en Lenguaje Java

52Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 53: Programación en Lenguaje Java

7. Introducción a los clientes ricos

En este tema veremos una introducción a lo que en algunos manuales se llaman "clientesricos", es decir, clientes de aplicaciones que admiten una gran variedad defuncionalidades, debida a que la API para construirlos las ofrece. En concreto hablamosde la librería Swing de Java.

Para poder entenderla mejor, primero daremos un repaso a la librería AWT (la primeralibrería gráfica de Java), y su evolución a Swing, explicando los elementos básicos de unay otra para poder construir una aplicación.

7.1. Introducción a AWT y Swing

7.1.1. AWT

AWT (Abstract Windows Toolkit) es la parte de Java que se emplea para construirinterfaces gráficas de usuario. Este paquete ha estado presente desde la primera versión(la 1.0), aunque con la 1.1 sufrió un cambio notable. En la versión 1.2 se incorporótambién a Java una librería adicional, Swing, que enriquece a AWT en la construcción deaplicaciones gráficas.

Controles de AWT

AWT proporciona una serie de controles (elementos gráficos como botones, listas,menús, etc), que podremos colocar en las aplicaciones visuales que implementemos.Dichos controles son subclases de la clase Component, y forman parte del paquetejava.awt. Algunos de los controles más comunes son:

Estructura de clases de los controles más comunes

Pasamos a continuación a explicar estos y otros controles más detalladamente en la

Programación en Lenguaje Java

53Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 54: Programación en Lenguaje Java

siguiente tabla:

Component La clase padre Component no se puede utilizar directamente. Es una claseabstracta, que proporciona algunos métodos útiles para sus subclases.

Botones Para emplear la clase Button, en el constructor simplemente indicamos el textoque queremos que tenga :

Button boton = new Button("Pulsame");

Etiquetas Para utilizar Label, el uso es muy similar al botón: se crea el objeto con el textoque queremos darle:

Label etiq = new Label("Etiqueta");

Areas de dibujo La clase Canvas se emplea para heredar de ella y crear componentespersonalizados. Accediendo al objeto Graphics de los elementos podremos darlela apariencia que queramos: dibujar líneas, pegar imágenes, etc:

Panel p = new Panel();p.getGraphics().drawLine(0, 0, 100, 100);p.getGraphics().drawImage(...);

Casillas de verificación Checkbox se emplea para marcar o desmarcar opciones. Podremos tenercontroles aislados, o grupos de Checkboxes en un objeto CheckboxGroup, deforma que sólo una de las casillas del grupo pueda marcarse cada vez.

Checkbox cb = new Checkbox("Mostrar subdirectorios",false);

System.out.println ("Esta marcada: " +cb.getState());

Listas Para utilizar una lista desplegable (objeto Choice ), se crea el objeto y se añaden,con el método addItem(...), los elementos que queramos a la lista:

Choice ch = new Choice();ch.addItem("Opcion 1");ch.addItem("Opcion 2");...int i = ch.getSelectedIndex();

Para utilizar listas fijas (objeto List), en el constructor indicamos cuántoselementos son visibles. También podemos indicar si se permite seleccionar varioselementos a la vez. Dispone de muchos de los métodos que tiene Choice paraañadir y consultar elementos.

List lst = new List(3, true);lst.addItem("Opcion 1");lst.addItem("Opcion 2");

Programación en Lenguaje Java

54Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 55: Programación en Lenguaje Java

Cuadros de texto Al trabajar con TextField o TextArea, se indica opcionalmente en el constructorel número de columnas (y filas en el caso de TextArea) que se quieren en elcuadro de texto.

TextField tf = new TextField(30);TextArea ta = new TextArea(5, 40);...tf.setText("Hola");ta.appendText("Texto 2");String texto = ta.getText();

Diálogos, ventanas ycontenedores

Se tiene la clase Frame para crear ventanas principales de aplicaciones, y la claseDialog para crear subventanas que dependan de ventanas principales:

Frame f = new Frame();Dialog dlg = new Dialog();

Son lo que se llama contenedores de nivel superior. También se tienencontenedores intermedios, como la clase Panel, que nos permiten organizar loscontroles dentro de los contenedores superiores.

Para añadir elementos (botones, listas, etiquetas, etc) en un contenedor, basta conllamar a su método add correspondiente:

Frame f = new Frame();Panel p = new Panel();

Button b = new Button();Label l = new Label();

p.add(b);p.add(l);f.add(p);

Menús Para utilizar menús, se emplea la clase MenuBar (para definir la barra de menú),Menu (para definir cada menú), y MenuItem (para cada opción en un menú). Unmenú podrá contener a su vez submenús (objetos de tipo Menu). También está laclase CheckboxMenuItem para definir opciones de menú que son casillas que semarcan o desmarcan.

MenuBar mb = new MenuBar();Menu m1 = new Menu ("Menu 1");Menu m11 = new Menu ("Menu 1.1");Menu m2 = new Menu ("Menu 2");MenuItem mi1 = new MenuItem ("Item 1.1");MenuItem mi11=new MenuItem ("Item 1.1.1");CheckboxMenuItem mi2 =

new CheckboxMenuItem("Item 2.1");mb.add(m1);mb.add(m2);m1.add(mi1);m1.add(m11);m11.add(mi11);m2.add(mi2);

Mediante el método setMenuBar(...) de Frame podremos añadir un menú

Programación en Lenguaje Java

55Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 56: Programación en Lenguaje Java

a una ventana:

Frame f = new Frame();f.setMenuBar(mb);

7.1.2. Swing

Anteriormente se ha visto una descripción de los controles AWT para construiraplicaciones visuales. En cuanto a estructura, no hay mucha diferencia entre los controlesproporcionados por AWT y los proporcionados por Swing: éstos se llaman, en general,igual que aquéllos, salvo que tienen una "J" delante; así, por ejemplo, la clase Button deAWT pasa a llamarse JButton en Swing , y en general la estructura del paquetejavax.swing es muy similar a la que tiene java.awt (aunque hay más controles, y seofrecen más funcionalidades en Swing).

Pero yendo más allá de la estructura, existen importantes diferencias entre loscomponentes Swing y los componentes AWT:

• Los componentes Swing están escritos sin emplear código nativo, con lo que ofrecenmás versatilidad multiplataforma (podemos dar a nuestra aplicación un aspecto que nodependa de la plataforma en que la estemos ejecutando).

• Los componentes Swing ofrecen más capacidades que los correspondientes AWT: losbotones pueden mostrar imágenes, hay más facilidades para modificar la apariencia delos componentes, existen más componentes diferentes que en AWT, etc.

• Al mezclar componentes Swing y componentes AWT en una aplicación, se debe tenercuidado de emplear contenedores AWT con elementos Swing, puesto que loscontenedores pueden solapar a los elementos (se colocan encima).

Controles de Swing

Los controles en Swing tienen en general el mismo nombre que los de AWT, con una "J"delante. Así, el botón en Swing es JButton, la etiqueta es JLabel , etc. Hay algunasdiferencias, como por ejemplo JComboBox (el equivalente a Choice de AWT), ycontroles nuevos. Vemos aquí un listado de algunos controles:

JComponent La clase padre para los componentes Swing es JComponent, paralela alComponent de AWT.

Botones Se tienen botones normales (JButton), de verificación (JCheckBox), de radio(JRadioButton), etc, similares a los Button, Checkbox de AWT, pero con másposibilidades (se pueden añadir imágenes, etc).

Programación en Lenguaje Java

56Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 57: Programación en Lenguaje Java

Etiquetas Las etiquetas son JLabel, paralelas a las Label de AWT pero con máscaracterísticas propias (iconos, etc).

Cuadros de texto Las clases JTextField y JTextArea representan los cuadros de texto en Swing,de forma parecida a los TextField y TextArea de AWT.

Listas Las clases JComboBox y JList se emplean para lo mismo que Choice y List enAWT, respectivamente.

Diálogos, ventanas ycontenedores

Las clases JDialog (y sus derivadas) y JFrame se emplean para definir diálogosy ventanas. Se tienen algunos cuadros de diálogo específicos, para elegir ficheros(JFileChooser ), para elegir colores (JColorChooser), etc.

Internamente a estos diálogos y ventanas, tenemos el contenedor JPanel,equivalente al Panel de AWT, para agrupar componentes.

Menús Con JMenu, JMenuBar, JMenuItem, se construyen los menús que seconstruían en AWT con Menu, MenuBar y MenuItem.

7.2. Gestores de disposición

Para colocar los controles Java en los contenedores se hace uso de un determinado gestorde disposición. Dicho gestor indica cómo se colocarán los controles en el contenedor,

Programación en Lenguaje Java

57Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 58: Programación en Lenguaje Java

siguiendo una determinada distribución. Para establecer qué gestor queremos, se empleael método setLayout(...) del contenedor. Por ejemplo:

Panel panel = new Panel();panel.setLayout(new BorderLayout());

Después, utilizamos el método add(...) del contenedor, para añadir controles. Dichométodo tendrá unos parámetros u otros en función del tipo de gestor de disposición que setenga, aunque, en cualquier caso, siempre se le pasa el control que queremos añadir alcontenedor (más aparte otros parámetros adicionales, si se requieren).

7.2.1. Tipos de gestores

Veremos ahora los gestores más importantes:

BorderLayout

(gestor por defecto paracontenedores tipoWindow)

Divide el área del contenedor en 5 zonas: Norte (NORTH), Sur (SOUTH), Este(EAST), Oeste (WEST) y Centro (CENTER), de forma que al colocar loscomponentes deberemos indicar en el método add(...) en qué zona colocarlo:

panel.setLayout(new BorderLayout());Button btn = new Button("Pulsame");panel.add(btn, BorderLayout.SOUTH);

Al colocar un componente en una zona, se colocará sobre el que existieraanteriormente en dicha zona (lo tapa).

FlowLayout

(gestor por defecto paracontenedores de tipoPanel)

Con este gestor, se colocan los componentes en fila, uno detrás de otro, con eltamaño preferido (preferredSize ) que se les haya dado. Si no caben en una fila,se utilizan varias.

panel.setLayout(new FlowLayout());panel.add(new Button("Pulsame"));

GridLayout Este gestor sitúa los componentes en forma de tabla, dividiendo el espacio delcontenedor en celdas del mismo tamaño, de forma que el componente ocupa todoel tamaño de la celda. Se indica en el constructor el número de filas y decolumnas. Luego, al colocarlo, va por orden (rellenando filas de izquierda aderecha).

panel.setLayout(new GridLayout(2,2));panel.add(new Button("Pulsame"));panel.add(new Label("Etiqueta"));

Sin gestor Si especificamos un gestor null, podremos colocar a mano los componentes en elcontenedor, con métodos como setBounds(...), o setLocation(...):

Programación en Lenguaje Java

58Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 59: Programación en Lenguaje Java

panel.setLayout(null);Button btn = new Button ("Pulsame");btn.setBounds(0, 0, 100, 30);panel.add(btn);

Con setBounds indicamos las coordenadas X,Y de la esquina superior izquierdadel control (2 primeros parámetros), y la anchura y altura del mismo (2 últimosparámetros). Observemos que de esta forma controlamos dónde va a irexactamente el control, y con qué tamaño. El inconveniente es que si seredimensiona la ventana, el control sigue en el mismo lugar que indicamos alprincipio.

7.2.2. Uso de los gestores

Los gestores de disposición que hemos visto son iguales para AWT y para Swing:

• Para establecer el gestor de disposición en un contenedor AWT o Swing, se utilizael método setLayout(...) del contenedor:

Panel panel = new Panel();panel.setLayout(new BorderLayout());

Frame f = new Frame();f.setLayout(new GridLayout(3, 2));

JPanel panel = new JPanel();panel.setLayout(new BorderLayout());

JFrame f = new JFrame();f.setLayout(new GridLayout(3, 2));

• Para añadir controles a un contenedor AWT o Swing, utilizamos el método add(...)correspondiente del contenedor (dependiendo del tipo de gestor que tengamos,emplearemos un método add(...) u otro):

Button b = new Button("Hola");Panel panel = new Panel();panel.add(b);Frame f = new Frame();f.add(panel, BorderLayout.CENTER);

JButton b = new JButton("Hola");JPanel panel = new JPanel();panel.add(b);JFrame f = new JFrame();f.add(panel, BorderLayout.CENTER);

Ejemplo: Vemos el aspecto de algunos componentes de AWT, y el uso de gestores dedisposición en este ejemplo, donde se añaden una serie de controles (etiquetas, botones,checkboxes, etc) en una ventana. La aplicación no hace nada, simplemente muestra loscontroles:

Programación en Lenguaje Java

59Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 60: Programación en Lenguaje Java

import java.awt.*;import java.awt.event.*;

/*** Este es un ejemplo donde se muestran algunos de los controles* de AWT*/

public class EjemploAWT extends Frame{

/*** Constructor*/

public EjemploAWT(){

setSize(400, 300);setLayout(new GridLayout(4, 2));

// Menus

MenuBar mb = new MenuBar();Menu m1 = new Menu ("Menu 1");Menu m11 = new Menu ("Menu 1.1");Menu m2 = new Menu ("Menu 2");MenuItem mi1 = new MenuItem ("Item 1.1");MenuItem mi11 = new MenuItem ("Item 1.1.1");CheckboxMenuItem mi2 = new CheckboxMenuItem("Item

2.1");mb.add(m1);mb.add(m2);m1.add(mi1);m1.add(m11);m11.add(mi11);m2.add(mi2);setMenuBar(mb);

// Componentes

Label lblBoton = new Label("Boton:");Label lblCheck = new Label("Checkbox:");

Programación en Lenguaje Java

60Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 61: Programación en Lenguaje Java

Label lblChoice = new Label("Choice:");Label lblText = new Label("TextField:");

Button btn = new Button ("Boton");Checkbox chk = new Checkbox ("Checkbox", true);Choice ch = new Choice();ch.addItem("Opcion 1");ch.addItem("Opcion 2");TextField txt = new TextField("Texto");

add(lblBoton);add(btn);add(lblCheck);add(chk);add(lblChoice);add(ch);add(lblText);add(txt);

// Evento para cerrar la ventanaaddWindowListener(new WindowAdapter(){

public void windowClosing (WindowEvent e){

System.exit(0);}

});}

/*** Main*/

public static void main (String[] args){

EjemploAWT e = new EjemploAWT();e.show();

}}

El aspecto de la aplicación utilizando componentes Swing es el que aparece acontinuación:

Programación en Lenguaje Java

61Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 62: Programación en Lenguaje Java

import java.awt.*;import javax.swing.*;import java.awt.event.*;

/*** Este es un ejemplo donde se muestran algunos de los controles* de Swing*/

public class EjemploSwing extends JFrame{

/*** Constructor*/

public EjemploSwing(){

setSize(400, 300);setLayout(new GridLayout(4, 2));

// Menus

JMenuBar mb = new JMenuBar();JMenu m1 = new JMenu ("Menu 1");JMenu m11 = new JMenu ("Menu 1.1");JMenu m2 = new JMenu ("Menu 2");JMenuItem mi1 = new JMenuItem ("Item 1.1");JMenuItem mi11 = new JMenuItem ("Item 1.1.1");JCheckBoxMenuItem mi2 = new JCheckBoxMenuItem("Item

2.1");mb.add(m1);mb.add(m2);m1.add(mi1);m1.add(m11);m11.add(mi11);m2.add(mi2);setJMenuBar(mb);

// Componentes

JLabel lblBoton = new JLabel("Boton:");JLabel lblCheck = new JLabel("Checkbox:");JLabel lblChoice = new JLabel("Choice:");JLabel lblText = new JLabel("TextField:");

JButton btn = new JButton ("Boton");JCheckBox chk = new JCheckBox ("Checkbox", true);JComboBox ch = new JComboBox();ch.addItem("Opcion 1");ch.addItem("Opcion 2");JTextField txt = new JTextField("Texto");

add(lblBoton);add(btn);add(lblCheck);add(chk);add(lblChoice);add(ch);add(lblText);add(txt);

Programación en Lenguaje Java

62Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 63: Programación en Lenguaje Java

// Evento para cerrar la ventanaaddWindowListener(new WindowAdapter(){

public void windowClosing (WindowEvent e){

System.exit(0);}

});}

/*** Main*/

public static void main (String[] args){

EjemploSwing e = new EjemploSwing();e.show();

}}

7.3. Modelo de eventos en Java

Entendemos por evento una acción o cambio en una aplicación que permite que dichaaplicación produzca una respuesta. El modelo de eventos de Java se descompone en dosgrupos de elementos: las fuentes y los oyentes de eventos. Las fuentes son los elementosque generan los eventos (un botón, un cuadro de texto, etc), mientras que los oyentes sonelementos que están a la espera de que se produzca(n) determinado(s) tipo(s) de evento(s)para emitir determinada(s) respuesta(s).

7.3.1. Oyentes para manejar eventos

Para poder gestionar eventos, necesitamos definir el manejador de eventoscorrespondiente, un elemento que actúe de oyente sobre las fuentes de eventos quenecesitemos considerar. Cada tipo de evento tiene asignada una interfaz, de modo quepara poder gestionar dicho evento, el manejador deberá implementar la interfaz asociada.Los oyentes más comunes son:

ActionListener Para eventos de acción (pulsar un Button, por ejemplo)

ItemListener Cuando un elemento (Checkbox, Choice , etc), cambia su estado

KeyListener Indican una acción sobre el teclado: pulsar una tecla, soltarla, etc.

MouseListener Indican una acción con el ratón que no implique movimiento del mismo:hacer click, presionar un botón, soltarlo, entrar / salir...

MouseMotionListener Indican una acción con el ratón relacionada con su movimiento:moverlo por una zona determinada, o arrastrar el ratón.

Programación en Lenguaje Java

63Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 64: Programación en Lenguaje Java

WindowListener Indican el estado de una ventana

Cada uno de estos tipos de evento puede ser producido por diferentes fuentes.

7.3.2. Modos de definir un oyente

Supongamos que queremos realizar una acción determinada al pulsar un botón. En estecaso, tenemos que asociar un ActionListener a un objeto Button. Veremos cómo podemoshacerlo:

1. Que la propia clase que usa el control implemente el oyente

class MiClase implements ActionListener{

public MiClase(){

...Button btn = new Button("Boton");btn.addActionListener(this);

...}

public void actionPerformed(ActionEvent e){

// Aqui va el codigo de la accion}

}

2. Definir otra clase aparte que implemente el oyente

class MiClase{

public MiClase(){

...Button btn = new Button("Boton");btn.addActionListener(new MiOyente());...

}}

class MiOyente implements ActionListener{

public void actionPerformed(ActionEvent e){

// Aqui va el codigo de la accion}

}

3. Definir una instancia interna del oyente

class MiClase{

public MiClase()

Programación en Lenguaje Java

64Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 65: Programación en Lenguaje Java

{...Button btn = new Button("Boton");btn.addActionListener(new ActionListener(){

public void actionPerformed(ActionEvent e){

// Aqui va el codigo de la accion}

});...

}}

7.3.3. Uso de los "adapters"

Algunos de los oyentes disponibles (como por ejemplo MouseListener) tienen variosmétodos que hay que implementar si queremos definir el oyente. Este trabajo puede serbastante pesado e innecesario si sólo queremos usar algunos métodos.

Una solución a esto es el uso de los adapters. Asociado a cada oyente con más de unmétodo hay una clase ...Adapter (para MouseListener está MouseAdapter , paraWindowListener está WindowAdapter, etc). Estas clases implementan las interfaces conlas que se asocian, de forma que se tienen los métodos implementados por defecto, y sólotendremos que sobreescribir los que queramos modificar.

Veamos la diferencia con el caso de MouseListener, suponiendo que queremos asociar unevento de ratón a un Panel para que haga algo al hacer click sobre él.

1. Mediante Listener:

class MiClase{

public MiClase(){

...Panel panel = new Panel();panel.addMouseListener(new MouseListener(){

public void mouseClicked(MouseEvent e){

// Aqui va el codigo de la accion}

public void mouseEntered(MouseEvent e){

// ... No se necesita}

public void mouseExited(MouseEvent e){

// ... No se necesita}

Programación en Lenguaje Java

65Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 66: Programación en Lenguaje Java

public void mousePressed(MouseEvent e){

// ... No se necesita}

public void mouseReleased(MouseEvent e){

// ... No se necesita}

});...

}}

Vemos que hay que definir todos los métodos, aunque muchos queden vacíos porque nose necesitan.

2. Mediante Adapter:

class MiClase{

public MiClase(){

...Panel panel = new Panel();panel.addMouseListener(new MouseAdapter(){

public void mouseClicked(MouseEvent e){

// Aqui va el codigo de la accion}

});...

}}

Vemos que aquí sólo se añaden los métodos necesarios, el resto ya están implementadosen MouseAdapter (o en el adapter que corresponda), y no hace falta ponerlos.

Ejemplo: Vemos el uso de oyentes en este ejemplo, donde se modifica el valor de uncontador en un cuadro de texto a través de 2 eventos diferentes: pulsando un botón que loincrementa en 1 cada vez, o eligiendo un valor predeterminado de un desplegable.Además, se tienen otros eventos implementados, como cambiar el texto de una etiquetacuando se pasa el ratón por encima, o el de cerrar la ventana:

Programación en Lenguaje Java

66Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 67: Programación en Lenguaje Java

import java.awt.*;import java.awt.event.*;

/*** Este es un ejemplo donde se muestran algunos de los controles* de AWT, y se modifica un contador bien pulsando un boton,* bien modificando el valor a mano, bien mediante un desplegable*/

public class EjemploAWT2 extends Frame{

// Contador a modificarTextField txtCont;

/*** Constructor*/

public EjemploAWT2(){

setSize(500, 200);

// ****** Panel con el contador ******

Panel panelCont = new Panel();final Label lblCont = new Label("Contador:");// Cambiar el texto de la etiqueta al pasar por

encimalblCont.addMouseListener(new MouseAdapter(){

public void mouseEntered(MouseEvent e){

lblCont.setText("En etiqueta!");}

public void mouseExited(MouseEvent e){

lblCont.setText("Contador:");}

});txtCont = new TextField("0");panelCont.add(lblCont);panelCont.add(txtCont);

// ****** Panel para el boton ******

Panel panelBoton = new Panel();Label lblBoton = new Label("Incrementar

Contador:");Button btn = new Button("Incrementar");// Incrementar el valor del contador al pulsarlobtn.addActionListener(new ActionListener(){

public void actionPerformed(ActionEvent e){

txtCont.setText("" +(Integer.parseInt(txtCont.getText()) + 1));

}});panelBoton.add(lblBoton);panelBoton.add(btn);

Programación en Lenguaje Java

67Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 68: Programación en Lenguaje Java

// ****** Panel para el desplegable ******

Panel panelChoice = new Panel();Label lblChoice = new Label("Establecer

Contador:");final Choice ch = new Choice();for (int i = 0; i < 10; i++)

ch.addItem("" + i);// Establecer el contador al elemento seleccionadoch.addItemListener(new ItemListener(){

public void itemStateChanged(ItemEvent e){

txtCont.setText(ch.getSelectedItem());}

});panelChoice.add(lblChoice);panelChoice.add(ch);

// Colocamos los panelesadd(panelCont, "South");add(panelBoton, "West");add(panelChoice, "East");

// Evento para cerrar la ventanaaddWindowListener(new WindowAdapter(){

public void windowClosing (WindowEvent e){

System.exit(0);}

});}

/*** Main*/

public static void main (String[] args){

EjemploAWT2 e = new EjemploAWT2();e.show();

}}

7.3.4. Diferencias entre AWT y Swing

El modelo de eventos explicado funciona exactamente igual para AWT que para Swing.Las diferencias en el modelo de eventos están en el hecho de que Swing, aparte de losoyentes vistos aquí, define otros subtipos de oyentes más específicos para controles suyospropios.

7.3.5. Hilos y Swing

Programación en Lenguaje Java

68Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 69: Programación en Lenguaje Java

En las aplicaciones Swing existe un hilo de procesamiento de eventos, que es elencargado de invocar todos los métodos de nuestros listeners cuando se produzca elevento correspondiente. Este hilo además se encargará de procesar eventos como elrepintado de la ventana. Si en los métodos de nuestros listeners realizásemos operacionesde larga duración, estaríamos bloqueando el hilo de eventos, por lo que la aplicación nopodría procesar otros eventos ni podría repintar su contenido. Es decir, veríamos que laaplicación no responde. Para evitar este problema, cualquier acción de larga duración quese invoque desde estos métodos deberá realizarse en un hilo independiente.

Sin embargo, se debe tener cuidado a la hora de utilizar hilos en programas Swing. Estoes debido a que, en general, una vez que se crea un componente, sólo se puede acceder aél (para obtener datos o modificarlos) desde el código de los eventos que tenga asociados(lo que se llama event-dispatching thread). En caso contrario, podrían provocarseinconsistencias que ocasionaran que el programa no funcionara correctamente.

Por esta razón, cuando necesitamos acceder a un componente desde fuera del código delevento, podemos utilizar los métodos invokeLater() o invokeAndWait() de la clasejavax.swing.SwingUtilities. En ambos casos, se pasa como parámetro el objeto hilo quequeremos ejecutar:

Thread t = new Thread(...);...SwingUtilities.invokeLater(t);SwingUtilities.invokeAndWait(t);

La diferencia entre uno y otro método es que invokeLater() devuelve el controlinmediatamente al programa principal para que siga ejecutando (y lanzará el hilo cuandopueda), e invokeAndWait() detiene el programa principal hasta que se pueda lanzar elhilo. Se recomienda en la medida de lo posible utilizar el primero de ellos. El métodorun() del hilo deberá tener todo el acceso a los componentes que se requiera.

7.4. Apariencia

La apariencia de un programa Java no se limita al sistema operativo en el que estemos,sino que podemos hacer que nuestro programa tenga apariencia Windows, Linux, ycualquier otra apariencia de la que podamos disponer.

Para cambiar la apariencia tenemos la clase UIManager, y su métodosetLookAndFeel(...). Por ejemplo, si queremos hacer que nuestro programa tenga laapariencia propia de Java, haremos algo como:

public static void main(String[] args){

...try{

UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());} catch (Exception ex) { ... }

}

Programación en Lenguaje Java

69Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 70: Programación en Lenguaje Java

La llamada a este método puede provocar una excepción, si la clase de look and feel quequeremos cargar no se encuentra.

Si disponemos de algún tipo de apariencia que nos hayamos descargado (normalmente sedescargan en ficheros JAR que hay que incluir en el CLASSPATH), la añadimospasándole como parámetro el nombre completo de la apariencia. Por ejemplo, así secargaría una apariencia de Linux GTK, o una de Windows, respectivamente:

UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel");UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");

7.4.1. Uso de bordes

Podemos definir qué bordes queremos que tenga cualquier subtipo de JComponent,aunque en general, para controles diferentes a un JPanel o JLabel Sun advierte que puedeque no se representen bien dichos bordes. En esos casos, se puede colocar el componentedentro de un JPanel, y asignar el borde al JPanel.

Para colocar un borde en un componente utilizamos el método setBorder(...) delcomponente. Dicho método acepta un parámetro de tipo Border, que es una interfaz. Paraindicar qué tipo de borde queremos como parámetro, podemos utilizar la clasejavax.swing.BorderFactory, y crear uno de los subtipos de bordes que permiten susmétodos. Aquí mostramos gráficamente algunos de ellos:

Programación en Lenguaje Java

70Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 71: Programación en Lenguaje Java

Por ejemplo, si queremos añadir un borde de línea rojo a un panel, pondríamos:

JPanel p = new JPanel();p.setBorder(BorderFactory.createLineBorder(Color.red));

Además de la gran cantidad de bordes disponibles, también podemos definirnos nuestrospropios bordes, creando una subclase de AbstractBorder, y definiendo los métodosnecesarios.

7.4.2. Uso de iconos

Algunos controles Swing como JLabel, JButton o JTabbedPane permiten enriquecer suapariencia añadiendo iconos en ellos, utilizando la interfaz Icon. Para utilizarla, seproporciona una implementación de la misma en la clase ImageIcon, que permite cargariconos desde imágenes JPG, GIF o PNG. Normalmente se utiliza esta clase para creariconos.

Por ejemplo, si queremos crear una etiqueta con un icono de un fichero GIF determinado,creamos la etiqueta con un constructor donde indicamos el texto de la etiqueta, su icono yla alineación horizontal:

JLabel lbl = new JLabel("Nombre", new ImageIcon("icono.gif"),SwingConstants.CENTER);

Programación en Lenguaje Java

71Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 72: Programación en Lenguaje Java

8. Ejercicios de introducción a AWT y Swing

8.1. Calculadora sencilla en AWT

Vamos a realizar una aplicación gráfica que simule una calculadora sencilla, en la claseCalculadora de la plantilla. La aplicación tendrá este aspecto:

Apariencia de la calculadora

En el primer cuadro de texto ponemos el primer operando, luego elegimos la operación arealizar (suma '+', resta '-', o multiplicación '*'), y en el segundo cuadro de texto ponemosel segundo operando. Pulsando en Calcular mostraremos el resultado en el tercer cuadrode texto.

Realizaremos la aplicación en AWT. El Frame tiene un tamaño de unos 300 de ancho por150 de alto (método setSize(...) que hay al principio del constructor). Podéis emplear unGridLayout de 4 filas y 2 columnas, e ir colocando los controles en él (3 etiquetas, 3cuadros de texto, 1 desplegable y 1 botón, en el orden que aparece en la figura).

En cuanto al evento del botón, leerá los valores enteros que haya en "Primer Operando" y"Segundo Operando" y los procesará con la operación seleccionada en "Operador". Paraleer valores enteros podéis utilizar algo como:

int op1, op2;op1 = Integer.parseInt(txtOp1.getText());op2 = Integer.parseInt(txtOp2.getText());

if (((String)(operadores.getSelectedItem())).equals("+"))txtRes.setText("" + (op1 + op2));

else ...

donde txtOp1 y txtOp2 serían los cuadros de texto del primer y segundo operandos,operadores sería el desplegable de operadores, y txtRes sería el cuadro de texto delresultado. Lo que hacemos es leer los dos enteros de los operandos, y según la operaciónseleccionada, mostrar el resultado correspondiente.

8.2. Calculadora sencilla en Swing

Cread una clase nueva llamada JCalculadora (en el mismo paquete que la clase

Programación en Lenguaje Java

72Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 73: Programación en Lenguaje Java

Calculadora), y haced en ella una calculadora similar a la del ejercicio 1, pero utilizandoSwing. Para convertir el programa a Swing tened en cuenta que hay que sustituir cadacontrol de AWT (Button, Label, TextField, Choice, Frame, etc) por su correspondiente deSwing (JButton, JLabel, JTextField, JComboBox, JFrame, etc)

8.3. Formatos de texto (*)

Construir una clase llamada JFormatos (en el mismo paquete que las clases de losanteriores ejercicios), que herede de JFrame, y que contenga un área de texto (JTextArea)y un menú llamado Formato, con las siguientes opciones:

• "Color Negro": será un JMenuItem que pondrá el color del texto del área de texto ennegro.

• "Color Rojo": otro JMenuItem que pondrá el color del texto del área de texto en rojo.

• "Negrita": será un JCheckBoxMenuItem que cuando esté marcado pondrá el texto ennegrita, y cuando no quitará la negrita del texto

• "Cursiva": similar al anterior, pero para establecer o quitar la cursiva.

Se recomienda que la clase implemente la interfaz ActionListener:

public class JFormatos extends JFrame implements ActionListener{

...

Después, en cada JMenuItem y JCheckBoxMenuItem, se añade como ActionListener laclase actual:

JMenuItem mi = new JMenuItem("Color Negro");mi.addActionListener(this);...JCheckBoxMenuItem cmi = new JCheckBoxMenuItem("Negrita");cmi.addActionListener(this);

Y en el actionPerformed que haya que implementar se hará una acción u otra según laacción seleccionada en el menú:

public void actionPerformed(ActionEvent e){

if (e.getActionCommand().equals("Color Negro")){

...} else if (e.getActionCommand.equals("Negrita")) {

...} else if...

}

Para cambiar el color del texto del cuadro se utiliza el método setForeground, pasándoleel color a utilizar, con las constantes de la clase Color, o definiendo el color a manoutilizando los constructores de Color.:

Programación en Lenguaje Java

73Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 74: Programación en Lenguaje Java

txt.setForeground(Color.black);

Para cambiar el estilo de la fuente (negrita / cursiva), se establece una nueva fuente parael cuadro de texto, con el método setFont, donde se le pasa un objeto de tipo Font,construido con tres parámetros:

txt.setFont(new Font(tipo, estilo, tamaño));txt.setFont(new Font("Arial", estilo, 16));

donde tipo será "Arial" en nuestro caso, y tamaño será 16. En cuanto al estilo será unacombinación de negrita y cursiva, en el caso de que estén seleccionadas las opcionescorrespondientes del menú. Si estuviesen seleccionadas las dos, por ejemplo, pondríamosel texto en negrita y cursiva con:

txt.setFont(new Font("Arial", Font.BOLD | Font.ITALIC, 16));

Programación en Lenguaje Java

74Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 75: Programación en Lenguaje Java

9. Consultas a una BD con JDBC

9.1. Introducción a JDBC

En la mayoría de las aplicaciones que nos vamos a encontrar, aparecerá una base de datoscomo fuente de información. JDBC nos va a permitir acceder a bases de datos (BD) desdeJava. Con JDBC no es necesario escribir distintos programas para distintas BD, sino queun único programa sirve para acceder a BD de distinta naturaleza. Incluso, podemosacceder a más de una BD de distinta fuente (Oracle, Access, MySql, etc.) en la mismaaplicación. Podemos pensar en JDBC como el puente entre una base de datos y nuestroprograma Java. Un ejemplo sencillo puede ser un applet que muestra dinámicamenteinformación contenida en una base de datos. El applet utilizará JDBC para obtener dichosdatos.

El esquema a seguir en un programa que use JDBC es el siguiente:

Esquema general de conexión con una base de datos

Un programa Java que utilice JDBC primero deberá establecer una conexión con elSGBD. Para realizar dicha conexión haremos uso de un driver específico para cadaSGBD que estemos utilizando. Una vez establecida la conexión ya podemos interrogar laBD con cualquier comando SQL (select, update, create, etc.). El resultado de un comandoselect es un objeto de la clase ResultSet, que contiene los datos que devuelve la consulta.Disponemos de métodos en ResultSet para manejar los datos devueltos. Tambiénpodemos realizar cualquier operación en SQL (creación de tablas, gestión de usuarios,etc.).

Conexión a través del API y un driver de JDBC

Programación en Lenguaje Java

75Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 76: Programación en Lenguaje Java

Para realizar estas operaciones necesitaremos contar con un SGBD (sistema gestor debases de datos) además de un driver específico para poder acceder a este SGBD. Vamos autilizar dos SGBD: MySQL (disponible para Windows y Linux, de libre distribución) yPostGres (sólo para Linux, también de libre distribución).

9.1.1. Drivers de acceso

Los drivers para poder acceder a cada SGBD no forman parte de la distribución de Javapor lo que deberemos obtenerlos por separado. ¿Por qué hacer uso de un driver?. Elprincipal problema que se nos puede plantear es que cada SGBD dispone de su propioAPI (la mayoría propietario), por lo que un cambio en el SGBD implica una modificaciónde nuestro código. Si colocamos una capa intermedia, podemos abstraer la conectividad,de tal forma que nosotros utilizamos un objeto para la conexión, y el driver se encarga detraducir la llamada al API. El driver lo suelen distribuir las propias empresas que fabricanel SGBD.

9.1.1.1. Tipos de drivers

Existe un estándar establecido que divide los drivers en cuatro grupos:

• Tipo 1: Puente JDBC-ODBC. ODBC (Open Database Connectivity) fue creado paraproporcionar una conexión a bases de datos en Microsoft Windows. ODBC permiteacceso a bases de datos desde diferentes lenguajes de programación, tales como C yCobol. El puente JDBC-ODBC permite enlazar Java con cualquier base de datosdisponible en ODBC. No se aconseja el uso de este tipo de driver cuando tengamosque acceder a bases de datos de alto rendimiento, pues las funcionalidades estánlimitadas a las que marca ODBC. Cada cliente debe tener instalado el driver. J2SEincluye este driver en su versión Windows y Solaris.

Configuración de un driver de tipo 1• Tipo 2: Parte Java, parte driver nativo. Es una combinación de implementación

Java y API nativo para el acceso a la base de datos. Este tipo de driver es más rápidoque el anterior, pues no se realiza el paso por la capa ODBC. Las llamadas JDBC setraducen en llamadas específicas del API de la base de datos. Cada cliente debe tenerinstalado el driver. Tiene menor rendimiento que los dos siguientes y no se puedenusar en Internet, ya que necesita el API de forma local.

Programación en Lenguaje Java

76Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 77: Programación en Lenguaje Java

Configuración de un driver de tipo 2• Tipo 3: Servidor intermediario de acceso a base de datos. Este tipo de driver

proporciona una abstracción de la conexión. El cliente se conecta a los SGBDmediante un componente servidor intermedio, que actúa como una puerta paramúltiples servidores. La ventaja de este tipo de driver es el nivel de abstracción. Elservidor de aplicaciones WebLogic incorpora este tipo de driver.

Configuración de un driver de tipo 3• Tipo 4: Drivers Java. Este es el más directo. La llamada JDBC se traduce

directamente en una llamada de red a la base de datos, sin intermediarios.Proporcionan mejor rendimiento. La mayoría de SGBD proporcionan drivers de estetipo.

Configuración de un driver de tipo 4

9.1.1.2. Instalación de drivers

La distribución de JDBC incorpora los drivers para el puente JDBC-ODBC que nospermite acceder a cualquier BD que se gestione con ODBC. Para MySQL, deberemosdescargar e instalar el SGBD y el driver, que puede ser obtenido en la direcciónhttp://dev.mysql.com/doc/mysql/en/Java_Connector.html. El driver para PostGres seobtiene en http://jdbc.postgresql.org

Para instalar el driver lo único que deberemos hacer es incluir el fichero JAR que locontiene en el CLASSPATH. Por ejemplo, para MySQL:

Programación en Lenguaje Java

77Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 78: Programación en Lenguaje Java

export CLASSPATH=$CLASSPATH:/directorio-donde-este/mysql-connector-java-3.0.15-ga-bin.jar

Con el driver instalado, podremos cargarlo desde nuestra aplicación simplementecargando dinámicamente la clase correspondiente al driver:

Class.forName("com.mysql.jdbc.Driver");

El driver JDBC-ODBC se carga como se muestra a continuación:

Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Y de forma similar para PostGres:

Class.forName("org.postgresql.Driver");

La carga del driver se debería hacer siempre antes de conectar con la BD.

Como hemos visto anteriormente, pueden existir distintos tipos de drivers para la mismabase de datos. Por ejemplo, a una BD en MySQL podemos acceder mediante ODBC omediante su propio driver. Podríamos pensar que la solución más sencilla sería utilizarODBC para todos las conexiones a SGBD. Sin embargo, dependiendo de la complejidadde la aplicación a desarrollar esto nos podría dar problemas. Determinados SGBDpermiten realizar operaciones (transacciones, mejora de rendimiento, escabilidad, etc.)que se ven mermadas al realizar su conexión a través del driver ODBC. Por ello espreferible hacer uso de driver específicos para el SGBD en cuestión.

El ejemplo más claro de problemas en el uso de drivers es con los Applets. Cuandoutilicemos acceso a bases de datos mediante JDBC desde un Applet, deberemos tener encuenta que el Applet se ejecuta en la máquina del cliente, por lo que si la BD está alojadaen nuestro servidor tendrá que establecer una conexión remota. Aquí encontramos elproblema de que si el Applet es visible desde Internet, es muy posible que el puerto en elque escucha el servidor de base de datos puede estar cortado por algún firewall, por loque el acceso desde el exterior no sería posible.

El uso del puente JDBC-ODBC tampoco es recomendable en Applets, ya que requiereque cada cliente tenga configurada la fuente de datos ODBC adecuada en su máquina.Esto podemos controlarlo en el caso de una intranet, pero en el caso de Internet serámejor utilizar otros métodos para la conexión.

En cuanto a las excepciones, debemos capturar la excepción SQLException en casi todaslas operaciones en las que se vea involucrado algún objeto JDBC.

9.1.2. Conexión a la BD

Una vez cargado el driver apropiado para nuestro SGBD deberemos establecer laconexión con la BD. Para ello utilizaremos el siguiente método:

Connection con = DriverManager.getConnection(url);

Programación en Lenguaje Java

78Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 79: Programación en Lenguaje Java

Connection con = DriverManager.getConnection(url, login, password);

La conexión a la BD está encapsulada en un objeto Connection. Para su creacióndebemos proporcionar la url de la BD y, si la BD está protegida con contraseña, el login ypassword para acceder a ella. El formato de la url variará según el driver que utilicemos.Sin embargo, todas las url tendrán la siguiente forma general:jdbc:<subprotocolo>:<nombre>, con subprotocolo indicando el tipo de SGBD y connombre indicando el nombre de la BD y aportando información adicional para laconexión.

Para conectar a una fuente ODBC de nombre bd, por ejemplo, utilizaremos la siguienteURL:

Connection con = DriverManager.getConnection("jdbc:odbc:bd");

En el caso de MySQL, si queremos conectarnos a una BD de nombre bd alojada en lamáquina local (localhost) y con usuario miguel y contraseña m++24, lo haremos de lasiguiente forma:

Connection con =DriverManager.getConnection("jdbc:mysql://localhost/bd",

"miguel", "m++24");

En el caso de PostGres (notar que hemos indicado un puerto de conexión, el 5432):

Connection con = DriverManager.getConnection("jdbc:postgresql://localhost:5432/bd", "miguel", "m++24");

Podemos depurar la conexión y determinar qué llamadas está realizando JDBC. Para elloharemos uso de un par de métodos que incorpora DriverManager. En el siguienteejemplo se indica que las operaciones que realice JDBC se mostrarán por la salidaestándar:

DriverManager.setLogWriter(new PrintWriter(System.out, true));

Una vez realizada esta llamada también podemos mostrar mensajes usando:

DriverManager.println("Esto es un mensaje");

9.2. Consulta a una base de datos con JDBC

9.2.1. Creación y ejecución de sentencias SQL

Una vez obtenida la conexión a la BD, podemos utilizarla para realizar consultas,inserción y/o borrado de datos de dicha BD. Todas estas operaciones se realizaránmediante lenguaje SQL. La clase Statement es la que permite realizar todas estasoperaciones. La instanciación de esta clase se realiza haciendo uso del siguiente métodoque proporciona el objeto Connection:

Statement stmt = con.createStatement();

Programación en Lenguaje Java

79Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 80: Programación en Lenguaje Java

Podemos dividir las sentencias SQL en dos grupos: las que actualizan la BD y las queúnicamente la consultan. En las siguientes secciones veremos cómo podemos realizarestas dos acciones.

9.2.2. Sentencias de consulta

Para obtener datos almacenados en la BD podemos realizar una consulta SQL (query).Podemos ejecutar la consulta utilizando el objeto Statement, pero ahora haciendo uso delmétodo executeQuery al que le pasaremos una cadena con la consulta SQL. Los datosresultantes nos los devolverá como un objeto ResultSet.

ResultSet result = stmt.executeQuery(query);

La consulta SQL nos devolverá una tabla, que tendrá una serie de campos y un conjuntode registros, cada uno de los cuales consistirá en una tupla de valores correspondientes alos campos de la tabla.

Los campos que tenga la tabla resultante dependerán de la consulta que hagamos, de losdatos que solicitemos que nos devuelva. Por ejemplo, podemos solicitar que una consultanos devuelva los campos expediente y nombre de los alumnos o bien que nos devuelvatodos los campos de la tabla alumnos.

Veamos el funcionamiento de las consultas SQL mediante un ejemplo:

String query = "SELECT * FROM ALUMNOS WHERE sexo = 'M'";ResultSet result = stmt.executeQuery(query);

En esta consulta estamos solicitando todos los registros de la tabla ALUMNOS en los queel sexo sea mujer (M), pidiendo que nos devuelva todos los campos (indicado con *) dedicha tabla. Nos devolverá una tabla como la siguiente:

exp nombre sexo

1286 Amparo M

1287 Manuela M

1288 Lucrecia M

Estos datos nos los devolverá como un objeto ResultSet. A continuación veremos cómopodemos acceder a los valores de este objeto y cómo podemos movernos por los distintosregistros.

El objeto ResultSet dispone de un cursor que estará situado en el registro que podemosconsultar en cada momento. Este cursor en un principio estará situado en una posiciónanterior al primer registro de la tabla. Podemos mover el cursor al siguiente registro conel método next del ResultSet. La llamada a este método nos devolverá true mientraspueda pasar al siguiente registro, y false en el caso de que ya estuviéramos en el últimoregistro de la tabla. Para la consulta de todos los registros obtenidos utilizaremos

Programación en Lenguaje Java

80Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 81: Programación en Lenguaje Java

normalmente un bucle como el siguiente:

while(result.next()) {// Leer registro

}

Ahora necesitamos obtener los datos del registro que marca el cursor, para lo cualpodremos acceder a los campos de dicho registro. Esto lo haremos utilizando los métodosgetXXXX(campo) donde XXXX será el tipo de datos de Java en el que queremos quenos devuelva el valor del campo. Hemos de tener en cuenta que el tipo del campo en latabla debe ser convertible al tipo de datos Java solicitado. Para especificar el campo quequeremos leer podremos utilizar bien su nombre en forma de cadena, o bien su índice quedependerá de la ordenación de los campos que devuelve la consulta. También debemostener en cuenta que no podemos acceder al mismo campo dos veces seguidas en el mismoregistro. Si lo hacemos nos dará una excepción.

Los tipos principales que podemos obtener son los siguientes:

getInt Datos enteros

getDouble Datos reales

getBoolean Campos booleanos (si/no)

getString Campos de texto

getDate Tipo fecha (Devuelve Date)

getTime Tipo hora (Devuelve Time)

Si queremos imprimir todos los datos obtenidos de nuestra tabla ALUMNOS del ejemplopodremos hacer lo siguiente:

int exp;String nombre;String sexo;

while(result.next()){exp = result.getInt("exp");nombre = result.getString("nombre");sexo = result.getString("sexo");System.out.println(exp + "\t" + nombre + "\t" + sexo);

}

Cuando un campo de un registro de una tabla no tiene asignado ningún valor, la consultade ese valor devuelve NULL. Esta situación puede dar problemas al intentar manejar esedato. La clase ResultSet dispone de un método wasNull que llamado después de accedera un registro nos dice si el valor devuelto fue NULL. Esto no sucede así para los datosnuméricos, ya que devuelve el valor 0. Comprobemos qué sucede en el siguiente código:

String sexo;

while(result.next()){exp = result.getInt("exp");

Programación en Lenguaje Java

81Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 82: Programación en Lenguaje Java

nombre = result.getString("nombre");sexo = result.getString("sexo");System.out.println(exp + "\t" + nombre.trim() + "\t" + sexo);

}

La llamada al método trim devolverá una excepción si el objeto nombre es NULL. Porello podemos realizar la siguiente modificación:

String sexo;

while(result.next()){exp = result.getInt("exp");System.out.print(exp + "\t");nombre = result.getString("nombre");if (result.wasNull()) {

System.out.print("Sin nombre asignado");else

System.out.print(nombre.trim());sexo = result.getString("sexo");System.out.println("\t" + sexo);

}

9.3. Restricciones y movimientos en el ResultSet

Cuando realizamos llamadas a BD de gran tamaño el resultado de la consulta puede serdemasiado grande y no deseable en términos de eficiencia y memoria. JDBC permiterestringir el número de filas que se devolverán en el ResultSet. La clase Statementincorpora dos métodos, getMaxRows y setMaxRows, que permiten obtener e imponerdicha restricción. Por defecto, el límite es cero, indicando que no se impone la restricción.Si, por ejemplo, antes de ejecutar la consulta imponemos un límite de 30 usando elmétodo setMaxRows(30), el resultado devuelto sólo contendrá las 30 primeras filas quecumplan con los criterios de la consulta.

Hasta ahora, el manejo de los datos devueltos en una consulta se realizaba con el métodonext de ResultSet. Podemos manejar otros métodos para realizar un movimiento nolineal por el ResultSet. Es lo que se conoce como ResultSet arrastable. Para que esto seaposible debemos utilizar el siguiente método en la creación del Statement:

Statement createStatement (int resultSetType,int resultSetConcurrency)

Los posibles valores que puede tener resultSetType son:ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.TYPE_SCROLL_SENSITIVE. El primer valor es el funcionamiento pordefecto: el ResultSet sólo se mueve hacia adelante. Los dos siguientes permiten que elresultado sea arrastable. Una característica importante en los resultados arrastables es quelos cambios que se produzcan en la BD se reflejan en el resultado, aunque dichos cambiosse hayan producido después de la consulta. Esto dependerá de si el driver y/o la BDsoporta este tipo de comportamiento. En el caso de INSENSITIVE, el resultado no essensible a dichos cambios y en el caso de SENSITIVE, sí. Los métodos que podemos

Programación en Lenguaje Java

82Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 83: Programación en Lenguaje Java

utilizar para movernos por el ResultSet son:

next Pasa a la siguiente fila

previous Ídem fila anterior

last Ídem última fila

first Ídem primera fila

absolute(int fila) Pasa a la fila número fila

relative(int fila) Pasa a la fila número fila desde la actual

getRow Devuelve la número de fila actual

isLast Devuelve si la fila actual es la última

isFirst Ídem la primera

El otro parámetro, resultSetConcurrency, puede ser uno de estos dos valores:ResultSet.CONCUR_READ_ONLY y ResultSet.CONCUR_UPDATABLE. Elprimero es el utilizado por defecto y no permite actualizar el resultado. El segundopermite que los cambios realizados en el ResultSet se actualicen en la base de datos. Siqueremos modificar los datos obtenidos en una consulta y queremos reflejar esos cambiosen la BD debemos crear una sentencia con TYPE_FORWARD_SENSITIVE yCONCUR_UPDATABLE.

9.3.1. Actualización de datos

Para actualizar un campo disponemos de métodos updateXXXX, de la misma forma queteníamos métodos getXXXX. Estos métodos reciben dos parámetros: el primero indica elnombre del campo (o número de orden dentro del ResultSet); el segundo indica el nuevovalor que tomará el campo del registro actual. Para que los cambios tengan efecto en laBD debemos llamar al método updateRow. El siguiente código es un ejemplo demodificación de datos:

rs.updateString("nombre","manolito");rs.updateRow();

Si queremos desechar los cambios producidos en la fila actual (antes de llamar aupdateRow) podemos llamar a cancelRowUpdates. Para borrar la fila actual tenemos elmétodo deleteRow. La llamada a este método deja una fila vacía en el ResultSet. Siintentamos acceder a los datos de esa fila nos dará una excepción. Podemos llamar almétodo rowDeleted el cual devuelve cierto si la fila actual ha sido eliminada (método noimplementado en MySQL).

Debemos tener en cuenta varias restricciones a la hora de actualizar un ResultSet: lasentencia SELECT que ha generado el ResultSet debe:

• Referenciar sólo una tabla.

Programación en Lenguaje Java

83Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 84: Programación en Lenguaje Java

• No contener una claúsula join o group by.• Seleccionar la clave primaria de la tabla.

Existe un registro especial al que no se puede acceder como hemos visto anteriormente,que es el registro de inserción. Este registro se utiliza para insertar nuevos registros en latabla. Para situarnos en él deberemos llamar al método moveToInsertRow. Una vezsituados en él deberemos asignar los datos con los métodos updateXXXX anteriormentedescritos y una vez hecho esto llamar a insertRow para que el registro se inserte en laBD. Podemos volver al registro donde nos encontrábamos antes de movernos al registrode inserción llamando a moveToCurrentRow.

9.4. Sentencias de actualización

La clase statement dispone de un método llamado executeUpdate el cual recibe comoparámetro la cadena de caracteres que contiene la sentencia SQL a ejecutar. Este métodoúnicamente permite realizar sentencias de actualización de la BD: creación de tablas(CREATE), inserción (INSERT), actualización (UPDATE) y borrado de datos(DELETE). El método a utilizar es el siguiente:

stmt.executeUpdate(sentencia);

Vamos a ver a continuación un ejemplo de estas operaciones. Crearemos una tablaALUMNOS en nuestra base de datos y añadiremos datos a la misma. La sentencia para lacreación de la tabla será la siguiente:

String st_crea = "CREATE TABLE ALUMNOS (exp INTEGER,nombre VARCHAR(32),sexo CHAR(1),PRIMARY KEY (exp)

)";stmt.executeUpdate(st_crea);

Una vez creada la tabla podremos insertar datos en ella como se muestra a continuación:

String st_inserta = "INSERT INTO ALUMNOS(exp, nombre)VALUES(1285, 'Manu', 'M')";

stmt.executeUpdate(st_inserta);

Cuando tengamos datos dentro de la tabla, podremos modificarlos utilizando para ellouna sentencia UPDATE:

String st_actualiza = "UPDATE FROM ALUMNOSSET sexo = 'H' WHERE exp = 1285";

stmt.executeUpdate(st_actualiza);

Si queremos eliminar un registro de la tabla utilizaremos una sentencia DELETE como semuestra a continuación:

String st_borra = "DELETE FROM ALUMNOSWHERE exp = 1285";

Programación en Lenguaje Java

84Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 85: Programación en Lenguaje Java

stmt.executeUpdate(st_borra);

El método executeUpdate nos devuelve un entero que nos dice el número de registros alos que ha afectado la operación, en caso de sentencias INSERT, UPDATE y DELETE.La creación de tablas nos devuelve siempre 0.

9.5. Otras llamadas a la BD

En la interfaz Statement podemos observar un tercer método que podemos utilizar parala ejecución de sentencias SQL. Hasta ahora hemos visto como para la ejecución desentencias que devuelven datos (consultas) debemos usar executeQuery, mientras quepara las sentencias INSERT, DELETE, UPDATE e instrucciones DDL utilizamosexecuteUpdate. Sin embargo, puede haber ocasiones en las que no conozcamos deantemano el tipo de la sentencia que vamos a utilizar (por ejemplo si la sentencia laintroduce el usuario). En este caso podemos usar el método execute.

boolean hay_result = stmt.execute(sentencia);

Podemos ver que el método devuelve un valor booleano. Este valor será true si lasentencia ha devuelto resultados (uno o varios objetos ResultSet), y false en el caso deque sólo haya devuelto el número de registros afectados. Tras haber ejecutado lasentencia con el método anterior, para obtener estos datos devueltos proporciona una seriede métodos:

int n = stmt.getUpdateCount();

El método getUpdateCount nos devuelve el número de registros a los que afecta laactualización, inserción o borrado, al igual que el resultado que devolvía executeUpdate.

ResultSet rs = stmt.getResultSet();

El método getResultSet nos devolverá el objeto ResultSet que haya devuelto en el casode ser una consulta, al igual que hacía executeQuery. Sin embargo, de esta forma nospermitirá además tener múltiples objetos ResultSet como resultado de una llamada. Esopuede ser necesario, por ejemplo, en el caso de una llamada a un procedimiento, que nospuede devolver varios resultados como veremos más adelante. Para movernos al siguienteResultSet utilizaremos el siguiente método:

boolean hay_mas_results = stmt.getMoreResults();

La llamada a este método nos moverá al siguiente ResultSet devuelto, devolviéndonostrue en el caso de que exista, y false en el caso de que no haya más resultados. Si existe,una vez nos hayamos movido podremos consultar el nuevo ResultSet llamandonuevamente al método getResultSet.

Otra llamada disponible es el método executeBatch. Este método nos permite enviarvarias sentencias SQL a la vez. No puede contener sentencias SELECT. Devuelve unarray de enteros que indicará el número de registros afectados por las sentencias SQL.

Programación en Lenguaje Java

85Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 86: Programación en Lenguaje Java

Para añadir sentencias haremos uso del método addBatch. Un ejemplo de ejecución es elsiguiente:

stmt.addBatch("INSERT INTO ALUMNOS(exp, nombre)VALUES(1285, 'Manu', 'M')");

stmt.addBatch("INSERT INTO ALUMNOS(exp, nombre)VALUES(1299, 'Miguel', 'M')");

int[] res = stmt.executeBatch();

Por último, vamos a comentar el método getGeneratedKeys, también del objetoStatement. En muchas ocasiones hacemos inserciones en tablas cuyo identificador es unautonumérico. Por lo tanto, este valor no lo especificaremos nosotros manualmente, sinoque se asignará de forma automática en la inserción. Sin embargo, muchas veces nospuede interesar conocer cual ha sido dicho identificador, para así por ejemplo poderinsertar a continuación un registro de otra tabla que haga referencia al primero. Esto lopodremos hacer con el método getGeneratedKeys, que nos devuelve un ResultSet quecontiene la clave generada:

ResultSet res = sentSQL.getGeneratedKeys();int id = -1;if(res.next()) {

id = res.getInt(1);}

Programación en Lenguaje Java

86Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 87: Programación en Lenguaje Java

10. Ejercicios de introducción a JDBC

10.1. Base de datos a usar

Para los primeros ejercicios vamos a utilizar el SGBD MySQL. Descargaros el ficherocomprimido con las plantillas de la sesión. La BD está disponible en el proyecto deEclipse. Es un fichero de texto con los comandos SQL para generar la BD. Para crear laBD ejecutáis la tarea Ant initBD, que se encuentra en la carpeta db. Es una base de datosmuy sencilla donde tenemos usuarios de una agencia de viajes y los vuelos disponibles.También se mantendrá los datos de quién ha reservado un vuelo. La estructura de la BDes la siguiente:

Programación en Lenguaje Java

87Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 88: Programación en Lenguaje Java

Tablas y relaciones en la base de datos a utilizar

10.2. Conexión con la base de datos y consulta simple

El primer ejercicio consiste en realizar una conexión a la BD y mostrar los datos de unaconsulta sencilla. Para ello debéis descargaros la plantilla de la sesión. Es un proyectopara Eclipse que contiene el driver para MySQL.

Lo primero es obtener una conexión (en el constructor de la clase). Luego váis a realizar

Programación en Lenguaje Java

88Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 89: Programación en Lenguaje Java

un método (listaVuelos) que realice un listado de todos los vuelos con toda suinformación. Finalizaréis con el cierre de la conexión. Nota: para obtener la fecha llamada getDate y para la hora getTime.

10.3. Movimiento por el ResultSet y actualización

Vamos a realizar un ejercicio para ver cómo nos podemos mover por el ResultSet. Paraello, realizaremos un menú que permita consultar hoteles, nos muestre los datos de unhotel y nos permita movernos por la lista de hoteles. El método a implementar esconsultaHoteles. El menú ya está implementado, simplemente tenéis que crear lassentencias y llamar a los métodos correspondientes. Una de las opciones permitedescontar una plaza disponible.

Para poder moveros por el ResultSet tenéis que crear la sentencia comoResultSet.TYPE_SCROLL_INSENSITIVE y ResultSet.CONCUR_READ_ONLY. El tipode consulta SENSITIVE no funciona en la versión actual de MySQL o del driver. Para laparte de actualización, tenéis que hacer que el tipo de concurrencia seaResultSet.CONCUR_UPDATABLE.

10.4. Actualización de la base de datos

Vamos a realizar un ejercicio donde actualicemos la BD. Hay que realizar un método, intactualizaEuros(), que cambiará el valor en el campo precio de la tabla hotelesconvirtiéndolo a euros. Para ello, primero tenéis que convertir la columna precio en real yluego realizar la conversión. Podéis usar los siguientes comandos SQL:

• alter table hoteles change precio precio double(10,3)• update hoteles set precio = precio/166.386

El método devolverá los registros que se han visto afectados y se mostrarán por la salidaestándar.

10.5. Optativos

Como parte optativa se proponen dos ejercicios: en el primero se trata de mostar unlistado de usuarios y permitir modificar todos los datos de un usuario en concreto; en elsegundo se añadirá una opción para borrar un usuario, usando deleteRow;

Programación en Lenguaje Java

89Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 90: Programación en Lenguaje Java

11. Transacciones, optimización de sentencias y patrones de datos

11.1. Optimización de sentencias

Cuando ejecutamos una sentencia SQL, esta se compila y se manda al SGBD. Si la vamosa invocar repetidas veces, puede ser conveniente dejar esa sentencia preparada(precompilada) para que pueda ser ejecutada de forma más eficiente. Para hacer estoutilizaremos la interfaz PreparedStatement, que podrá obtenerse a partir de la conexióna la BD de la siguiente forma:

PreparedStatement ps = con.prepareStatement("UPDATE FROM alumnosSET sexo = 'H' WHERE exp>1200 AND exp<1300");

Vemos que a este objeto, a diferencia del objeto Statement visto anteriormente, leproporcionamos la sentencia SQL en el momento de su creación, por lo que estarápreparado y optimizado para la ejecución de dicha sentencia posteriormente.

Sin embargo, lo más común es que necesitemos hacer variaciones sobre la sentencia, yaque normalmente no será necesario ejecutar repetidas veces la misma sentenciaexactamente, sino variaciones de ella. Por ello, este objeto nos permite parametrizar lasentencia. Estableceremos las posiciones de los parámetros con el carácter '?' dentro de lacadena de la sentencia, tal como se muestra a continuación:

PreparedStatement ps = con.prepareStatement("UPDATE FROM alumnosSET sexo = 'H' WHERE exp > ? AND exp < ?");

En este caso tenemos dos parámetros, que será el número de expediente mínimo y elmáximo del rango que queremos actualizar. Cuando ejecutemos esta sentencia, el sexo delos alumnos desde expediente inferior hasta expediente superior se establecerá a 'H'.

Para dar valor a estos parámetros utilizaremos los métodos setXXX donde XXX será eltipo de los datos que asignamos al parámetro (recordad los métodos del ResultSet),indicando el número del parámetro (que empieza desde 1) y el valor que le queremos dar.Por ejemplo, para asignar valores enteros a los parámetros de nuestro ejemplo haremos:

ps.setInt(1,1200);ps.setInt(2,1300);

Una vez asignados los parámetros, podremos ejecutar la sentencia llamando al métodoexecuteUpdate (ahora sin parámetros) del objeto PreparedStatement:

int n = ps.executeUpdate();

Igual que en el caso de los objetos Statement, podremos utilizar cualquier otro de los

Programación en Lenguaje Java

90Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 91: Programación en Lenguaje Java

métodos para la ejecución de sentencias, executeQuery o execute, según el tipo desentencia que vayamos a ejecutar.

Una característica importante es que los parámetros sólo sirven para datos, es decir, nopodemos sustituir el nombre de la tabla o de una columna por el signo '?'. Otra cosa atener en cuenta es que una vez asignados los parámetros, estos no desaparecen, sino quese mantienen hasta que se vuelvan a asignar o se ejecute una llamada al métodoclearParameters.

11.1.1. SQL injection

Un problema de seguridad en la base de datos que se nos puede plantear es el SQLinjection. Se trata de insertar código SQL dentro de otro código SQL, para alterar sufuncionamiento y conseguir que se ejecute alguna sentencia maliciosa. Imaginad quetenemos el siguiente código en una página .jsp o en una clase Java:

String s="SELECT * FROM usuarios WHERE nombre='"+nombre+"';";

La variable nombre es una cadena cuyo valor viene de un campo que es introducido porel usuario. Al introducir el usuario un nombre cualquiera, el código SQL se ejecuta ynada extraño pasa. Pero esta opción nos permite añadir código propio que nos permitadañar o incluso permitirnos tomar el control de la BD. Imaginad que el usuario haintroducido el siguiente código: Miguel'; drop table usuarios; select * from usuarios;grant all privileges .... Entonces el código que se ejecutaría sería:

SELECT * FROM usuarios WHERE nombre='Miguel'; drop table usuarios;select * from usuarios; grant all privileges ...

Para evitar la inyección de SQL se recurre, en Java, a usar una sentencia preparada. Deesta manera tendríamos:

PreparedStatement ps = con.prepareStatement("SELECT * FROM usuarios WHERE nombre=?");

ps.setString(nombre);

Y la sentencia SQL que se ejecutaría, con la misma entrada que antes, es:

SELECT * FROM usuarios WHERE nombre="Miguel'; drop table usuarios;select * from usuarios; grant all privileges ...";

11.2. Transacciones

Muchas veces, cuando tengamos que realizar una serie de acciones, queremos que todasse hayan realizado correctamente, o bien que no se realice ninguna de ellas, pero no quese realicen algunas y otras no.

Podemos ver esto mediante un ejemplo, en el que se va a hacer una reserva de vuelos para

Programación en Lenguaje Java

91Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 92: Programación en Lenguaje Java

ir desde Alicante a Osaka. Para hacer esto tendremos que hacer trasbordo en dosaeropuertos, por lo que tenemos que reservar un vuelo Alicante-Madrid, un vueloMadrid-Amsterdam y un vuelo Amsterdam-Osaka. Si cualquiera de estos tres vuelosestuviese lleno y no pudiésemos reservar, no queremos reservar ninguno de los otros dosporque no nos serviría de nada. Por lo tanto, sólo nos interesa que la reserva se lleve acabo si podemos reservar los tres vuelos.

Una transacción es un conjunto de sentencias que deben ser ejecutadas como una unidad,de forma que si una de ellas no puede realizarse, no se llevará a cabo ninguna. Dicho deotra manera, las transacciones hacen que la BD pase de un estado consistente al siguiente.

Pero para hacer esto encontramos un problema. Pensemos en nuestro ejemplo de lareserva de vuelos, en la que necesitaremos realizar las siguientes inserciones (reservas):

try {stmt.executeUpdate("INSERT INTO RESERVAS(pasajero, origen, " +

+ "destino) VALUES('Paquito', 'Alicante', 'Madrid')");stmt.executeUpdate("INSERT INTO RESERVAS(pasajero, origen, " +

+ "destino) VALUES('Paquito', 'Madrid', 'Amsterdam')");stmt.executeUpdate("INSERT INTO RESERVAS(pasajero, origen, " +

+ "destino) VALUES('Paquito', 'Amsterdam', 'Osaka')");}catch(SQLException e) {

// ¿Dónde ha fallado? ¿Qué hacemos ahora?}

En este caso, vemos que si falla la reserva de uno de los tres vuelos obtendremos unaexcepción, pero en ese caso, ¿cómo podremos saber dónde se ha producido el fallo yhasta qué acción debemos deshacer? Con la excepción lo único que sabemos es que algoha fallado, pero no sabremos dónde ha sido, por lo que de esta forma no podremos saberhasta qué acción debemos deshacer.

Para hacer esto de una forma limpia asegurando la consistencia de los datos, utilizaremoslas operaciones de commit y rollback.

Cuando realicemos cambios en la base de datos, estos cambios se harán efectivos en ellade forma persistente cuando realicemos la operación commit. En el modo de operaciónque hemos visto hasta ahora, por defecto tenemos activado el modo auto-commit, deforma que siempre que ejecutamos alguna sentencia se realiza commit automáticamente.Sin embargo, en el caso de las transacciones con múltiples sentencias, no nos interesaráhacer estos cambios persistentes hasta haber comprobado que todos los cambios sepueden hacer de forma correcta. Para ello desactivaremos este modo con:

con.setAutoCommit(false);

Al desactivar este modo, una vez hayamos hecho las modificaciones de forma correcta,deberemos hacerlas persistentes mediante la operación commit llamando de formaexplícita a:

con.commit();

Programación en Lenguaje Java

92Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 93: Programación en Lenguaje Java

Si por el contrario hemos obtenido algún error, no queremos que esas modificaciones selleven a cabo finalmente en la BD, por lo que podremos deshacerlas llamando a:

con.rollback();

Por lo tanto, la operación rollback deshará todos los cambios que hayamos realizado paralos que todavía no hubiésemos hecho commit para hacerlos persistentes, permitiéndonosde esta forma implementar estas transacciones de forma atómica.

Nuestro ejemplo de la reserva de vuelos debería hacerse de la siguiente forma:

try {con.setAutoCommit(false);stmt.executeUpdate("INSERT INTO RESERVAS(pasajero, origen, " +

+ "destino) VALUES('Paquito', 'Alicante', 'Madrid')");stmt.executeUpdate("INSERT INTO RESERVAS(pasajero, origen, " +

+ "destino) VALUES('Paquito', 'Madrid', 'Amsterdam')");stmt.executeUpdate("INSERT INTO RESERVAS(pasajero, origen, " +

+ "destino) VALUES('Paquito', 'Amsterdam', 'Osaka')");// Hemos podido reservar los tres vuelos correctamentecon.commit();

}catch(SQLException e) {

// Alguno de los tres ha fallado, deshacemos los cambiostry {

con.rollback();}catch(SQLException e) {};

}

Una característica relacionada con las transacciones es la concurrencia en el acceso a laBD. Dicho de otra forma, qué sucede cuando varios usuarios se encuentran accediendo ala vez a los mismos datos y pretenden modificarlos. Un ejemplo sencillo: tenemos unatienda y dos usuarios están accediendo al mismo disco, del cual sólo queda una unidad. Elprimero de los usuarios consulta el disponible, comprueba que existe una unidad y lointroduce en su cesta de la compra. El otro usuario en ese preciso momento también estáconsultando el disponible, también le aparece una unidad y también intenta introducirloen su cesta de la compra. Al segundo usuario el sistema no debería dejarle actualizar losdatos que está manejando el primero.

La concurrencia es manejada por los distintos SGBD de manera distinta. PostGrespermite nivel de lectura confirmada. En este nivel, si una transacción lee una fila de la BDy otra transacción cambia la misma fila, en el momento que la primera transacción intenteleer de nuevo se actualiza el valor. Si las dos intentan modificar la misma fila la segundase bloqueará hasta que la primera finalice la transacción. Esto último hay que tenerlo encuenta para evitar bloqueos. Para saber el nivel concurrencia permitido por el SGBDpodemos utilizar el siguiente método de la clase Connection:

int con.getTransactionIsolation();

Los valores más comunes que puede devolver este método son:

Programación en Lenguaje Java

93Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 94: Programación en Lenguaje Java

TRANSACTION_NONE (no soporta transacciones),Connection.TRANSACTION_READ_UNCOMMITTED (soporta transacciones en sunivel más bajo), Connection.TRANSACTION_READ_COMMITTED (el nivel dePostGres antes comentado). MySQL incorpora transacciones en sus últimas versiones.Sin embargo, su funcionamiento es diferente a PostGres. Si una aplicación actualiza unafila y dentro de su transacción otra aplicación intenta modificarla, MySQL permite laactualización y no se produce ningún problema.

Un posible problema en las transacciones es el interbloqueo. Un interbloqueo se produceen la siguiente situación: una aplicación tiene que modificar dos registros. Otra aplicaciónmodifica los mismos, pero en orden inverso. Se empiezan a ejecutar las dos aplicaciones ala vez y al haber modificado un registro no dejan que la otra lo modifique. Sin embargo,ninguna de las dos terminan porque están esperando que se desbloquee su registro.MySQL no detecta esta situación. Es más, permite cambiar los datos que una transacciónsin finalizar haya realizado. PostGres, al bloquear los registros sí que provoca uninterbloqueo que detecta y lanza una excepción.

11.3. Data Access Object (DAO)

11.3.1. Problema a resolver

Supongamos que el código de acceso a los recursos de datos (normalmente bases de datosrelacionales) está incluído dentro de clases que tienen además otras responsabilidadesdiferentes. Por ejemplo supongamos que en una biblioteca tuviéramos una claseGestorPrestamos que se ocupara de realizar y gestionar los préstamos de libros. En unaprimera aproximación podríamos hacer que esta clase se encargara tanto de la lógica denegocio (por ejemplo comprobar si un usuario es "moroso" antes de prestarle un libro)como del acceso a datos (introducir el préstamo en una hipotética tabla de préstamos).

Este tipo de enfoque lleva a sistemas poco modulares y difícilmente mantenibles. Ennuestro caso, el cambio de las reglas de negocio implicaría cambios enGestorPrestamos. El problema es que además, un cambio en la base de datos tambiénimplica cambios en GestorPrestamos.

Nota:Distintas responsabilidades no deben ser delegadas en la misma clase. Este es un principiobásico del desarrollo de software, propuesto originalmente por Dijkstra en los años 70 (aunqueno exactamente en esta forma), que lo llamó separation of concerns o separación deincumbencias.

Claramente la implementación de GestorPrestamos descrita con anterioridad viola elprincipio de separación de incumbencias. Si nos llevamos la persistencia de datos a unaclase separada y dejamos GestorPrestamos solo con la lógica de negocio conseguimosun sistema mucho más modular y mantenible.

Programación en Lenguaje Java

94Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 95: Programación en Lenguaje Java

11.3.2. Discusión y beneficios

El DAO (Data Access Object) es un patrón de diseño directamente basado en elseparation of concerns, en el que se separa la persistencia de datos del resto defuncionalidades del sistema. El siguiente esquema UML muestra una estructura de clasestípica para el DAO de préstamos de la biblioteca:

Patrón DAO. Ejemplo de la biblioteca

Como se observa en la figura, el DAO es el punto de entrada al almacén de datos (aquírepresentado por un DataSource JDBC) y proporciona operaciones de tipo CRUD(Create-Read-Update-Delete).

Destacamos algunos puntos importantes:

• Como se ve, el DAO no tiene por qué implementar todas las operaciones CRUD(quizá en nuestro sistema no se puedan borrar los préstamos, solo devolver el libroquedando el registro del préstamo en la base de datos).

• En general (aunque esto es una decisión de diseño), por cada objeto de negocio ennuestro sistema, crearemos un DAO distinto. En nuestro caso además delPrestamosDAO podríamos tener también un UsuarioDAO y un LibroDAO.

• Aunque aquí el almacén de datos se representa como una base de datos compatibleJDBC no tiene por qué ser siempre así, como discutiremos a continuación

• La información que devuelve o se le pasa al DAO se encapsula en objetos de tipotransfer object (en nuestro caso la clase Prestamo), que, simplificando, no son másque "contenedores de información" y que trataremos en la discusión del patróncorrespondiente.

Hay que tener en cuenta que si el DAO se considera un patrón J2EE (o hablando más engeneral un patrón de aplicaciones de tipo enterprise) es porque prácticamente todas lasaplicaciones J2EE de cierta dimensión hacen uso intensivo de almacenes persistentes dedatos (normalmente bases de datos relacionales) aunque muchas aplicaciones J2SE no lohagan.

Otro importante beneficio del DAO es la independencia del almacén de datos: elcambio de motor de base de datos o el paso de usar un pequeño archivo XML a usar una

Programación en Lenguaje Java

95Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 96: Programación en Lenguaje Java

base de datos relacional para almacenar datos solo afectará al DAO y no a las clasesencargadas de la lógica de negocio o de presentación. Se suele usar el patrón Factory parapoder instanciar los DAOs reduciendo al máximo la dependencia del DAO concreto acrear (por ejemplo de MySQL, Oracle, XML, fichero .properties, ...)

Nota:Un principio básico de un buen diseño es identificar los aspectos de la aplicación que cambiano pueden cambiar y separarlos de los que van a permanecer fijos. Muchos patrones de diseñose basan en encapsular de alguna forma la parte que cambia para hacer más fácil la extensión delsistema. En este caso, el DAO encapsula la parte que puede variar (la interacción con la fuente dedatos).

11.3.3. Relación con otros patrones

El DAO se relaciona comúnmente con los siguientes patrones:

• Transfer object: la información que se envía/recibe del DAO se "empaqueta" enestos objetos.

• Factory: con el objeto de conseguir la independencia del almacén de datos,comúnmente se usa este patrón para instanciar los DAOs.

11.3.4. Ejemplo

Vamos a ilustrar el uso de estos patrones con un ejemplo. En primer lugar, para instanciarlos DAOs utilizaremos el patrón factoría. Nuestra factoría tendrá una serie de métodoscon los que podremos obtener los diferentes DAOs.

public class FactoriaDAOs {private static FactoriaDAOs me = new FactoriaDAOs();

private FactoriaDAOs() {}

public static FactoriaDAOs getInstance() {return me;

}

public ILibroDAO getLibroDAO() {return new LibroJDBCDAO();

}

public IClienteDAO getClienteDAO() {return new ClienteJDBCDAO();

}

}

Podemos observar que el tipo de datos que realmente devuelven los métodos de lafactoría son las interfaces que definen nuestros DAOs. De esta forma, si queremoscambiar la implementación del DAO (por ejemplo para acceder a los datos mediante JPA,

Programación en Lenguaje Java

96Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 97: Programación en Lenguaje Java

en lugar de JDBC), lo único que tendremos que hacer es instanciar en la factoría otraimplementación distinta de la correspondiente interfaz, y el resto de la aplicación no severá afectada. En esta interfaz definiremos las operaciones que nos ofrece el DAO:

public interface ILibroDAO {

LibroTO selectLibro(String isbn) throws DAOException;void addLibro(LibroTO libro) throws DAOException;int delLibro(String isbn) throws DAOException;List<LibroTO> getAllLibros() throws DAOException;int updateLibro(LibroTO libro) throws DAOException;

}

Una posible implementación de la anterior interfaz es la siguiente:

public class LibroJDBCDAO implements ILibroDAO {

public LibroTO selectLibro(String isbn) throws DAOException {

LibroTO libro = null;

Connection conn = null;PreparedStatement st = null;ResultSet rs = null;

try {String query = "select * from LIBRO where ISBN = ?";

conn = FuenteDatosJDBC.getInstance().createConnection();

st = conn.prepareStatement(query);st.setString(1, isbn);rs=st.executeQuery();

if (rs.next()) {libro = new LibroTO();libro.setIsbn(isbn);libro.setTitulo(rs.getString("titulo"));libro.setAutor(rs.getString("autor"));libro.setNumPaginas(rs.getInt("numPaginas"));libro.setFechaAlta(rs.getDate("fechaAlta"));

}

} catch (SQLException sqle) {throw new DAOException("Error en el select de libro", sqle);

} finally {try {if (rs != null) {rs.close();rs = null;

}if (st != null) {st.close();st = null;

}if (conn != null) {conn.close();

Programación en Lenguaje Java

97Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 98: Programación en Lenguaje Java

conn = null;}

} catch (SQLException sqlError) {throw new RuntimeException("Error cerrando las conexiones",

sqlError);}

}

return libro;}...

}

En esta implementación se utiliza JDBC para acceder a los datos. Hemos de destacar enella el uso de nested exceptions para encapsular los errores que puedan producirse en lautilización de JDBC. De esta manera, a la capa superior siempre le llegarán excepcionesde tipo DAOException, lo cual nos aislará de la implementación concreta que utilicemospara acceder a los datos (esto no sería así si devolviesemos SQLException, que pertenecea la librería JDBC). También es importante cerrar las conexiones siempre en el bloquefinally, tal como podemos ver en el ejemplo, para así asegurarnos de que siempre secierren, pase lo que pase en el método.

Destacamos también el uso del patrón transfer object (TO) para encapsular y devolver losdatos obtenidos. El objeto TO tiene como campos todos los datos que se obtienen en laconsulta, y los correspondientes getters y setters para acceder a ellos:

public class LibroTO {

private static final long serialVersionUID =5874144497759547336L;

private String isbn;private String titulo;private String autor;private int numPaginas;private Date fechaAlta;

public LibroTO() {}

public LibroTO(String isbn, String titulo, String autor,int numPaginas, Date fechaAlta) {

this.init(isbn, titulo, autor, numPaginas, fechaAlta);}

public LibroTO(LibroTO libro) {this.init(libro.isbn, libro.titulo, libro.autor,

libro.numPaginas, libro.fechaAlta);}

private void init(String isbn, String titulo, String autor,int numPaginas, Date fechaAlta) {

this.isbn = isbn;this.titulo = titulo;this.autor = autor;

Programación en Lenguaje Java

98Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 99: Programación en Lenguaje Java

this.numPaginas = numPaginas;this.fechaAlta = fechaAlta;

}

public String getAutor() {return autor;

}

public void setAutor(String autor) {this.autor = autor;

}

// Resto de getters y setters...

}

Utilizaremos este tipo de objetos para encapsular los datos que nuestro DAO intercambiecon las capas superiores. Estos serán los datos que se obtengan en las consultas, o bien losdatos que queramos insertar o modificar.

Por último, hemos visto que nuestro DAO obtenía las conexiones a través de un objetoFuenteDatosJDBC. Este objeto nos permitirá independizarnos de la forma de obtener lasconexiones. De esta forma, posteriormente podría ser modificado para que en lugar decrear nuevas conexiones directamente, las obtenga a partir de una fuente de datosgestionada por un servidor de aplicaciones.

public class FuenteDatosJDBC {

private static FuenteDatosJDBC me = new FuenteDatosJDBC();

private static Log logger =LogFactory.getLog(FuenteDatosJDBC.class.getName());

private FuenteDatosJDBC() {try {Class.forName("com.mysql.jdbc.Driver");

} catch (ClassNotFoundException cnfe) {logger.fatal("No se encuentra el Driver de MySQL ",cnfe);

}}

public static FuenteDatosJDBC getInstance() {return me;

}

public Connection createConnection() {Connection conn = null;

try {conn = DriverManager.getConnection(

"jdbc:mysql://localhost/biblioteca", "root", "");} catch (SQLException sqle) {logger.fatal("No se ha podido crear la conexion", sqle);

}

return conn;

Programación en Lenguaje Java

99Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 100: Programación en Lenguaje Java

}

}

Programación en Lenguaje Java

100Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 101: Programación en Lenguaje Java

12. Ejercicios de transacciones, optimización, y DAO

12.1. Transacciones

Vamos a trabajar con las transacciones. Para ello, vais a crear un método llamadoreservaViaje. Pedirá por la entrada estándar tres parámetros, el identificador del usuarioque reserva, el número de vuelo y el número de hotel. Se debe restar tanto al hotel comoal vuelo una plaza del campo PlazasDisp. Cuando se haga esto, se debe introducir unregistro en las tablas correspondientes de reserva. Como el campo PlazasDisp es sinsigno, cuando intentemos restar a algún elemento que no tenga plazas, dará un errorMySQL. Este es un buen ejercicio para comprobar el funcionamiento de lastransacciones.

12.2. Sentencias preparadas

En este ejercicio vamos a trabajar con las sentencias preparadas. Tenéis que hacer unmétodo que liste los hoteles que estén por debajo de un precio determinado y que tenganun número de plazas disponibles. Haced uso de las sentencias preparadas.

12.3. Creación de patrones

Vamos a hacer los DAO de la base de datos que hemos estado usando hasta ahora. Parasimplificarlo, haremos el DAO de la tabla Hoteles. También tenéis que implementar elHotelTO. El DAO tendrá un método de listar hoteles (que devolverá un List de hoteles),otro para seleccionar un hotel por su identificador, uno más para añadir un nuevo hotel yotro para borrarlo, y por último uno para modificarlo.

En la plantilla de esta sesión tenéis los métodos a implementar. Como parte optativa, sepropone que hagáis el resto de DAOs.

Programación en Lenguaje Java

101Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 102: Programación en Lenguaje Java

13. Roadmap

13.1. Aspectos destacados

• Marco de colecciones de Java• Uso del polimorfismo mediante interfaces o clases abstractas• Patrón factoría• Concepto de wrapper• Tratamiento de errores mediante la captura de excepciones• Lanzar excepciones para la gestión de errores• Identificar los casos en los que es necesario la utilización de hilos• Creación de hilos mediante herencia y mediante interfaces. Determinar la forma más

adecuada en cada caso• Operaciones básicas y propiedades de los hilos• Métodos para la sincronización de hilos. Identificar los casos en los que es necesario

utilizar cada una de ellas• Manejo de las operaciones básicas de los flujos de E/S• Tipos de hilos existentes y funciones de cada uno• Acceso a ficheros mediante hilos de E/S• Acceso a los recursos de una aplicación• Manejo de los flujos DataInputStream y DataOutputStream• Mecanismo de serialización de objetos• Acceder a una URL desde una aplicación Java• Programación del protocolo HTTP desde una aplicación cliente• Visión general de los componentes principales para crear aplicaciones AWT/Swing

(botones, menús, desplegables, ventanas, etc)• Uso de gestores de disposición (layout managers) para colocar los componentes en

aplicaciones gráficas AWT/Swing• Manejo de eventos de diferentes tipos (de acción, de ratón, de ventana, etc) en

aplicaciones AWT/Swing• Establecer una conexión mediante JDBC• Realizar una consulta a la BD y explorar el conjunto de registros devuelto• Ejecutar sentencias de actualización de la BD• Efectuar transacciones en la BD• Optimización de sentencias• Patrón DAO y Transfer Objects

13.2. Certificación Sun

Aunque el resto del curso de Especialista está orientado a certificaciones en el ámbito deJ2EE, este módulo de Java y Herramientas de Desarrollo se basa en las certificacionespara Java básico o estándar. Dichas certificaciones son dos:

Programación en Lenguaje Java

102Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 103: Programación en Lenguaje Java

• SCJP (Sun Certified Java Programmer - Certificado Sun de Programador Java):http://www.sun.com/training/certification/java/scjp.xml

• SCJP (Sun Certified Java Developper - Certificado Sun de Desarrollador Java):http://www.sun.com/training/certification/java/scjd.xml

Para la primera (SCJP), se debe completar un examen. En el caso del certificado para laplataforma Java 1.5, el examen abarca los siguientes apartados generales:

• Sección 1: Declaraciones, inicializaciones y ámbitos: evalúa si el alumno es capazde escribir código que declare clases o interfaces, utilice adecuadamente la estructurade paquetes e imports, utilice código con tipos simples, arrays, objetos estáticos,variables locales, constructores, métodos estáticos y no estáticos, sobrecarga demétodos, etc

• Sección 2: Control de flujo: uso de sentencias if, switch, bucles (for, do,

while, break, continue), manejo y uso de excepciones (try-catch-finally), etc• Sección 3: Contenidos del API: uso de wrappers básicos (Integer, Boolean, etc),

entrada/salida de ficheros, serialización de objetos para E/S, formateo de datos con elpaquete java.text, y parseo de cadenas mediante expresiones regulares y similares(paquetes java.util y java.util.regex)

• Sección 4: Concurrencia: manejo de hilos (mediante Thread y mediante Runnable),estados de los hilos, interbloqueos y sincronización

• Sección 5: Conceptos sobre orientación a objetos: desarrollo de código que cumplalos requerimientos de encapsulamiento, cohesión y acoplamiento entre clases (muchacohesión, poco acoplamiento). Uso del polimorfismo y del casting. Uso de métodossobrecargados, llamadas a la superclase, etc.

• Sección 6: Colecciones: determinar qué tipos de colecciones (listas, hashmaps, etc)utilizar en diferentes supuestos. Comparaciones y ordenaciones entre objetos de unacolección, etc

• Sección 7: Fundamentos: uso correcto de los modificadores de acceso, declaracionesde paquetes, imports. Seguimiento de trazas. Manejo de referencias a objetos. Uso delrecolector de basura... etc

Para la segunda (SCJD) es necesario haber obtenido antes la primera certificación(SCJP). Después, se deben superar dos pruebas: un supuesto de programación, y unexamen.

La primera prueba (el supuesto de programación), consiste en escribir código paraimplementar una supuesta aplicación para empresa. Se evaluarán aspectos comodocumentación, diseño orientado a objetos, desarrollo de la interfaz gráfica,interbloqueos, etc.

La segunda prueba (el examen), es una explicación sobre el desarrollo que hayamoshecho en el supuesto de programación anterior, explicando las decisiones principales quehemos tenido que tomar, ventajas y desventajas de las mismas, y justificación de dichasdecisiones, en función de los objetivos propuestos para la implementación.

Programación en Lenguaje Java

103Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 104: Programación en Lenguaje Java

Más información sobre las certificaciones en los enlaces respectivos vistos antes.

13.3. Recursos adicionales

13.3.1. Bibliografía

• Curso de Java, Ian F. Darwin, Ed. Anaya Multimedia, Colección O'Reilly• Java 2 v5.0, Varios autores, Ed. Anaya Multimedia, Colección Wrox• Piensa en Java, Bruce Eckel, Ed. Prentice Hall• Core Java 2, Cay Horstmann y Gary Cornell, Ed. Prentice Hall PTR• Java in a Nutshell, David Flanagan, Ed. O'Reilly Media

13.3.2. Enlaces

• Web oficial de Java, http://www.java.sun.com

Programación en Lenguaje Java

104Copyright © 2008-2009 Depto. CCIA All rights reserved.

Page 105: Programación en Lenguaje Java

Programación en Lenguaje Java

105Copyright © 2008-2009 Depto. CCIA All rights reserved.