我想避免(大部分)Netbeans 6.9.1的警告,我对'Leaking this in constructor'
警告有疑问。
我理解这个问题,在构造函数中调用方法并传递“this
”是危险的,因为“this
”可能尚未完全初始化。
在我的单例类中修复警告很容易,因为构造函数是私有的,只能从同一个类中调用。
旧代码(简化):
private Singleton() {
...
addWindowFocusListener(this);
}
public static Singleton getInstance() {
...
instance = new Singleton();
...
}
新代码(简化):
private Singleton() {
...
}
public static Singleton getInstance() {
...
instance = new Singleton();
addWindowFocusListener( instance );
...
}
如果构造函数是公共的并且可以从其他类调用,则此修复程序无效。如何修复以下代码:
public class MyClass {
...
List<MyClass> instances = new ArrayList<MyClass>();
...
public MyClass() {
...
instances.add(this);
}
}
当然我想要一个修复,它不需要使用这个类修改我的所有代码(例如通过调用init方法)。
答案 0 :(得分:43)
由于你确保将instances.add(this)
放在构造函数的末尾,你应该是安全的,告诉编译器简单地禁止警告 (*)。就其性质而言,警告并不一定意味着出现了问题,只需要您的注意。
如果您知道自己在做什么,可以使用@SuppressWarnings
注释。就像他在评论中提到的Terrel一样,以下注释是从NetBeans 6.9.1开始的:
@SuppressWarnings("LeakingThisInConstructor")
(*)更新:正如Isthar和Sergey所指出的,有些情况下“泄漏”的构造函数代码看起来非常安全(如你的问题所示)但事实并非如此。有更多读者可以批准吗?我正考虑因上述原因删除这个答案。
答案 1 :(得分:35)
[chiccodoro备注:解释为什么/何时泄漏this
会导致问题,即使泄漏语句最后放在构造函数中:]
最终字段语义不同于&#39; normal&#39;字段语义。一个例子,
我们玩网络游戏。让我们让一个Game对象从网络中检索数据,并让Player对象监听游戏中的事件,从而采取相应的行动。游戏对象隐藏了所有网络细节,玩家只对事件感兴趣:
import java.util.*;
import java.util.concurrent.Executors;
public class FinalSemantics {
public interface Listener {
public void someEvent();
}
public static class Player implements Listener {
final String name;
public Player(Game game) {
name = "Player "+System.currentTimeMillis();
game.addListener(this);//Warning leaking 'this'!
}
@Override
public void someEvent() {
System.out.println(name+" sees event!");
}
}
public static class Game {
private List<Listener> listeners;
public Game() {
listeners = new ArrayList<Listener>();
}
public void start() {
Executors.newFixedThreadPool(1).execute(new Runnable(){
@Override
public void run() {
for(;;) {
try {
//Listen to game server over network
Thread.sleep(1000); //<- think blocking read
synchronized (Game.this) {
for (Listener l : listeners) {
l.someEvent();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
public synchronized void addListener(Listener l) {
listeners.add(l);
}
}
public static void main(String[] args) throws InterruptedException {
Game game = new Game();
game.start();
Thread.sleep(1000);
//Someone joins the game
new Player(game);
}
}
//Code runs, won't terminate and will probably never show the flaw.
似乎一切都很好:对列表的访问是正确同步的。缺点是这个例子将Player.this泄漏给正在运行线程的Game。
决赛非常scary:
...编译器有很大的自由来跨越同步障碍移动最终字段的读取 ...
这几乎击败了所有正确的同步。但幸运的是
只有在完全初始化对象后才能看到对象的引用的线程才能保证看到该对象的正确初始化值#s
final
个字段。
在示例中,构造函数将对象引用写入列表。 (因此尚未完全初始化,因为构造函数没有完成。)在写入之后,构造函数仍未完成。它只需要从构造函数返回,但让我们假设它还没有。现在,执行程序可以完成其工作并向所有侦听器广播事件,包括尚未初始化的播放器对象!可能无法写入播放器(名称)的最后一个字段,并将导致打印null sees event!
。
答案 2 :(得分:13)
您拥有的最佳选择:
WindowFocusListener
部分(也可以是内部或匿名)。最好的解决方案,这样每个类都有特定的目的。使用单例作为漏洞构造函数的变通方法并不是很有效。
答案 3 :(得分:12)
这是一个很好的案例,说明创建类实例的工厂会有所帮助。如果Factory负责创建类的实例,那么您将拥有一个调用构造函数的集中位置,并且向代码中添加必需的init()
方法将非常简单。
关于你的直接解决方案,我建议你将任何泄漏this
的调用移动到构造函数的最后一行,然后在你“证明”它是安全的之后用注释来抑制它们。这样做。
在IntelliJ IDEA中,您可以使用以上注释中的以下注释来抑制此警告:
//noinspection ThisEscapedInObjectConstruction
答案 4 :(得分:4)
可以写:
addWindowFocusListener(Singleton.this);
这将阻止NB显示警告。
答案 5 :(得分:2)
使用嵌套类(由Colin建议)可能是您的最佳选择。这是伪代码:
private Singleton() {
...
}
public static Singleton getInstance() {
...
instance = new Singleton();
addWindowFocusListener( new MyListener() );
...
private class MyListener implements WindowFocusListener {
...
}
}
答案 6 :(得分:2)
不需要单独的侦听器类。
public class Singleton implements WindowFocusListener {
private Singleton() {
...
}
private void init() {
addWindowFocusListener(this);
}
public static Singleton getInstance() {
...
if(instance != null) {
instance = new Singleton();
instance.init();
}
...
}
}
答案 7 :(得分:1)
注释@SuppressWarnings(“LeakingThisInConstructor”)仅适用于类而不适用于构造函数本身。
Solusion我会建议: 创建私有方法init(){/ *在这里使用* /}并从构造函数中调用它。 NetBeans不会警告您。
答案 8 :(得分:0)
假设您最初有一个这样的类,它将自己用作ActionListener,因此您最终调用addActionListener(this)来生成警告。
private class CloseWindow extends JFrame implements ActionListener {
public CloseWindow(String e) {
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLayout(new BorderLayout());
JButton exitButton = new JButton("Close");
exitButton.addActionListener(this);
add(exitButton, BorderLayout.SOUTH);
}
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
if(actionCommand.equals("Close")) {
dispose();
}
}
}
正如@Colin Hebert所提到的,你可以把ActionListener分成它自己的类。当然,这需要引用要调用.dispose()的JFrame。如果您不想填充变量名称空间,并且希望能够将ActionListener用于多个JFrame,则可以使用getSource()来检索按钮,然后执行一系列getParent()调用检索扩展JFrame的类,然后调用getSuperclass以确保它是一个JFrame。
private class CloseWindow extends JFrame {
public CloseWindow(String e) {
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
setLayout(new BorderLayout());
JButton exitButton = new JButton("Close");
exitButton.addActionListener(new ExitListener());
add(exitButton, BorderLayout.SOUTH);
}
}
private class ExitListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
String actionCommand = e.getActionCommand();
JButton sourceButton = (JButton)e.getSource();
Component frameCheck = sourceButton;
int i = 0;
String frameTest = "null";
Class<?> c;
while(!frameTest.equals("javax.swing.JFrame")) {
frameCheck = frameCheck.getParent();
c = frameCheck.getClass();
frameTest = c.getSuperclass().getName().toString();
}
JFrame frame = (JFrame)frameCheck;
if(actionCommand.equals("Close")) {
frame.dispose();
}
}
}
上面的代码适用于任何扩展JFrame的类的任何级别的子项。显然,如果你的对象只是一个JFrame,那只需要直接检查那个类而不是检查超类。
最终使用这个方法你会得到一个类似这样的引用:MainClass $ CloseWindow,它有超类JFrame,然后你将这个引用转换为JFrame并处理它。
答案 9 :(得分:0)
将您的this
换成双括号。如果Netbeans在子语句中,则默认忽略一些错误。
public MyClass() {
...
instances.add((this));
}