SEN1002 · 6. hafta · GUI and Event-driven Programming

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.

event-driven programmingeventevent sourceevent listenerevent handlerActionListenerActionEventactionPerformedaddActionListenerinner classanonymous inner classlambda expressionfunctional interfaceadapter classeffectively final

Ş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:

  1. Olay kaynağı, olay nesnesi, dinleyici. Butona tıklanınca Swing bir ActionEvent yaratır ve butona kayıtlı her ActionListener’ın actionPerformed metodunu çağırır.
  2. Kayıt şart. Listener yazıp addActionListener ile bağlamazsan hiçbir şey olmaz, hata da vermez.
  3. Lambda sadece tek metotlu arayüzle. ActionListener evet, MouseListener hayır.
  4. Lambda ve anonim sınıf yerel değişkeni değiştiremez. Değişecek her şeyi alan yap.

Olay modeli

ParçaGöreviÖrnek
Olay kaynağı (event source)Olayı üreten bileşenJButton, JTextField (Enter), JCheckBox
Olay nesnesi (event object)Ne olduğunu taşıyan nesneActionEvent, ItemEvent, MouseEvent, KeyEvent
Dinleyici (event listener)Olaya tepki veren nesneActionListener, ItemListener, MouseListener
Olay işleyici (event handler)Dinleyicinin çağrılan metoduactionPerformed(ActionEvent e)

Kullanacağın listener’lar:

ArayüzNe zaman?Metot(lar)Kayıt
ActionListenerButon tıklandı, metin kutusunda EnteractionPerformed(ActionEvent)addActionListener
ItemListenerCheckbox / radio seçildi ya da kaldırıldıitemStateChanged(ItemEvent)addItemListener
MouseListenerTıklama, basma, bırakma, giriş, çıkış5 metotaddMouseListener
MouseMotionListenerFare hareketi, sürükleme2 metotaddMouseMotionListener
KeyListenerTuşa basma3 metotaddKeyListener

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ıfLambda
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ıfLambda
thisAnonim sınıfın nesnesiDıştaki nesne
Dış nesneye erişimDisSinif.thisDoğ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ışı butona doClick() 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, sonra first 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üşünceDoğrusu
Listener sınıfını yazdım, buton çalışıraddActionListener 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 olurSadece tek metotlu arayüzler; MouseListener için MouseAdapter
Anonim sınıfta this dış nesnedirAnonim nesnedir; dış nesne Dis.this
actionPerformed’ı ben çağırırımSwing ç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 eAynı kapsamda iki e olmaz; catch’e ex de

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

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
Count: 3

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".

Soru 2orta

Find the compile error.

JButton b = new JButton("+1");
int count = 0;
b.addActionListener(e -> count++);
Cevabı göster
Lambda içinden yerel değişken değiştirilemez (effectively final değil)

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.

Soru 3orta

Find the compile error.

class Handler implements ActionListener {
    public void actionPerformed() {
        System.out.println("clicked");
    }
}
Cevabı göster
actionPerformed(ActionEvent e) parametresi eksik

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.

Soru 4zor

Find the compile error.

JPanel p = new JPanel();
p.addMouseListener(e -> System.out.println("click"));
Cevabı göster
MouseListener fonksiyonel arayüz değil, lambda yazılamaz

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) { ... } }.

Soru 5orta

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
command=+ = 10 / command=/ = 4

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ı.

Soru 6kolay

Find the compile error.

JButton b = new JButton("ok");
b.addActionListener(new ActionListener());
Cevabı göster
ActionListener bir arayüz, new ile doğrudan yaratılamaz

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.

Soru 7kolay

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
hello.addActionListener(e -> msg.setText("Hi, " + e.getActionCommand()));

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.

Soru 8zor

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
(a) anonim sınıfın nesnesi, (b) Outer nesnesi

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ı.

Soru 9orta

A button's listener is registered, then button.setEnabled(false); is called and button.doClick(); runs. Is the listener called?

Cevabı göster
Hayır

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.

Soru 10kolay

Which statement is correct about the three steps of event handling?

  1. Create the event object, call actionPerformed yourself, then repaint
  2. Write a class implementing the listener interface, create an object of it, register it with the source using addXxxListener
  3. Extend JButton and override its click() method
Cevabı göster
Write a class implementing the listener interface, create an object of it, register it with the source using addXxxListener

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.

Soru 11orta

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
convertButton.addActionListener(e -> { try { ... } catch (NumberFormatException ex) { ... } });

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).

Deftere not kartı

Olay güdümlü: program sırayla değil, kullanıcı ne yaparsa ona tepki verir. 3 parça: kaynak (JButton) → olay nesnesi (ActionEvent) → dinleyici (ActionListener). 3 adım: listener yaz → nesnesini yarat → kaynak.addActionListener(listener). ActionListener: tek metot public void actionPerformed(ActionEvent e) (public ŞART). e.getSource() → olayı yapan nesne (== ile karşılaştır). e.getActionCommand() → butonun yazısı. Handler yazmanın 4 yolu: ayrı sınıf, iç sınıf, anonim sınıf, lambda. İç sınıf dış nesnenin private alanlarına erişir. Anonim: new ActionListener() { public void actionPerformed(ActionEvent e) { ... } } Lambda: b.addActionListener(e -> label.setText("hi")); sadece tek metotlu arayüz. Lambda/anonim içinde YEREL değişken: sadece oku, effectively final. Sayaç → alan yap. Anonimde this = anonim nesne (dış için Dis.this). Lambda'da this = dış nesne. MouseListener 5 metot → lambda YOK → MouseAdapter'dan türet, istediğini override et. JCheckBox: ItemListener / ItemEvent.SELECTED. setEnabled(false) → olay gelmez.