1. Introducción muy breve a Swing y AWT
Si Java fuera un coche, Swing y AWT — serían su «salpicadero» y «volante». AWT (Abstract Window Toolkit) — la primera biblioteca para crear aplicaciones de escritorio en Java. Utiliza componentes «nativos» del sistema operativo, por lo que se ve diferente en Windows, Linux y Mac.
Swing — una capa más moderna sobre AWT, escrita íntegramente en Java. Swing es más bonito, flexible, admite un montón de funciones adicionales y se ve igual en todas las plataformas (bueno, casi). En ambos casos, todos los elementos de la interfaz (botones, campos de texto, casillas de verificación, etc.) admiten trabajo con eventos.
En esta lección usaremos Swing: es más sencillo para principiantes y aparece más a menudo en ejemplos didácticos. Vamos a ver cómo está organizado el manejo de eventos con el ejemplo más popular — la reacción a pulsar un botón.
Creación de un botón
En Swing, un botón se crea así:
JButton button = new JButton("¡Púlsame!");
Aquí, «¡Púlsame!» — es el texto del botón.
Añadir un oyente
Para reaccionar a la pulsación, hay que «registrar» un oyente para el evento:
button.addActionListener(new MyActionListener());
¿Pero qué es ese MyActionListener? Es una clase que implementa la interfaz ActionListener:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class MyActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("¡Se pulsó el botón!");
}
}
Cuando el usuario pulsa el botón, Swing llama al método actionPerformed de todos los oyentes registrados con addActionListener.
Ejemplo completo: una ventana sencilla con un botón
Vamos a reunirlo todo y crear una aplicación funcional:
import javax.swing.*;
import java.awt.event.*;
public class SimpleButtonApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Ejemplo de evento");
JButton button = new JButton("¡Púlsame!");
// Añadimos un oyente al botón
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("¡Se pulsó el botón!");
JOptionPane.showMessageDialog(frame, "¡Hurra! ¡Botón pulsado!");
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(button);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Qué ocurre aquí:
- Creamos una ventana (JFrame).
- Creamos un botón (JButton).
- Añadimos un oyente (clase anónima, para no crear archivos aparte).
- Dentro de actionPerformed mostramos un mensaje en la consola y una ventana emergente (JOptionPane).
- Añadimos el botón a la ventana y mostramos la ventana al usuario.
Prueba tú mismo a copiar este código, ejecutarlo — y pulsar el botón.
2. Clases anónimas y expresiones lambda
En el código Java moderno, escribir clases separadas por un solo manejador — es como ir en un tanque a por el pan. Es mucho más cómodo usar clases anónimas o expresiones lambda.
Clase anónima
Ya vimos este enfoque arriba:
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// código de manejo
}
});
Aquí la clase se declara en el sitio, sin nombre.
Expresión lambda (Java 8+)
Si la interfaz del oyente es funcional (es decir, contiene un único método abstracto), puedes usar una lambda:
button.addActionListener(e -> {
System.out.println("¡Botón pulsado mediante lambda!");
});
O incluso más corto si el cuerpo es de una sola línea:
button.addActionListener(e -> System.out.println("Lambda: ¡botón pulsado!"));
Esto no solo reduce el código, sino que lo hace más legible. Dato para curiosos: la interfaz ActionListener es funcional porque tiene un único método actionPerformed.
4. Otros tipos de eventos
¡El mundo no se limita a los botones! En Swing y AWT casi cada elemento de la interfaz admite sus propios eventos. Estos son los más populares:
Eventos del ratón: MouseListener y MouseAdapter
Si quieres reaccionar a los clics del ratón, utiliza MouseListener:
button.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("¡Clic del ratón en el botón!");
}
// Los demás métodos pueden dejarse vacíos
@Override public void mousePressed(MouseEvent e) {}
@Override public void mouseReleased(MouseEvent e) {}
@Override public void mouseEntered(MouseEvent e) {}
@Override public void mouseExited(MouseEvent e) {}
});
Escribir cinco métodos vacíos por un solo manejador no es muy cómodo. Para eso está MouseAdapter:
button.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("¡Clic del ratón en el botón (mediante adaptador)!");
}
});
MouseAdapter implementa todos los métodos de la interfaz y tú solo sobreescribes los necesarios.
Eventos de teclado: KeyListener/KeyAdapter
Si necesitas captar pulsaciones de teclas:
button.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
System.out.println("Tecla pulsada: " + e.getKeyChar());
}
});
Eventos de cambio de texto: DocumentListener
Para campos de texto (JTextField, JTextArea) hay oyentes específicos:
JTextField textField = new JTextField();
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
System.out.println("Texto modificado: " + textField.getText());
}
@Override public void removeUpdate(DocumentEvent e) {}
@Override public void changedUpdate(DocumentEvent e) {}
});
Por cierto, para la mayoría de eventos hay adaptadores para no tener que escribir métodos vacíos.
5. Práctica: mini-ejemplo con ventana y botón
Hagamos una aplicación GUI sencillísima: una ventana con un botón que, al pulsarlo, incrementa un contador y muestra un mensaje.
Ejemplo: contador de pulsaciones
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ClickCounterApp {
public static void main(String[] args) {
JFrame frame = new JFrame("Contador de pulsaciones");
JButton button = new JButton("¡Púlsame!");
JLabel label = new JLabel("Pulsaciones: 0");
// Contador de pulsaciones (debe ser final o efectivamente final)
final int[] count = {0};
button.addActionListener(e -> {
count[0]++;
label.setText("Pulsaciones: " + count[0]);
});
frame.setLayout(new FlowLayout());
frame.add(button);
frame.add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(250, 100);
frame.setVisible(true);
}
}
Explicación:
- Creamos una ventana, un botón y una etiqueta para mostrar el contador.
- Usamos un array de un solo elemento (final int[] count = {0};) para sortear la restricción de las lambdas sobre variables final/efectivamente final.
- Cada vez que se pulsa el botón, incrementamos el contador y actualizamos el texto de la etiqueta.
Idea para practicar: ¡intenta cambiar el color del botón en cada quinta pulsación!
6. Cómo funciona el modelo de eventos por dentro
Echemos un vistazo bajo el capó un momento. Cuando llamas a:
button.addActionListener(listener);
el botón (objeto JButton) añade tu oyente a una lista interna. Cuando el usuario pulsa el botón, por dentro ocurre lo siguiente:
- El botón crea un objeto de evento (ActionEvent).
- El botón recorre la lista de oyentes y llama al método actionPerformed de cada uno.
- Tu manejador realiza las acciones necesarias (por ejemplo, actualiza la etiqueta).
Lo principal a recordar: cada evento puede tener cualquier número de oyentes — puedes añadir varios manejadores para un mismo botón; se llamarán uno detrás de otro.
7. Esquema visual: procesamiento del evento
graph TD
A[El usuario pulsó el botón] --> B{JButton}
B --> C[Crear ActionEvent]
C --> D[Llamar a actionPerformed de todos los oyentes]
D --> E[El manejador ejecuta las acciones]
7. Errores comunes al trabajar con eventos en Swing y AWT
Error n.º 1: olvidaste registrar el oyente. El botón no reacciona a las pulsaciones porque olvidaste llamar a addActionListener.
Error n.º 2: trabajo pesado en el manejador. Si en el manejador del evento inicias un bucle largo o descargas algo de Internet, la interfaz «se quedará colgada». Para tareas largas usa hilos separados o SwingWorker.
Error n.º 3: intentar modificar una variable desde una lambda sin ser final/efectivamente final. Dentro de una lambda solo se pueden usar variables final o «efectivamente final». Para contadores usa arrays o clases específicas (AtomicInteger, si quieres ir en serio).
Error n.º 4: olvidaste eliminar el oyente. Si eliminas un componente pero no eliminaste el oyente, es posible una fuga de memoria. Cancela las suscripciones cuando el componente ya no sea necesario.
Error n.º 5: no se han implementado todos los métodos de la interfaz. Si implementas, por ejemplo, MouseListener directamente, ¡no olvides todos los métodos! Mejor usa adaptadores (MouseAdapter, KeyAdapter).
GO TO FULL VERSION