IT TIP

생성자 경고에서 이것을 누출

itqueen 2020. 10. 14. 21:32
반응형

생성자 경고에서 이것을 누출


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)생성자의 끝에 넣어야하므로 IMHO는 컴파일러에게 경고 (*) 를 억제하도록 지시하는 것이 안전해야합니다 . 경고는 본질적으로 문제가 있음을 의미하는 것이 아니라주의가 필요합니다.

수행중인 작업을 알고 있다면 @SuppressWarnings주석을 사용할 수 있습니다 . 그의 주석에서 언급 한 Terrel과 같이 다음 주석은 NetBeans 6.9.1부터 수행합니다.

@SuppressWarnings("LeakingThisInConstructor")

(*) 업데이트 : Isthar와 Sergey가 지적했듯이 "누수"생성자 코드가 (귀하의 질문에서와 같이) 완벽하게 안전 해 보일 수 있지만 그렇지 않은 경우가 있습니다. 이것을 승인 할 수있는 독자가 더 있습니까? 언급 된 이유로이 답변을 삭제할 것을 고려하고 있습니다.


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

모두 좋은 것 같습니다 : 목록에 대한 액세스가 올바르게 동기화되었습니다. 결함은이 예제가 스레드를 실행하는 Game에 Player.this를 유출한다는 것입니다.

결승전은 무섭습니다 .

... 컴파일러는 동기화 장벽을 넘어 최종 필드의 읽기를 이동할 수있는 많은 자유를 가지고 있습니다 .

이것은 모든 적절한 동기화를 거의 실패합니다. 하지만 다행히

객체가 완전히 초기화 된 후에 만 객체에 대한 참조를 볼 수있는 스레드는 해당 객체의 필드에 대해 올바르게 초기화 된 값을 볼 수 있습니다.final

예제에서 생성자는 목록에 대한 개체 참조를 작성합니다. (따라서 생성자가 완료되지 않았기 때문에 아직 완전히 초기화되지 않았습니다.) 쓰기 후에도 생성자는 아직 완료되지 않았습니다. 생성자에서 반환해야하지만 아직 반환하지 않았다고 가정 해 보겠습니다. 이제 실행기는 아직 초기화되지 않은 플레이어 객체를 포함하여 모든 리스너에게 작업을 수행하고 이벤트를 브로드 캐스트 할 수 있습니다! 플레이어 (이름)의 마지막 필드는 작성되지 않을 수 있으며 결과적으로 인쇄 null sees event!됩니다.


최고의 옵션 :

  • WindowFocusListener다른 클래스에서 부품을 추출합니다 (내부 또는 익명 일 수도 있음). 최상의 솔루션, 이런 식으로 각 클래스는 특정 목적을 가지고 있습니다.
  • 경고 메시지를 무시하십시오.

누수 생성자에 대한 해결 방법으로 싱글 톤을 사용하는 것은 실제로 효율적이지 않습니다.


이것은 클래스의 인스턴스를 생성 한 Factory가 도움이되는 좋은 경우입니다. 팩토리가 클래스의 인스턴스 생성을 담당했다면 생성자가 호출되는 중앙 위치를 갖게되며 필요한 init()메서드를 코드 에 추가하는 것은 간단 합니다.

즉각적인 해결책과 관련하여 누출 this되는 모든 호출을 생성자의 마지막 줄로 이동 한 다음 안전하다고 "증명"하면 주석으로 억제하는 것이 좋습니다.

IntelliJ IDEA에서 줄 바로 위에 다음 주석을 사용하여이 경고를 억제 할 수 있습니다.
//noinspection ThisEscapedInObjectConstruction


다음과 같이 쓸 수 있습니다.

addWindowFocusListener(Singleton.this);

이렇게하면 NB가 경고를 표시하지 않습니다.


Colin이 제안한 중첩 클래스를 사용하는 것이 아마도 최선의 선택 일 것입니다. 다음은 의사 코드입니다.

private Singleton() {
  ...
}

public static Singleton getInstance() {

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

  private class MyListener implements WindowFocusListener {
  ...
  }
}

별도의 리스너 클래스가 필요하지 않습니다.

public class Singleton implements WindowFocusListener {

    private Singleton() {
      ...
    }    

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

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

@SuppressWarnings ( "LeakingThisInConstructor") 주석은 생성자 자체가 아닌 클래스에만 적용됩니다.

Solusion 나는 제안합니다 : 개인 메서드 생성 init () {/ * 여기에서 이것을 사용 * /} 생성자에서 호출하십시오. NetBeans는 경고하지 않습니다.


원래 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();
        }
    }
}

As @Colin Hebert mentioned, you could separate the ActionListener out into its own class. Of course this would then require a reference to the JFrame that you want to call .dispose() on. If you'd prefer not to fill up your variable name space, and you want to be able to use the ActionListener for multiple JFrames, you could do it with getSource() to retrieve the button followed by a chain of getParent() calls to retrieve the Class that extends JFrame and then call getSuperclass to make sure it's a 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();
        }
    }
}

The above code will work for any button that is a child at any level of a class which extends JFrame. Obviously if your object just is a JFrame it's just a matter of checking that class directly rather than checking the super class.

Ultimately using this method you're getting a reference to something like this: MainClass$CloseWindow which has the super class JFrame and then you're casting that reference to JFrame and disposing of it.


Wrap your this in double brackets. Netbeans ignores some errors by default if they are in sub-statements.

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

https://stackoverflow.com/a/8357990

참고URL : https://stackoverflow.com/questions/3921616/leaking-this-in-constructor-warning

반응형