在构造函数警告中泄漏这个
我想避免(大部分)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方法)。
既然你确保把你的instances.add(this)
放在构造函数的末尾,那么你应该安全的告诉编译器只是禁止警告 (*) 。 从本质上讲,警告并不一定意味着有什么问题,而只是需要你的注意。
如果你知道你在做什么,你可以使用@SuppressWarnings
注解。 和Terrel在评论中提到的一样,NetBeans 6.9.1中提供了以下注释:
@SuppressWarnings("LeakingThisInConstructor")
(*)更新:正如Isthar和Sergey指出的,有些情况下,“泄漏”的构造函数代码看起来可能非常安全(如在你的问题中),但事实并非如此。 有更多的读者可以批准吗? 我正在考虑删除这个答案为所述的原因。
[注:chiccodoro:解释为什么/当泄漏this
可能会导致问题,即使泄漏的声明放在构造函数的最后:]
最终字段语义不同于“正常”字段语义。 一个例子,
我们玩networking游戏。 让一个Game对象从networking中获取数据,一个Player对象监听来自游戏的事件的行为。 游戏对象隐藏所有的networking细节,玩家只对事件感兴趣:
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。
决赛是非常可怕的 :
编译器有很大的自由来移动同步障碍的最终字段的读取…
这几乎失败了所有适当的同步。 但幸运的是
一个线程,只能看到一个对象的引用后 ,该对象已被完全初始化,保证看到该对象的
final
字段正确初始化的值。
在该示例中,构造函数将对象引用写入列表。 (因此还没有完全初始化,因为构造函数没有完成。)写完之后,构造函数仍然没有完成。 它只是从构造函数返回,但是我们假设它还没有。 现在执行者可以完成工作,并将事件广播给所有的听众,包括尚未初始化的播放器对象! 玩家(名字)的最后一个字段可能不会被写入,并且会导致打印null sees event!
。
创build类的实例的工厂将会很有帮助。 如果一个工厂负责创build类的实例,那么你将拥有一个中心位置,在这个位置调用构造函数,并且将一个必需的init()
方法添加到代码中将是微不足道的。
关于您的直接解决scheme,我build议您将任何this
泄漏的调用移动到构造函数的最后一行,然后在“certificate”安全的情况下使用注释将其压制。
在IntelliJ IDEA中,你可以用下面的注释来消除这个警告:
//noinspection ThisEscapedInObjectConstruction
你有最好的select:
- 在另一个类中提取
WindowFocusListener
部分(也可以是内部或匿名的)。 最好的解决办法就是这样每个class级都有一个特定的目的。 - 忽略警告消息。
使用单例作为泄漏构造函数的解决方法不是很有效。
可以写:
addWindowFocusListener(Singleton.this);
这将防止NB显示警告。
使用嵌套类(如科林build议)可能是您最好的select。 这里是伪代码:
private Singleton() { ... } public static Singleton getInstance() { ... instance = new Singleton(); addWindowFocusListener( new MyListener() ); ... private class MyListener implements WindowFocusListener { ... } }
注释@SuppressWarnings(“LeakingThisInConstructor”)只适用于类不向构造函数本身。
妄想我会build议:创build私有方法init(){/ *在这里使用* /}并从构造函数中调用它。 NetBeans不会警告你。
不需要单独的监听器类。
public class Singleton implements WindowFocusListener { private Singleton() { ... } private void init() { addWindowFocusListener(this); } public static Singleton getInstance() { ... if(instance != null) { instance = new Singleton(); instance.init(); } ... } }
用双括号包裹你的this
。 如果它们在子语句中,Netbeans默认会忽略一些错误。
public MyClass() { ... instances.add((this)); }
假设你最初有一个这样的类,它本身就是一个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。 如果您不想填充variables名称空间,并且希望能够将ActionListener用于多个JFrame,则可以使用getSource()来获取button,然后调用一系列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的任何级别的子级的任何button。 显然,如果你的对象只是一个JFrame,那只是直接检查这个类而不是检查超类。
最终使用这种方法你得到了这样的引用:MainClass $ CloseWindow它有超类JFrame,然后你将该引用转换为JFrame和处置它。