在构造函数警告中泄漏这个

asa*_*n74 79 java constructor netbeans this netbeans-6.9

我想避免(大多数)Netbeans 6.9.1的警告,我的'Leaking this in constructor'警告有问题.

我理解这个问题,在构造函数中调用一个方法并传递" this"是危险的,因为" this"可能没有完全初始化.

很容易在我的单例类中修复警告,因为构造函数是私有的,只能从同一个类中调用.

旧代码(简化):

private Singleton() {
  ...
  addWindowFocusListener(this);
}

public static Singleton getInstance() {

  ...
  instance = new Singleton();
  ...
}
Run Code Online (Sandbox Code Playgroud)

新代码(简化):

private Singleton() {
  ...
}

public static Singleton getInstance() {

  ...
  instance = new Singleton();
  addWindowFocusListener( instance );
  ...
}
Run Code Online (Sandbox Code Playgroud)

如果构造函数是公共的并且可以从其他类调用,则此修复不起作用.如何修复以下代码:

public class MyClass {

  ...
  List<MyClass> instances = new ArrayList<MyClass>();
  ...

  public MyClass() {
    ...
    instances.add(this);
  }

}
Run Code Online (Sandbox Code Playgroud)

当然我想要一个修复,它不需要使用这个类修改我的所有代码(例如通过调用init方法).

chi*_*oro 44

既然你确保把你instances.add(this)放在构造函数的末尾,你应该安全告诉编译器简单地抑制警告 (*).就其本质而言,警告并不一定意味着出现了问题,只需要您的注意.

如果您知道自己在做什么,可以使用@SuppressWarnings注释.就像Terrel在他的评论中提到的那样,以下注释是从NetBeans 6.9.1开始的:

@SuppressWarnings("LeakingThisInConstructor")
Run Code Online (Sandbox Code Playgroud)

(*)更新:正如Isthar和Sergey所指出的,有些情况下"泄漏"的构造函数代码看起来非常安全(如在您的问题中),但事实并非如此.有更多读者可以批准吗?我正考虑因上述原因删除这个答案.

  • 这在单线程设置中仅是"安全的".从JLS 7 17.5:"当一个对象的构造函数*完成*时,它被认为是完全初始化的.在该对象被*完全*初始化之后,只能看到对象引用的线程可以保证看到正确初始化的值该对象的最终字段".如果你将`this`泄漏到构造函数中的另一个线程,就没有这样的保证!此外,'在构造函数的末尾'可以由VM重新排序. (16认同)
  • 如果其他一些课程扩展了这门课怎么办?那么即使`this`在构造函数的末尾泄露,它仍然不在_construction进程的末尾_,对吧?这意味着即使在单线程环境中它也可能不安全.也就是说,除非类是`final`或者它的构造函数是私有的. (3认同)
  • @Ishtar - 这些都是强项!我有两个问题可以更好地理解它们:ad 1 - 你将如何从构造函数内部将 `this` 传递给不同的线程?广告 2 - 即使重新排序改变了代码的语义,VM 是否可能重新排序构造函数中的代码行?- 我建议你对此给出一个新的答案,因为它与目前所说的其他事情大不相同,它会让你更自由地阐述;-) (2认同)

Ish*_*tar 36

[chiccodoro备注:解释为什么/何时泄漏this可能导致问题,即使泄漏语句最后放在构造函数中:]

最终字段语义不同于"普通"字段语义.一个例子,

我们玩网络游戏.让我们让一个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.
Run Code Online (Sandbox Code Playgroud)

似乎一切都很好:对列表的访问是正确同步的.缺点是这个例子将Player.this泄漏给正在运行线程的Game.

决赛是非常可怕的:

...编译器有很大的自由来跨同步障碍移动最终字段的读取...

这几乎打败了所有正确的同步.但幸运的是

该对象完全初始化之后只能看到对象引用的线程可以保证看到该对象final字段的正确初始化值.

在该示例中,构造函数将对象引用写入列表.(因此尚未完全初始化,因为构造函数没有完成.)在写入之后,构造函数仍未完成.它只需要从构造函数返回,但我们假设它还没有.现在,执行程序可以完成其工作并向所有侦听器广播事件,包括尚未初始化的播放器对象!播放器(名称)的最后一个字段可能无法写入,并将导致打印null sees event!.

  • 顺便说一句 - 也许OP的问题是一个设计问题.一个对象在创建时在某处注册的事实可能表明它的构造函数做了一些不适合的事情:构造函数应初始化实例而不修改任何其他对象. (3认同)

Col*_*ert 13

您拥有的最佳选择:

  • WindowFocusListener在另一个课程中提取你的部分(也可以是内部或匿名).最好的解决方案,这样每个类都有特定的目的.
  • 忽略警告消息.

使用单例作为漏洞构造函数的变通方法并不是很有效.


Nat*_* W. 12

这是创建类的实例的工厂有用的好例子.如果Factory负责创建类的实例,那么您将拥有一个调用构造函数的集中位置,并且init()向代码中添加必需的方法将是微不足道的.

关于你的直接解决方案,我建议你移动任何泄漏this到构造函数最后一行的调用,然后在你"证明"这样做是安全的时候用注释来抑制它们.

在IntelliJ IDEA中,您可以使用行上方的以下注释来禁止此警告:
//noinspection ThisEscapedInObjectConstruction