在构造函数警告中泄漏此内容

2022-08-31 11:32:51

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

我理解这个问题,在构造函数中调用一个方法并传递“”是危险的,因为“”可能尚未完全初始化。thisthis

在我的单例类中修复警告很容易,因为构造函数是私有的,并且仅从同一类调用。

旧代码(简化):

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方法)。


答案 1

由于您确保将 your 放在构造函数的末尾,因此恕我直言,您应该可以安全地告诉编译器简单地禁止显示警告 (*)。。就其本质而言,警告并不一定意味着有问题,它只是需要你的注意。instances.add(this)

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

@SuppressWarnings("LeakingThisInConstructor")

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


答案 2

[chiccodoro的评论:解释为什么/当泄漏可能会导致问题,即使泄漏语句放在构造函数的最后:]

最终字段语义不同于“正常”字段语义。举个例子,

我们玩网络游戏。让我们创建一个从网络检索数据的 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 泄漏给正在运行线程的游戏。

决赛非常可怕

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

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

只有在对象完全初始化才能看到对该对象的引用的线程保证看到该对象字段的正确初始化值。final

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