Download Además de escribir aplicaciones completas, Java se puede usar para

Document related concepts
no text concepts found
Transcript
COMPONENTES JAVABEANS
Además de escribir aplicaciones completas, Java se puede usar para construir
componentes reutilizables.
Un componente es una porción de código que se utiliza para ser incorporado a
una aplicación. En POO los componentes son clases compiladas. El ejemplo más simple
es el del componente visual que utilizan los productos RAD, como un botón o un menú
que se incluye en un formulario.
En general, un componente es una cáscara, a la que el programador cliente le
agrega comportamiento. Este comportamiento suele definirse en base a estímulos y las
respuestas a los mismos, en consonancia con la metodología de orientación a objetos y la
programación guiada por eventos que estudiaremos luego.
Los componentes de Java se denominan JavaBeans, y pueden ser simples o
indexados.
Un componente se suele caracterizar por las propiedades que determinan su
estado, los métodos que definen el comportamiento y los eventos a los cuales va a
responder.
Si bien en Java no hay propiedades, se pueden implementar métodos de consulta y modificación para cada una.
Éste es el enfoque de los JavaBeans. Cada componente tiene una serie de propiedades, con métodos que empiezan con “get” y “set” para obtener sus valores y
modificarlos. Por ejemplo, si queremos referirnos a una propiedad xxx, crearemos en
nuestro componente dos métodos: getXxx y setXxx, con esa convención de mayúsculas y
minúsculas. En el caso de las propiedades lógicas podemos utilizar “is” en vez de “get”.
Si un componente es de sólo lectura tendrá un método “get”, pero no “set”, mientras que
un componente de sólo escritura tendrá un método “set”, pero no “get”. Los métodos
“get” recibirán siempre un parámetro que deberá ser del tipo de la propiedad.
Los métodos de un componente son simples métodos de la clase, públicos.
Además, todo componente JavaBeans debe tener un constructor sin parámetros.
Todas estas convenciones, aunque parezcan caprichosas, son muy importantes
porque los productos RAD y otros integradores de componentes utilizan reflexión para
examinar el componente y determinar qué eventos y propiedades soporta. Para ello
utilizan una clase llamada Introspector, que tiene un método de clase getBeanInfo(Class).
Y éste va a buscar métodos que cumplan con la convención de nombres recién descripta.
Otra forma en que actúan los productos RAD es utilizando, para un componente
cualquiera de nombre Xxx, una clase XxxBeanInfo, que debe implementar la interfaz
BeanInfo.
Del mismo modo, si el componente utiliza eventos adicionales a los predefinidos –cosa que es perfectamente factible– se debe utilizar la nomenclatura estándar de
Swing, que luego veremos en más detalle: para el evento xxxEvent debe haber una
interfaz XxxListener, dos métodos, addXxxListener y removeXxxListener, etc..
A continuación se muestra el código fuente de un simple componente sin eventos:
// archivo Punto.java
package carlosfontela.geometria;
2
public class Punto {
private double coordX;
private double coordY;
public Punto (double x, double y) {
coordX = x;
coordY = y;
}
// propiedad x, de tipo double, de lectura y escritura
public double getX () {
return coordX;
}
public void setX (double nuevoX) {
coordX = nuevoX;
}
// propiedad y, de tipo double, de lectura y escritura
public double getY () {
return coordY;
}
public void setY (double nuevoY) {
coordY = nuevoY;
}
public void trasladar (double deltaX, double deltaY) {
coordX += deltaX;
coordY += deltaY;
}
public double distancia (Punto otro) {
return Math.sqrt
( Math.pow((coordX-otro.coordX),2) +
Math.pow((coordY-otro.coordY),2) );
}
}
Como se ve, hemos convertido a nuestra clase Punto en un componente estándar con sólo agregar los “get” y “set” correspondientes. Las propiedades de la clase se
llaman, entonces, x e y, y los métodos son trasladar y distancia.
Los JavaBeans deben ser incluidos en un archivo JAR, que contiene un manifiesto para indicar que se trata de un JavaBeans y dar a conocer el nombre de la clase y
del paquete respectivos. Para funcionar correctamente, el archivo JAR debe tener todo el
directorio (paquete) con las clases necesarias.
En general las herramientas para generar componentes se ocupan de todos los
detalles y generan un manifiesto por nosotros. De todas maneras, aquí mostramos un
manifiesto de ejemplo:
// Punto.mf
Manifest-Version: 1.0
Name: carlosfontela.geometria/Punto.class
Java-Bean: True
3
Recordemos también que, como un componente puede ser utilizado en un
contexto concurrente, deben sincronizarse todos sus métodos públicos. Es cierto que esto
puede dar lugar a deficiencias en el desempeño, pero cualquier optimización en este
sentido deberá hacerse solamente si se comprueba que la sincronización no es necesaria,
además de demostrarse que la supuesta ineficiencia es tal y que es eliminada al suprimir
la sincronización.
Los componentes JavaBeans pueden ser usados desde páginas JSP, mediante la
etiqueta jsp:usebean, que se ocupa de crear una nueva instancia del componente si no
existiera. Por ejemplo, observemos el siguiente código JSP:
<%
CarroCompras c = (CarroCompras)session.getAttribute("carro");
// si no hay carro de compras, creamos uno:
if (c == null) {
c = new CarroCompras();
session.setAttribute("carro",c);
}
%>
El código recién escrito puede reducirse bastante si el carro de compras fuera un
componente JavaBeans:
<jsp:usebean
id="carro"
class="carlosfontela.utilidades.ecommerce.CarroCompras"
scope="session"
/>
Notemos que se le puede definir un alcance de sesión, aplicación, etc., usando el
atributo scope. Igualmente se puede incluir una etiqueta jsp:setProperty, para inicializar
propiedades del componente, y una jsp:getProperty para obtener su valor. Veremos
brevemente el lenguaje JSP en el próximo capítulo.
PROGRAMACIÓN GUIADA POR EVENTOS
La programación secuencial y sus limitaciones
La modalidad de programación que se usaba antes de la llegada de los ambientes de interfaz de usuario gráfica, desde el punto de vista de la interacción del usuario con
el programa, es llamada programación secuencial.
En este paradigma, el programa tiene el control de lo que pasa e interactúa con
el usuario pidiéndole datos solamente. Se puede decir que es el usuario quien espera al
programa, ya que solamente podemos decirle lo que queremos cuando éste nos lo
pregunta explícitamente.
La arquitectura típica de esta forma de interacción es un programa que opera
según un diagrama de actividades similar al siguiente:
4
Leer comando o valor
Análisis del dato
Proceso y generación de resultado
[seguir]
Salida hacia el usuario
[terminar]
Ejemplos de esta forma de interacción son los sistemas operativos de línea de
comandos, como DOS y Unix, y todas las aplicaciones que se desarrollaban en esos
entornos.
Pero aun en esos ambientes fue surgiendo la necesidad de mejorar la interacción
con el usuario, de modo de darle la impresión de que es él quien maneja la secuencia de
ejecución.
Así apareció el concepto de menú, en el cual, si bien no se altera la forma de
trabajo que hemos descripto, se presentan al usuario una serie de opciones de las cuales
éste elige una, y la computadora realiza una u otra acción en respuesta. A continuación se
muestra el diagrama de actividades de una interfaz con menúes:
5
Menú
[opción 3]
[opción 1]
[opción 2]
Análisis del dato
Análisis del dato
Análisis del dato
[seguir]
Proceso y generación de resultado
Proceso y generación de resultado
Proceso y generación de resultado
Salida hacia el usuario
Salida hacia el usuario
Salida hacia el usuario
[terminar]
Con el tiempo, se fue dejando de lado la idea de un único menú en cada pantalla
y se pasó a tener menúes desplegables con un gran número de opciones.
Eso hicieron los programas típicos de procesamiento de texto y planilla de
cálculo. Pero como mientras se estaba editando un documento no se podía trabajar
simultáneamente con un menú, se implementó el concepto de modo, de forma tal que el
usuario podía cambiar, por ejemplo, del “modo de edición” al “modo de menú” o
viceversa, pulsando una tecla. Wordstar y Visicalc fueron los más conocidos entre los
precursores de esta forma de interacción.
Pero notemos que en estos casos las opciones que tiene el usuario siempre están
limitadas a las que la aplicación le muestra en pantalla.
¿Cómo haríamos con este esquema para prever las múltiples acciones que puede
hacer el usuario en un ambiente WIMP?
Observemos que en una interfaz gráfica el usuario puede cerrar una ventana,
minimizarla o maximizarla, moverla, seleccionar cualquier botón, seleccionar algún
menú, hacer clic con el mouse en algún gráfico o elemento que se ve en la pantalla,
escribir algo con el teclado en un cuadro de texto, etc., todo esto sin un orden prefijado
por el programador de la aplicación. Incluso el usuario posee múltiples caminos para
realizar una misma acción. La noción de entrada y salida viene asociada ya no tanto a lo
que se ingresa por teclado o se informa en pantalla, sino al almacenamiento de datos en
medios permanentes o la impresión en papel.
Eventos y programación
Al estudiar los diagramas de estados de UML dijimos que un evento indica la
aparición de un estímulo que puede disparar una transición de estados. Es la
especificación de un acontecimiento significativo, como una señal recibida, un cambio de
6
estado o el simple paso de un intervalo de tiempo.
La programación guiada por eventos (cuyo nombre viene de la expresión
inglesa “event driven programming”) es la modalidad de interacción que se impuso con
las interfaces de usuario WIMP desde el punto de vista del programador. De todas
maneras, no es exclusiva de éstas.
La idea de la programación por eventos, expresada de un modo simple es que,
en lugar de que el usuario espere al programa, ponemos al programa a esperar al usuario.
Ahora el sistema espera la ocurrencia de eventos y actúa en consecuencia. Es decir, el
programa debe saber cómo manejar los objetos disponibles y responder a los estímulos
que vienen del usuario.
En la programación guiada por eventos, no es que las operaciones se produzcan
porque el programa llegó a una determinada fase de ejecución, sino que las provoca un
usuario o un actor externo mediante un evento.
Un evento, en este contexto, es algo interesante que pasa en el sistema. Podría
decirse que un evento es una condición o acción que es observada por el sistema pero
ocurre fuera del control del mismo. Observamos que esta definición no invalida la
anterior, sino que la complementa.
Los eventos son previstos, pero no planeados. Esto es, se conoce el hecho de
que el evento puede ocurrir, pero no se conocen las circunstancias o el momento en que
ocurrirá.
A continuación se muestra como ejemplo el diagrama de estados de una aplicación simple utilizando programación por eventos:
Inicio aplicación
[Aplicación iniciada]
Esperando eventos
[Nuevo evento]
[Evento procesado]
Procesando eventos
[Evento de terminación]
Estas aplicaciones se inician creando la pantalla principal e iniciando la cola de
eventos. El estado casi permanente de la aplicación es estar inactiva, esperando la
ocurrencia de eventos.
Los eventos son procesados en orden, obteniéndolos de la cola de eventos, y el
proceso de cada evento implica despacharlo al componente correcto.
Los eventos pueden ser provocados por el usuario, cuando es éste quien los
7
dispara, o por el sistema o el medio.
Ejemplos de eventos de usuario pueden ser:
Un clic del mouse.
•
La pulsación de una tecla.
•
Una selección en un menú.
•
Un movimiento del mouse.
•
La toma o pérdida de foco.
•
Ejemplos de eventos del sistema o del medio pueden ser:
•
Los intervalos del reloj.
•
El paso del tiempo.
•
El cierre del sistema.
•
Un mensaje que proviene de otra aplicación.
•
Un mensaje que proviene de otra computadora.
La POO es muy importante para soportar la programación por eventos. Cada
componente que recibe un estímulo es un objeto, y este objeto va a responder al mensaje
con la ejecución de un método definido en su clase. Por ejemplo, cuando un usuario pulsa
sobre un botón, está provocando un evento que envía un mensaje a dicho botón. A su vez
esto provoca que éste reaccione con un método definido en su clase.
Eventos en Swing
¿Cómo manejamos el evento? Es decir, ¿cómo llamamos al código que debe
procesar el evento?
Cuando se hace uso de callbacks, como en el caso de Swing, existe una entidad
que contiene el código de manejo para los eventos. Cada componente registra qué código
callback se debe ejecutar cuando recibe un evento.
Así, hay un controlador que está permanentemente escuchando para ver si se
producen eventos. Cuando recibe uno, llama al método que corresponda en el
componente que se suscribió para recibirlo.
En consecuencia, un evento es también un vínculo entre una ocurrencia en el
sistema y una porción de código que responde a esa ocurrencia. Esa porción de código es
lo que denominamos un manejador de eventos.
Ya hemos dicho que cada componente Swing (un objeto instancia de una clase
heredera de JComponent) puede disparar un evento. Este evento puede ser escuchado por
uno o más observadores1 (en inglés, “listeners”), que actúan en base a lo que el
programador haya indicado. De este modo, el evento se lanza en un lugar (el
componente) y puede ser manejado en un lugar diferente (el observador).
Cada observador es una instancia de una clase que implementa una interfaz
observadora. Llamamos interfaz observadora a cualquiera que sea descendiente de
EventListener, como ActionListener, KeyListener, etc..
Los eventos son instancias de clases descendientes de EventObject, como
ActionEvent, ItemEvent, etc..
A continuación se muestra una tabla que clarifica un poco estos conceptos:
1
La palabra “oyente”, que se usa en general, no me parece adecuada porque da una idea de pasividad.
Además, “observador” está más en consonancia con el patrón Observador, en el cual se basa el manejo de
eventos de Swing.
8
Significado
Hereda de
Ejemplos
Otro nombre
Componente
Clase capaz de
recibir y emitir
estímulos
JComponent
JButton, JLabel,
JMenu, etc.
Emisor de
eventos
Observador
Clase que está
dispuesta a recibir
eventos
interfaz
ActionListener,
EventListener FocusListener,
etc.
Evento
Cualquier cosa
EventObject
interesante que
ocurre en el sistema
KeyEvent,
MouseEvent
Suscriptor
Estímulo
Véase este ejemplo:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Hola3 extends JApplet {
JButton boton = new JButton("Presione");
JTextField texto = new JTextField(4);
class BotonHola implements ActionListener {
public void actionPerformed(ActionEvent e) {
texto.setText(“Hola a todos”);
}
}
BotonHola observador = new BotonHola();
public void init() {
boton.addActionListener(observador);
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(boton);
cp.add(texto);
}
}
Notemos que la clase BotonHola implementa la interfaz observadora ActionListener. Por lo tanto, es un objeto de la clase BotonHola el observador que va a manejar el
evento.
Toda la lógica de manejo del evento va en la clase observadora. Así lo hicimos
al escribir:
class BotonHola implements ActionListener {
public void actionPerformed(ActionEvent e) {
texto.setText(“Hola a todos”);
}
}
En general las clases observadoras se definen como internas al componente,
9
porque es lógico acoplarlas con las clases a las cuales sirven.
De todas formas, se pueden mantener ambas clases separadas. Esto es útil para
permitir que el programador personalice el comportamiento de componentes visuales sin
tener que cambiar las propias clases.
Otra forma habitual de trabajar es mediante clases internas anónimas. En nuestro ejemplo, la clase BotonHola sólo sirvió para definir el método actionPerformed. A
continuación se escribe el applet completa con una clase interna anónima descendiente de
JButton, lo que nos ahorra la definición de BotonHola:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Hola5 extends JApplet {
JButton boton = new JButton("Presione");
JTextField texto = new JTextField(4);
// la clase BotonHola no está más:
ActionListener observador = new ActionListener() {
public void actionPerformed(ActionEvent e) {
texto.setText(“Hola a todos”);
}
};
public void init() {
boton.addActionListener(observador);
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(boton);
cp.add(texto);
}
}
Pero el lanzamiento del evento, representado aquí por un objeto de la clase
ActionEvent, se hace desde la clase Hola5. Como acabamos de decir, se pueden añadir
más observadores que respondan a un determinado evento. Por eso decimos que cada
componente Swing puede reportar todos los eventos individualmente, y es el
programador quien registra su interés en uno en particular, o simplemente lo ignora.
Todos los componentes de Swing tienen dos métodos para cada tipo de evento
que pueden lanzar: addXxxListener y removeXxxListener, donde Xxx es el nombre de la
interfaz. El primero permite registrar el interés en un evento y el segundo declarar que el
mismo ya no interesa más. Por lo tanto, la suscripción puede manejarse dinámicamente.
En líneas generales, lo que hace un programador al que le interesa un evento es
crear un objeto observador y registrarlo con el componente que va a lanzar dicho evento.
Eso hicimos en el ejemplo:
boton.addActionListener(observador);
Descomponiendo el proceso en pasos, la idea es:
1. Agregar la palabra Listener al nombre de evento, y ésta será la interfaz
10
observadora a implementar (en la clase interna).
2. Implementar la interfaz y escribir los métodos para los eventos que se
quieren capturar.
3. Crear un objeto de la clase observadora y registrarlo en el componente con
el prefijo add frente al nombre de la misma.
Observamos que en este modelo de eventos se trabaja con el patrón Observador,
pues hay un objeto que actúa en función de los cambios que se hacen sobre alguna parte
del sistema. El sistema funciona como objeto “observado”, y el observador maneja los
eventos reaccionando con un comportamiento determinado.
Como dijimos, cada componente puede lanzar uno o más eventos. Pero cada
tipo de componente soporta sólo ciertos tipos de eventos. Si se intenta capturar un evento
que no existe para un determinado componente, se provocará un error de compilación.
A continuación hay una lista de algunos eventos, interfaces observadoras y
métodos que se usan2:
Evento
Significado
Interfaz
observadora
Métodos de la interfaz
ActionEvent
Un botón
presionado y,
en general,
cualquier
elección
ActionListener
actionPerformed (ActionEvent)
AdjustmentEvent
Cualquier
acción que
implique
ajustar
posición
AdjustmentListener
adjustmentValueChanged
(AdjustmentEvent)
FocusEvent
Toma de foco FocusListener
por el
componente
focusGained (FocusEvent),
focusLost (FocusEvent)
KeyEvent
Presión de
una tecla
keyPressed (KeyEvent),
keyReleased (KeyEvent),
keyTyped (KeyEvent)
MouseEvent
Clic de mouse MouseListener
mouseClicked (MouseEvent),
mouseEntered(MouseEvent),
mouseExited (MouseEvent),
mousePressed (MouseEvent),
mouseReleased (MouseEvent)
MouseEvent
Movimiento
de mouse
mouseDragged (MouseEvent),
mouseMoved(MouseEvent)
2
KeyListener
MouseMotionListener
Como se ve, los nombres son bastante descriptivos.
11
Evento
Significado
Interfaz
observadora
WindowEvent
Cambios de
WindowListener
estado en la
ventana en
cuanto a
activarla,
agrandarla,
achicarla, etc.
windowOpened(WindowEvent),
windowClosing(WindowEvent),
windowClosed(WindowEvent),
windowActivated
(WindowEvent),
windowDeactivated
(WindowEvent),
windowIconified (WindowEvent),
windowDeiconified
(WindowEvent)
ItemEvent
Elección de
ItemListener
un ítem, como
una opción en
un botón de
radio, en una
lista, etc.
itemStateChanged(ItemEvent)
TextEvent
Escritura de
un texto
addTextListener,
removeTextListener
TextListener
Métodos de la interfaz
Ya hemos dicho que no cualquier componente dispara cualquier evento. Por
ejemplo, un JButton puede disparar un ActionEvent pero no un WindowEvent; de la
misma forma, JCheckBox disparará un ItemEvent, pero no un AdjustmentEvent.
Como hay interfaces que tienen muchos métodos, y a veces no vamos a querer
implementar todos, Swing nos provee adaptadores de interfaz3, que son clases para las
cuales ya existen los métodos implementados, generalmente vacíos. De esa manera, se
pueden delegar las implementaciones que no se quieran hacer, heredando de la clase
adaptadora. De todas maneras, no todas las interfaces tienen clases adaptadoras.
A continuación se muestra un pequeño programa que responde a tres tipos de
eventos para el mismo botón:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Hola6 extends JApplet {
BotonHola boton = new BotonHola("Presione");
JTextField texto = new JTextField(4);
// clase interna a Hola6:
class BotonHola extends JButton {
public BotonHola (String leyenda) {
super(leyenda);
setBackground(Color.green);
addFocusListener(f);
addMouseListener(m);
3
Como WindowAdapter, MouseAdapter, etc..
12
}
// un atributo de una clase interna anónima:
FocusListener f = new FocusListener() {
public void focusGained(FocusEvent e) {
boton.setBackground(Color.red);
}
public void focusLost(FocusEvent e) {
boton.setBackground(Color.green);
}
};
// un atributo de una clase interna anónima:
MouseListener m = new MouseListener() {
public void mouseEntered(MouseEvent e) {
boton.setBackground(Color.red);
}
public void mouseExited(MouseEvent e) {
boton.setBackground(Color.green);
}
public void mousePressed(MouseEvent e) {
texto.setText(“Hola a todos”);
}
public void mouseReleased(MouseEvent e) {
boton.setBackground(Color.green);
}
public void mouseClicked(MouseEvent e) {
texto.setText(“Hola a todos”);
}
};
}
public void init() {
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(boton);
cp.add(texto);
}
}
Generalización del concepto de eventos
El concepto de evento está tan asociado a las interfaces de usuario gráficas que
todas las implementaciones RAD permiten usar eventos predefinidos para los
componentes visuales. Sin embargo, como un lenguaje orientado a objetos debiera
promover la reutilización y extensión, los auténticos lenguajes de POO deberían poder
definir nuevos eventos, incluso sin estar asociados a un componente visual. Esto es lo que
ocurre en la biblioteca Swing de Java.
La idea de una generalización del concepto de evento es extenderlo a cualquier
sistema, no sólo al entorno visual y la interacción con el usuario.
De este modo, por ejemplo, podríamos denominar eventos a los siguientes
escenarios:
• El vuelo AR1023 ha despegado.
13
•
•
Se ha detectado humo en el sensor número 2.
Se ha retirado un objeto del arreglo v.
Ocurre que el concepto de evento no está limitado a la representación de cambios debidos a acciones directas del usuario. La programación guiada por eventos
responde al paradigma de suscriptor y distribuidor, por lo que todo problema al que le sea
aplicable dicho paradigma se podrá implementar con eventos.
En el paradigma de suscriptor y distribuidor hay un objeto controlador que se
ocupa de escuchar eventos de distintas fuentes y enviarlos a los suscriptores interesados.
Para que el controlador sepa qué objetos están interesados en cada evento, cada
observador se tiene que suscribir o registrar para indicar qué eventos le interesan. Una
vez que se produce el evento, el controlador que estaba escuchando llama a los métodos
(esto se conoce como callback) que correspondan en los objetos interesados.
Java sólo permite definir eventos para componentes. Sin embargo, esto no es
una limitación, sino que habrá que hacer componentes de nuestras clases generadoras de
eventos.
Las propiedades de los componentes JavaBeans pueden notificar a otros objetos
el hecho de que han cambiado. Ante un cambio, lanzan un evento de la clase
PropertyChangeEvent, que puede ser capturado por un objeto que implemente la interfaz
PropertyChangeListener.
Por lo tanto, para poder implementar este modelo, habrá que:
• Tener un componente JavaBeans cuyos cambios de propiedades interese
notificar.
• Implementar objetos que registren su interés en recibir los eventos enviados
por los cambios en propiedades del componente.
• Disparar los eventos de cambio de propiedades en el componente hacia los
objetos observadores.
El componente debe implementar los métodos addPropertyChangeListener,
removePropertyChangeListener y firePropertyChange. Para la implementación de los
dos primeros, podemos delegar la tarea en la clase predefinida PropertyChangeSupport,
que se ocupa de mantener la lista de observadores. En este último caso, debemos incluir
un objeto de esta clase como atributo, que además nos va a servir para que se utilice el
método firePropertyChange.
Por su lado, los observadores deben implementar la interfaz PropertyChangeListener, que expone el método propertyChange, y utilizarlo para programar la acción a
seguir cuando se recibe el evento.
Incluso existe la posibilidad de que un objeto decida evitar un cambio en una
propiedad, elevando una excepción PropertyVetoException, que provoca que la
propiedad vuelva al estado inicial.
Al final del capítulo hicimos un ejercicio que usa estas facilidades.
HILOS DE CIERRE
Una aplicación Java puede terminar por muchas razones. La más simple es la
salida normal que se produce una vez que terminó de ejecutarse el último hilo. Otra
posibilidad es la llamada al método System.exit desde algún punto del programa. Pero
14
también puede terminar por decisión del usuario, que la puede interrumpir bruscamente,
por una excepción no capturada u otras finalizaciones menos ortodoxas.
Ahora bien, en cualquiera de estos casos, la terminación de la aplicación implica la finalización de una instancia de la JVM. Por eso, se pueden definir ciertos hilos que
se ejecuten antes de terminar con dicha instancia, lo que hará más segura la terminación
de un programa: se podrán cerrar conexiones de red, vínculos con bases de datos, persistir
algunos objetos, etc..
Hay dos métodos para administrar estos hilos: Runtime.addShutdownHook y
Runtime.removeShutdownHook, ambos con un parámetro de tipo Thread.
EJERCICIO: EVENTOS DEFINIDOS POR EL PROGRAMADOR
Enunciado:
Implementar un simulador de horno a microondas trabajando con eventos de
notificación de cambios. Implementar también una clase de alarma, que responda al
evento de finalización de la cocción.
Solución:
// archivo Microondas2.java
package carlosfontela.varios;
import java.util.*;
import java.beans.*;
public class Microondas2 extends Observable {
private int tiempo;
// en segundos
private int tiempoRestante;
// en segundos
private int potencia; // en Watt
private boolean andando;
private long horaInicio; // en milisegundos desde 1/1/1970
private Timer t;
private ObservacionTiempo observacionTiempo;
private PropertyChangeSupport cambio;
// una clase interna derivada de TimerTask:
class ObservacionTiempo extends TimerTask {
public void run() {
// tiempo en ms desde 1/1/1970:
long horaActual = System.currentTimeMillis();
long falta = horaActual - horaInicio;
if (falta > 0)
tiempoRestante = (int)(falta / 1000);
else {
andando = false;
tiempoRestante = 0;
observacionTiempo.cancel();
setChanged();
notifyObservers();
cambio.firePropertyChange("andando",
new Boolean(true), new Boolean(false));
15
}
}
}
public Microondas2() {
tiempo = 0;
potencia = 0;
andando = false;
horaInicio = 0;
t = new Timer();
cambio = new PropertyChangeSupport(this);
}
public int getTiempo() {
return tiempo;
}
public int getPotencia() {
return potencia;
}
public void setTiempo (int t) {
tiempo = t;
}
public void setPotencia (int p) {
potencia = p;
}
public void incrementarPotencia() {
potencia += 100;
}
public void decrementarPotencia() {
potencia -= 100;
}
public void incrementarTiempo() {
tiempo += 30;
}
public void decrementarTiempo() {
tiempo -= 30;
}
public void inicioCoccion() {
andando = true;
horaInicio = System.currentTimeMillis(); // tiempo en ms desde 1/1/1970
observacionTiempo = new ObservacionTiempo();
t.scheduleAtFixedRate(observacionTiempo,0,1000);
}
public int getTiempoRestante() {
return tiempoRestante;
}
public void detenerCoccion() {
16
}
observacionTiempo.cancel();
andando = false;
tiempoRestante = 0;
public boolean isAndando() {
return andando;
}
public void addPropertyChangeListener(PropertyChangeListener o) {
cambio.addPropertyChangeListener(o);
}
public void removePropertyChangeListener(PropertyChangeListener o) {
cambio.removePropertyChangeListener(o);
}
}
// archivo Alarma.java
package carlosfontela.varios;
import java.beans.*;
import java.util.*;
import carlosfontela.basico.Beep;
public class Alarma implements PropertyChangeListener {
private Microondas2 observado;
public void pitido() {
new Beep(2);
}
public void setObservado(Microondas2 obs) {
observado = obs;
observado.addPropertyChangeListener(this);
}
public Microondas2 getObservado() {
return observado;
}
public void propertyChange (PropertyChangeEvent e) {
pitido();
}
}