我继承了一个大型的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 techniques和Be 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代码在这种情况下应该是安全的。)