Olay güdümlü programlama: listener, anonim sınıf, lambda
Butona tıklanınca ne olur: olay kaynağı, listener arayüzü, kayıt, getSource ve handler'ı iç sınıf, anonim sınıf ya da lambda ile yazmak.
Şimdiye kadar yazdığın programlar baştan sona sırayla çalışıyordu. GUI programı ise pencereyi açtıktan sonra bekler: kullanıcı bir butona tıklayınca, bir tuşa basınca ya da fareyi oynatınca ilgili kod çalışır. Buna olay güdümlü programlama (event-driven programming) denir. Sınavda bu konudan “handler yaz” soruları, anonim sınıfı lambdaya çevirme ve derleme hataları (effectively final, eksik public, yanlış imza) gelir.
Altın kurallar:
- Olay kaynağı, olay nesnesi, dinleyici. Butona tıklanınca Swing bir
ActionEventyaratır ve butona kayıtlı herActionListener’ınactionPerformedmetodunu çağırır. - Kayıt şart. Listener yazıp
addActionListenerile bağlamazsan hiçbir şey olmaz, hata da vermez. - Lambda sadece tek metotlu arayüzle. ActionListener evet, MouseListener hayır.
- Lambda ve anonim sınıf yerel değişkeni değiştiremez. Değişecek her şeyi alan yap.
Olay modeli
| Parça | Görevi | Örnek |
|---|---|---|
| Olay kaynağı (event source) | Olayı üreten bileşen | JButton, JTextField (Enter), JCheckBox |
| Olay nesnesi (event object) | Ne olduğunu taşıyan nesne | ActionEvent, ItemEvent, MouseEvent, KeyEvent |
| Dinleyici (event listener) | Olaya tepki veren nesne | ActionListener, ItemListener, MouseListener |
| Olay işleyici (event handler) | Dinleyicinin çağrılan metodu | actionPerformed(ActionEvent e) |
Kullanacağın listener’lar:
| Arayüz | Ne zaman? | Metot(lar) | Kayıt |
|---|---|---|---|
ActionListener | Buton tıklandı, metin kutusunda Enter | actionPerformed(ActionEvent) | addActionListener |
ItemListener | Checkbox / radio seçildi ya da kaldırıldı | itemStateChanged(ItemEvent) | addItemListener |
MouseListener | Tıklama, basma, bırakma, giriş, çıkış | 5 metot | addMouseListener |
MouseMotionListener | Fare hareketi, sürükleme | 2 metot | addMouseMotionListener |
KeyListener | Tuşa basma | 3 metot | addKeyListener |
Hepsi java.awt.event paketinde: import java.awt.event.*; yazmayı unutma.
Handler yazmanın dört yolu
1. İç sınıf (inner class)
Bir sınıfın içinde tanımlanan, static olmayan sınıf. En büyük avantajı: dış nesnenin private alanlarına doğrudan erişir.
public class CounterPanel extends JPanel {
private int count = 0;
private final JButton button = new JButton("Click");
private final JLabel label = new JLabel("Count: 0");
public CounterPanel() {
add(button);
add(label);
button.addActionListener(new ClickHandler()); // kayıt
}
private class ClickHandler implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
count++; // dış sınıfın alanı
label.setText("Count: " + count);
}
}
}
Üç tıklamadan sonra:
Count: 3
2. Sınıfın kendisi listener olsun
Sınıf implements ActionListener der ve addActionListener(this) ile kendini kaydeder. Birden fazla buton aynı metodu paylaşır; hangisinin tıklandığını e.getSource() söyler.
public class CalcPanel extends JPanel implements ActionListener {
private final JTextField a = new JTextField("8", 4), b = new JTextField("2", 4);
private final JButton plus = new JButton("+"), div = new JButton("/");
private final JLabel result = new JLabel("=");
public CalcPanel() {
add(a); add(b); add(plus); add(div); add(result);
plus.addActionListener(this);
div.addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
int x = Integer.parseInt(a.getText());
int y = Integer.parseInt(b.getText());
if (e.getSource() == plus) result.setText("= " + (x + y));
else if (e.getSource() == div) result.setText("= " + (x / y));
System.out.println("command=" + e.getActionCommand() + " " + result.getText());
}
}
+ sonra / tıklanınca, ardından b “0” yapılıp / tekrar tıklanınca:
command=+ = 10
command=/ = 4
ArithmeticException: / by zero
Son satırı handler basmadı: handler içinde yakalanmayan istisna dışarı çıktı, denemede onu doClick() çağrısının etrafındaki try/catch yakaladı. Gerçek pencerede bu istisna Swing’in olay iş parçacığına çıkar ve konsola stack trace basılır. Bu yüzden handler içinde try/catch kullanırsın (aşağıdaki TemperatureFrame örneğine bak).
3. Anonim iç sınıf (anonymous inner class)
Adı olmayan, yazıldığı yerde tek bir nesnesi yaratılan sınıf. new ArayuzAdi() { ...metotlar... } biçimindedir. Arayüzden new yapılamaz ama gövde eklersen o gövde, arayüzü uygulayan adsız bir sınıf olur.
JButton hello = new JButton("Hello");
JLabel msg = new JLabel();
hello.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
msg.setText("Hi, " + e.getActionCommand());
}
});
Tıklanınca msg.getText():
Hi, Hello
4. Lambda ifadesi
Tek abstract metodu olan arayüze fonksiyonel arayüz (functional interface) denir. ActionListener öyle. Bu durumda anonim sınıfın bütün kalıbı yerine sadece parametre ve gövde yazarsın:
hello.addActionListener(e -> msg.setText("Hi, " + e.getActionCommand()));
| Anonim sınıf | Lambda |
|---|---|
new ActionListener() { | (yok) |
public void actionPerformed(ActionEvent e) { | e -> |
msg.setText(...); | msg.setText(...) |
} }); | ); |
Gövde birden fazla satırsa süslü parantez kullanılır: e -> { satır1; satır2; }.
Yakalanan değişkenler ve this
Anonim sınıf ve lambda, dışarıdaki yerel değişkenleri kullanabilir ama değiştiremez. Kullandıkları yerel değişken final ya da effectively final (hiç yeniden atanmayan) olmalı.
int count = 0;
b.addActionListener(e -> count++); // DERLENMEZ
local variables referenced from a lambda expression must be final or effectively final. Alanlar (instance variables) bu kurala tabi değil; sayaçlar, toplamlar ve durum bilgisi için alan kullan.
this ikisinde farklı anlam taşır:
| Anonim sınıf | Lambda | |
|---|---|---|
this | Anonim sınıfın nesnesi | Dıştaki nesne |
| Dış nesneye erişim | DisSinif.this | Doğrudan this |
Outer.wire() içinde, Outer.toString() = "Outer":
lambda: this → Outer
anonim: this.toString().equals("Outer") → false
anonim: Outer.this → Outer
Fare ve adapter sınıfları
MouseListener’ın beş metodu var. Sadece tıklamayla ilgileniyorsan beşini de yazmak zorunda kalmamak için Java hazır adapter sınıfları verir: MouseAdapter, KeyAdapter, WindowAdapter. Bunlar arayüzün bütün metotlarını boş gövdeyle uygular; sen sadece istediğini override edersin. Adapter bir sınıf olduğu için lambda değil anonim sınıfla kullanılır.
JPanel area = new JPanel();
area.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
System.out.println("clicked at " + e.getX() + "," + e.getY());
}
});
(40, 25) noktasına tıklanınca:
clicked at 40,25
Checkbox ve diğer olaylar
JCheckBox ve JRadioButton seçim değişince ItemEvent üretir. e.getStateChange() == ItemEvent.SELECTED ile işaretlenip işaretlenmediğini öğrenirsin; ya da doğrudan checkBox.isSelected().
JCheckBox bold = new JCheckBox("Bold");
bold.addItemListener(e -> System.out.println("bold=" + (e.getStateChange() == ItemEvent.SELECTED)));
// iki kez tıklanır
bold=true
bold=false
Diğer yararlı metotlar:
button.setEnabled(false): bileşen gri olur, olay üretmez. Denemede devre dışı butonadoClick()listener’ı çalıştırmadı.button.removeActionListener(l): kaydı siler. Bunun için listener’ı bir değişkende tutman gerekir.textField.addActionListener(...): metin kutusunda Enter’a basılınca çalışır.
TUZAK · birden fazla listener’ın sırası. Bir butona iki listener kaydedersen ikisi de çalışır. Hangi sırayla çağrılacakları Java belgelerinde garanti edilmez. Denemede (JDK 26) son kaydedilen önce çalıştı:
second registered, sonrafirst registered. Sınavda sıraya bağlı kod yazma; iki işi tek handler’da sırayla yap.
Bir pencereyi baştan sona kurmak
Geçen haftanın TemperatureFrame örneğine olay ekleyelim. Sıra: bileşenleri alan olarak tanımla, constructor’da yerleştir, en sonda listener’ı bağla.
public class TemperatureFrame extends JFrame {
private final JTextField celsiusField = new JTextField(6);
private final JButton convertButton = new JButton("Convert");
private final JLabel resultLabel = new JLabel("F: ?");
public TemperatureFrame() {
super("C to F");
setLayout(new FlowLayout());
add(new JLabel("Celsius:"));
add(celsiusField);
add(convertButton);
add(resultLabel);
convertButton.addActionListener(e -> {
try {
double c = Double.parseDouble(celsiusField.getText().trim());
double f = c * 9 / 5 + 32;
resultLabel.setText("F: " + f);
} catch (NumberFormatException ex) {
resultLabel.setText("Invalid number");
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 100);
}
}
Girilen değer “100”, sonra ” 37 ”, sonra “abc” iken Convert’e basılınca etiket:
F: 212.0
F: 98.6
Invalid number
Lambda parametresi e, catch’teki değişken ex oldu. Aynı adı (e) kullansaydın derleme hatası alırdın: lambda parametresi o kapsamda zaten tanımlı.
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Listener sınıfını yazdım, buton çalışır | addActionListener ile kaydetmezsen hiçbir şey olmaz |
actionPerformed() parametresiz yazılabilir | İmza actionPerformed(ActionEvent e), ve public |
new ActionListener() | Arayüz; gövde ekle (anonim) ya da lambda yaz |
Lambda içinde count++ (yerel) | Effectively final kuralı; alan kullan |
| Her listener lambda olur | Sadece tek metotlu arayüzler; MouseListener için MouseAdapter |
Anonim sınıfta this dış nesnedir | Anonim nesnedir; dış nesne Dis.this |
actionPerformed’ı ben çağırırım | Swing çağırır, sen sadece kaydedersin |
e.getSource().equals("+") | getSource bir nesne döner: e.getSource() == plus ya da getActionCommand() |
Lambda parametresi e, catch’te de e | Aynı kapsamda iki e olmaz; catch’e ex de |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is printed after the button is clicked three times?
class CounterPanel extends JPanel {
private int count = 0;
private JButton button = new JButton("Click");
private JLabel label = new JLabel("Count: 0");
CounterPanel() {
add(button); add(label);
button.addActionListener(new ClickHandler());
}
private class ClickHandler implements ActionListener {
public void actionPerformed(ActionEvent e) {
count++;
label.setText("Count: " + count);
}
}
}
// after 3 clicks: System.out.println(label.getText());Cevabı göster
ClickHandler bir iç sınıf (inner class); dış nesnenin count ve label alanlarına doğrudan erişir. Her tıklamada actionPerformed bir kez çalışır: count 1, 2, 3 olur, etiket "Count: 3".
Find the compile error.
JButton b = new JButton("+1");
int count = 0;
b.addActionListener(e -> count++);Cevabı göster
Lambda ve anonim sınıflar dışarıdaki yerel değişkenleri sadece okuyabilir, ve o değişken final ya da effectively final olmalı. javac: local variables referenced from a lambda expression must be final or effectively final. Çözüm: sayacı sınıfın alanı yap (private int count;), alanlar bu kurala tabi değil.
Find the compile error.
class Handler implements ActionListener {
public void actionPerformed() {
System.out.println("clicked");
}
}Cevabı göster
Arayüzdeki metot void actionPerformed(ActionEvent e). Parametresiz sürüm ayrı bir metot (overload) olur, arayüzün metodu yazılmamış kalır. javac: Handler is not abstract and does not override abstract method actionPerformed(ActionEvent) in ActionListener.
Find the compile error.
JPanel p = new JPanel();
p.addMouseListener(e -> System.out.println("click"));Cevabı göster
Lambda sadece tek abstract metotlu arayüzlerle (functional interface) çalışır. MouseListener'da beş metot var (mouseClicked, mousePressed, mouseReleased, mouseEntered, mouseExited). javac: MouseListener is not a functional interface. Çözüm: new MouseAdapter() { public void mouseClicked(MouseEvent e) { ... } }.
What is printed?
// a = "8", b = "2"; both buttons call this same handler
public void actionPerformed(ActionEvent e) {
int x = Integer.parseInt(a.getText());
int y = Integer.parseInt(b.getText());
if (e.getSource() == plus) result.setText("= " + (x + y));
else if (e.getSource() == div) result.setText("= " + (x / y));
System.out.println("command=" + e.getActionCommand() + " " + result.getText());
}
// user clicks "+" then "/"Cevabı göster
getSource() olayı tetikleyen nesneyi döner, == ile hangi butonun olduğunu ayırırsın. getActionCommand() varsayılan olarak butonun yazısıdır. +: 8 + 2 = 10. /: 8 / 2 = 4 (int bölme). b "0" olsaydı / butonu ArithmeticException: / by zero fırlatırdı.
Find the compile error.
JButton b = new JButton("ok");
b.addActionListener(new ActionListener());Cevabı göster
javac: ActionListener is abstract; cannot be instantiated. Ya bir sınıf yazıp onu yaratırsın, ya anonim sınıf gövdesi eklersin (new ActionListener() { public void actionPerformed(ActionEvent e) { ... } }), ya da lambda kullanırsın.
Rewrite this anonymous class as a lambda expression.
hello.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
msg.setText("Hi, " + e.getActionCommand());
}
});Cevabı göster
ActionListener'ın tek metodu var, bu yüzden lambda yazılabilir. Parametre listesi (e), ok, gövde. Tek ifade olduğu için süslü parantez ve return gerekmez. Butona tıklanınca etiket Hi, Hello olur.
Inside an instance method of class Outer (whose toString() returns "Outer"), what does this refer to (a) inside an anonymous ActionListener and (b) inside a lambda listener?
Cevabı göster
Anonim sınıf yeni bir sınıftır, içindeki this o sınıfın nesnesidir; dış nesne için Outer.this yazılır. Lambda yeni bir sınıf açmaz, this dıştaki nesnedir. Denemede lambda this için Outer bastı, anonim sınıfta this.toString().equals("Outer") false çıktı, Outer.this ise Outer bastı.
A button's listener is registered, then button.setEnabled(false); is called and button.doClick(); runs. Is the listener called?
Cevabı göster
Devre dışı (disabled) bileşen olay üretmez; doClick da tıklama yapmaz. Denemede listener'ın mesajı basılmadı, sadece sonraki satır çalıştı. Kullanıcının bir işlemi tekrarlamasını engellemek için kullanılan standart yöntem budur.
Which statement is correct about the three steps of event handling?
- Create the event object, call actionPerformed yourself, then repaint
- Write a class implementing the listener interface, create an object of it, register it with the source using addXxxListener
- Extend JButton and override its click() method
Cevabı göster
Olay nesnesini Swing yaratır ve actionPerformed'ı Swing çağırır; sen çağırmazsın. Senin işin: (1) listener arayüzünü uygulayan bir sınıf (ya da anonim sınıf / lambda), (2) ondan nesne, (3) addActionListener ile kaynağa kaydetmek. Kayıt unutulursa hiçbir şey olmaz, hata da vermez.
Write the event handling for TemperatureFrame (fields celsiusField, convertButton, resultLabel): when Convert is clicked, read the Celsius value, compute F = C * 9 / 5 + 32 and show F: 212.0. If the text is not a number, show Invalid number. Use a lambda.
Cevabı göster
Constructor'ın sonuna eklenir. Alanlara lambda içinden erişmek serbest (sadece yerel değişkenler effectively final olmalı). trim() kenar boşluklarını temizler:
convertButton.addActionListener(e -> {
try {
double c = Double.parseDouble(celsiusField.getText().trim());
double f = c * 9 / 5 + 32;
resultLabel.setText("F: " + f);
} catch (NumberFormatException ex) {
resultLabel.setText("Invalid number");
}
});Denemede: "100" → F: 212.0, " 37 " → F: 98.6, "abc" → Invalid number. c double olduğu için c * 9 / 5 double işlemdir; C int olsaydı 9 / 5 sırası önemli olurdu.Doğrulama: Olaylar pencere açmadan headless modda doClick() ile tetiklendi (java -Djava.awt.headless=true verify/SEN1002/W06.java ve W06b.java). Derleme hataları javac ile denendi (verify/SEN1002/err/E06a-e.java).