ОК, чтобы добавить это в качестве слушателя внутри конструктора конечного класса, если он однопоточный? - PullRequest
1 голос
/ 12 апреля 2019

Я унаследовал большое приложение Swing.Рассмотрим версию этого главного окна графического интерфейса этого класса:

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

final class ListenerExample1 extends JFrame implements ActionListener {

    private final JButton button = new JButton("Click me");
    private int clickCount = 0;

    public static void main(String... args) throws Exception {
        SwingUtilities.invokeAndWait( () -> {
            ListenerExample1 instance = new ListenerExample1();
            instance.setVisible(true);
            instance.pack();
        } );
    }

    ListenerExample1() throws IllegalStateException {
        if (!EventQueue.isDispatchThread()) throw new IllegalStateException("the current thread (" + Thread.currentThread().toString() + ") is not EventQueue's dispatch thread");

        add(button);
        button.addActionListener(this);
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    }

    @Override public void actionPerformed(ActionEvent e) throws IllegalStateException {
        if (!EventQueue.isDispatchThread()) throw new IllegalStateException("the current thread (" + Thread.currentThread().toString() + ") is not EventQueue's dispatch thread");

        markButtonAsClicked();
    }

    void markButtonAsClicked() throws IllegalStateException {
        if (!EventQueue.isDispatchThread()) throw new IllegalStateException("the current thread (" + Thread.currentThread().toString() + ") is not EventQueue's dispatch thread");

        button.setText("click #" + (++clickCount));
    }

}

Этот код сокращен в 100 раз по сравнению с фактическим приложением, чтобы получить Минимальный, Полный и Проверяемый пример .

IntelliJ правильно предупреждает меня, что эта строка

button.addActionListener(this);

приводит к тому, что эта ссылка исчезает во время строительства.

Я полностью осознаю, почему это обычно плохо.Это подробно обсуждается в "Библии", Параллелизм Java на практике .Некоторые специальные статьи Гетца на эту тему: Техника безопасного строительства и Быть хорошим слушателем событий .

Однако действительно ли это плохо в этом?case?

В частности, обратите внимание, что этот класс должен быть затронут только одним потоком.Это из-за правила, согласно которому компоненты Swing должны быть затронуты только потоком диспетчеризации EventQueue («EDT»).Итак, основной метод использует SwingUtilities.invokeAndWait, а все методы конструктора и экземпляра проверяют, является ли вызывающий поток EDT.

Также обратите внимание, что этот класс является окончательным, что исключает проблемы подкласса.

Итак, я думаю, что приведенный выше код действительно безопасен, несмотря на предупреждение IntelliJ, но я хочу знать, пропустил ли я какую-то тонкую проблему.

Есть причины, которые не очевидныв приведенном выше упрощенном коде, почему я хочу добавить это в качестве прослушивателя в несколько конструкторов моего реального приложения.

Говоря о IntelliJ, я отмечаю, что приведенный выше код можно упростить.Вместо того, чтобы класс реализовывал ActionListener, который, следовательно, без веских причин предоставляет метод actionPerformed, вы можете использовать лямбду для неявного создания внутреннего класса экземпляра:

import java.awt.EventQueue;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

final class ListenerExample2 extends JFrame {

    private final JButton button = new JButton("Click me");
    private int clickCount = 0;

    public static void main(String... args) throws Exception {
        SwingUtilities.invokeAndWait( () -> {
            ListenerExample2 instance = new ListenerExample2();
            instance.setVisible(true);
            instance.pack();
        } );
    }

    ListenerExample2() throws IllegalStateException {
        if (!EventQueue.isDispatchThread()) throw new IllegalStateException("the current thread (" + Thread.currentThread().toString() + ") is not EventQueue's dispatch thread");

        add(button);
        button.addActionListener( (e) -> markButtonAsClicked() );
        setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    }

    void markButtonAsClicked() throws IllegalStateException {
        if (!EventQueue.isDispatchThread()) throw new IllegalStateException("the current thread (" + Thread.currentThread().toString() + ") is not EventQueue's dispatch thread");

        button.setText("click #" + (++clickCount));
    }

}

К моему удивлению, IntelliJ не предупреждает, чтоэта новая лямбда-линия

button.addActionListener( (e) -> markButtonAsClicked() );

снова вызывает эту ссылку во время строительства - и эта строка наверняка пропускает эту ссылку!

Это простодефект в коде проверки IntelliJ?

Или есть какая-то тонкая причина, почему лямбда-код на самом деле безопасен в целом?(Этот новый лямбда-код должен быть безопасным в данном конкретном случае по тем же причинам, которые обсуждались выше для исходной версии.)

...