it-swarm.com.ru

Утечка в предупреждении конструктора

Я хотел бы избежать (большей части) предупреждений 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).

75
asalamon74

Поскольку вы ставите instances.add(this) в конце конструктора, вы иМХО должно быть безопасно сказать компилятору просто подавить предупреждение (*) . Предупреждение по своей природе не обязательно означает, что что-то не так, оно просто требует вашего внимания.

Если вы знаете, что делаете, вы можете использовать аннотацию @SuppressWarnings. Как Террел упоминал в своих комментариях, следующая аннотация делает это с NetBeans 6.9.1:

@SuppressWarnings("LeakingThisInConstructor")

(*) Обновление: Как указали Истар и Сергей, бывают случаи, когда «протекающий» код конструктора может выглядеть совершенно безопасным (как в вашем вопросе), и все же это не так. Есть ли еще читатели, которые могут это одобрить? Я рассматриваю возможность удаления этого ответа по указанным причинам.

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, которая запускает поток. 

Финал довольно страшно :

... у компиляторов есть большая свобода для перемещения чтений конечных полей через барьеры синхронизации ...

Это в значительной степени побеждает всю правильную синхронизацию. Но к счастью 

Поток, который может видеть только ссылку на объект после того, как этот объект был полностью инициализирован, гарантированно увидит правильно инициализированные значения для полей final этого объекта. 

В этом примере конструктор записывает ссылку на объект в список. (И, таким образом, еще не была полностью инициализирована, поскольку конструктор не завершил работу.) После записи конструктор все еще не завершен. Он просто должен вернуться из конструктора, но давайте предположим, что еще нет. Теперь исполнитель может выполнять свою работу и транслировать события всем слушателям, включая еще не инициализированный объект проигрывателя! Последнее поле игрока (имя) может быть не записано, и в результате будет напечатан null sees event!

34
Ishtar

Лучшие варианты у вас есть:

  • Извлеките свою часть WindowFocusListener в другой класс (также может быть внутренним или анонимным). Лучшее решение, таким образом, каждый класс имеет определенную цель.
  • Игнорировать предупреждающее сообщение.

Использование синглтона в качестве обходного пути для негерметичного конструктора не очень эффективно.

13
Colin Hebert

Это хороший случай, когда Фабрика, которая создала экземпляры вашего класса, была бы полезна. Если фабрика отвечала за создание экземпляров вашего класса, то у вас было бы централизованное расположение, где вызывался конструктор, и было бы тривиально добавить требуемый метод init() в ваш код.

Что касается вашего немедленного решения, я бы посоветовал вам переместить все вызовы с утечкой this в последнюю строку вашего конструктора, а затем подавить их с помощью аннотации, как только вы «доказали», что это безопасно.

В IntelliJ IDEA вы можете отключить это предупреждение следующим комментарием прямо над строкой:
//noinspection ThisEscapedInObjectConstruction 

12
Nate W.

Можно написать: 

addWindowFocusListener(Singleton.this);

Это предотвратит отображение NB предупреждения.

4
Andrew

Использование вложенного класса (как предлагает Колин), вероятно, ваш лучший вариант. Вот псевдокод:

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") применима только к классу, а не к самому конструктору.

Решение Я бы предложил: Создать приватный метод init () {/ * используйте это здесь * /} и вызовите его из конструктора. 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 на его собственный класс. Конечно, для этого потребуется ссылка на JFrame, для которого вы хотите вызвать .dispose (). Если вы предпочитаете не заполнять пространство имен переменных и хотите использовать ActionListener для нескольких JFrames, вы можете сделать это с помощью getSource (), чтобы получить кнопку, за которой следует цепочка вызовов 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. Очевидно, что если ваш объект просто является JFrame, это просто вопрос проверки этого класса напрямую, а не проверки суперкласса.

В конечном итоге, используя этот метод, вы получаете ссылку на что-то вроде этого: MainClass $ CloseWindow, который имеет суперкласс JFrame, а затем вы приводите эту ссылку к JFrame и удаляете ее.

0
sage88

Оберните ваш this в двойные скобки. NetBeans игнорирует некоторые ошибки по умолчанию, если они присутствуют в подвыражениях. 

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

https://stackoverflow.com/a/8357990

0
user7868