web-dev-qa-db-ja.com

コンストラクターの警告でこれを漏らす

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メソッドを呼び出します)。

77
asalamon74

コンストラクタの最後にinstances.add(this)を必ず配置するので、 私見は、単に警告を抑制するようにコンパイラに伝えるために安全でなければなりません (*)。警告は、その性質上、何か問題があることを必ずしも意味するものではなく、注意が必要です。

自分が何をしているかわかっている場合は、@SuppressWarnings注釈。彼のコメントで言及されたTerrelのように、NetBeans 6.9.1以降、次の注釈がそれを行います。

@SuppressWarnings("LeakingThisInConstructor")

(*)Update: IstharとSergeyが指摘したように、「あなたの質問のように」「漏れる」コンストラクタコードが完全に安全に見える場合がありますが、そうではありません。これを承認できる読者はいますか?上記の理由により、この回答を削除することを検討しています。

43
chiccodoro

[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.

すべてが良いようです:リストへのアクセスは正しく同期されています。欠点は、この例ではPlayer.thisがスレッドを実行しているGameにリークすることです。

最終はかなり 怖い

...コンパイラは、最終フィールドの読み取りを同期バリア ...

これは、適切な同期をすべて無効にします。しかし、幸いなことに

after そのオブジェクトが completely 初期化された後、オブジェクトへの参照のみを見ることができるスレッドは、そのために正しく初期化された値を見ることが保証されますオブジェクトのfinalフィールド。

この例では、コンストラクターはオブジェクト参照をリストに書き込みます。 (したがって、コンストラクターが終了しなかったため、まだ完全に初期化されていません。)書き込み後、コンストラクターはまだ実行されていません。コンストラクタから戻る必要がありますが、まだ戻っていないものとします。これで、エグゼキューターはそのジョブを実行し、まだ初期化されていないプレーヤーオブジェクトを含むすべてのリスナーにイベントをブロードキャストできます。プレーヤーの最後のフィールド(名前)は書き込まれない場合があり、null sees event!

35
Ishtar

あなたが持っている最高のオプション:

  • WindowFocusListener部分を別のクラスに抽出します(内部または匿名の場合もあります)。最善の解決策は、このように各クラスに特定の目的があります。
  • 警告メッセージは無視してください。

漏れやすいコンストラクターの回避策としてシングルトンを使用することは、実際には効率的ではありません。

13
Colin Hebert

これは、クラスのインスタンスを作成したファクトリが役立つ場合の良い例です。ファクトリーがクラスのインスタンスの作成を担当する場合、コンストラクターが呼び出される場所が一元化され、必要なinit()メソッドをコードに追加するのは簡単です。

あなたの直接の解決策については、thisをリークする呼び出しをコンストラクタの最後の行に移動し、それが安全であることが「証明」されたらアノテーションでそれらを抑制することをお勧めします。

IntelliJ IDEAでは、行のすぐ上にある次のコメントでこの警告を抑制することができます。
//noinspection ThisEscapedInObjectConstruction

12
Nate W.

書くことができます:

addWindowFocusListener(Singleton.this);

これにより、NBが警告を表示しないようにします。

4
Andrew

ネストされたクラス(Colinが提案する)を使用するのがおそらく最良の選択肢です。擬似コードは次のとおりです。

private Singleton() {
  ...
}

public static Singleton getInstance() {

  ...
  instance = new Singleton();
  addWindowFocusListener( new MyListener() );
  ...

  private class MyListener implements WindowFocusListener {
  ...
  }
}
2
Ron Stern

個別のリスナークラスは必要ありません。

public class Singleton implements WindowFocusListener {

    private Singleton() {
      ...
    }    

    private void init() {
      addWindowFocusListener(this);
    }

    public static Singleton getInstance() {    
      ...
      if(instance != null) {
        instance = new Singleton();
        instance.init();
      }
      ...
    }
}
2
jkuruvila

アノテーション@SuppressWarnings( "LeakingThisInConstructor")は、コンストラクター自体ではなく、クラスにのみ適用されます。

Solusion私はお勧めします:プライベートメソッドinit(){/ * this here use this * /}を作成し、コンストラクターから呼び出します。 NetBeansは警告しません。

1
CAB

もともと、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を拡張するClassを取得し、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である場合は、スーパークラスをチェックするのではなく、そのクラスを直接チェックするだけです。

最終的にこのメソッドを使用すると、スーパークラスJFrameを持つMainClass $ CloseWindowのようなものへの参照を取得し、その参照をJFrameにキャストして破棄します。

0
sage88

thisを二重括弧で囲みます。 Netbeansは、サブステートメントにある場合、デフォルトでいくつかのエラーを無視します。

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

https://stackoverflow.com/a/835799

0
user7868