Interfaces e o mecanismo de callback em Java
Um dos usos mais comuns de interfaces em Java está na implementação de callbacks. O conceito de callback consiste em criar ações que são executadas em resposta a outras ações. Em outras palavras, determinadas ações disparam automaticamente a execução de outras.
Um exemplo clássico é o clique em um botão. Ao pressionar um botão, uma ação é realizada, e em resposta a esse clique outras ações são executadas. Por exemplo, clicar no ícone de uma impressora pode iniciar o processo de impressão de um documento.
Considere o exemplo a seguir:
public class EventsApp {
public static void main(String[] args) {
Button button = new Button(new ButtonClickHandler());
button.click();
button.click();
button.click();
}
}
class ButtonClickHandler implements EventHandler {
public void execute() {
System.out.println("Botão pressionado!");
}
}
interface EventHandler {
void execute();
}
class Button {
EventHandler handler;
Button(EventHandler action) {
this.handler = action;
}
public void click() {
handler.execute();
}
}
Nesse código, a classe Button
possui um construtor que recebe um objeto que implementa a interface EventHandler
.
O método click
, que simula o clique no botão, chama o método execute
desse objeto.
A implementação de EventHandler
é feita pela classe ButtonClickHandler
, e o objeto dessa classe é passado para o construtor de Button
no programa principal. Assim, o construtor define qual será o manipulador de eventos para aquele botão. Cada vez que button.click()
é chamado, o manipulador configurado é executado.
A saída do programa será:
Botão pressionado! Botão pressionado! Botão pressionado!
Surge a questão: por que definir um manipulador de eventos em uma interface e implementá-lo separadamente, em vez de escrever o comportamento diretamente na classe Button
?
class Button {
public void click() {
System.out.println("Botão pressionado!");
}
}
O motivo é que, no momento da definição de Button
, nem sempre é possível saber exatamente quais ações devem ocorrer ao clicar no botão. Isso é especialmente relevante quando Button
e a aplicação principal pertencem a pacotes ou bibliotecas diferentes e são desenvolvidos por equipes distintas.
Além disso, podem existir múltiplos botões (Button
) na aplicação, cada um executando ações diferentes. Nesse caso, cada instância precisa ter seu próprio comportamento associado.
Veja um exemplo em que o programa principal define dois botões distintos:
public class EventsApp {
public static void main(String[] args) {
Button tvButton = new Button(new EventHandler() {
private boolean on = false;
public void execute() {
if (on) {
System.out.println("Televisor desligado.");
on = false;
} else {
System.out.println("Televisor ligado!");
on = true;
}
}
});
Button printButton = new Button(new EventHandler() {
public void execute() {
System.out.println("Impressão iniciada...");
}
});
tvButton.click();
printButton.click();
tvButton.click();
}
}
Nesse exemplo, há dois botões: um para alternar o estado de um televisor e outro para iniciar a impressão em uma impressora.
Os manipuladores de eventos são definidos diretamente no código como objetos anônimos que implementam EventHandler
.
No caso do botão da televisão, o manipulador mantém um estado interno (on
) para controlar se o aparelho está ligado ou desligado.
A saída será:
Televisor ligado! Impressão iniciada... Televisor desligado.
Interfaces nesse contexto são amplamente utilizadas em APIs gráficas como AWT, Swing e JavaFX, onde a manipulação de eventos de elementos da interface gráfica é uma necessidade recorrente.