是否可以在单线程的情况下将其添加为最终类的构造函数中的侦听器?

时间:2019-04-12 17:44:39

标签: java constructor this listener final

我继承了一个大型的Swing应用程序。考虑一下它的GUI主窗口类的基本版本:

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次,以实现Minimal, Complete, and Verifiable example

IntelliJ正确警告我这一行

button.addActionListener(this);

导致此引用在构造过程中转义。

我完全知道为什么这通常是一件坏事。 《圣经》 Java Concurrency In Practice中对此进行了详细讨论。格茨(Goetz)关于这个主题的一些专门文章是Safe construction techniquesBe a good (event) listener

但是,在这种情况下,实际上是不好的吗?

尤其要注意,此类仅应被单个线程接触。那是因为Swing components must only be touched by EventQueue's dispatch thread(“ EDT”)的规则。因此,main方法使用SwingUtilities.invokeAndWait,构造函数和实例方法都检查调用线程是否为EDT。

还要注意,该类是最终的,排除了子类的问题。

因此,尽管IntelliJ发出警告,我认为上面的代码实际上是安全的,但我想知道是否忽略了一些细微的问题。

有一些原因(在上面的简化代码中并不明显),为什么我要将其作为侦听器添加到实际应用程序的多个构造函数中。

说到IntelliJ,我注意到上面的代码可以简化。您可以使用lambda隐式创建实例内部类,而不必让类实现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并没有警告这一新的lambda行

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

再次导致this引用在构造过程中转义-并且这行肯定会确实使this转义!

这仅仅是IntelliJ检查代码中的缺陷吗?

还是有一些微妙的原因使lambda代码实际上总体上是安全的? (出于与上述原始版本相同的原因,这种新的lambda代码在这种情况下应该是安全的。)

0 个答案:

没有答案