Утечка в предупреждении конструктора - PullRequest
76 голосов
/ 13 октября 2010

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

Ответы [ 10 ]

43 голосов
/ 13 октября 2010

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

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

@SuppressWarnings("LeakingThisInConstructor")

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

35 голосов
/ 14 апреля 2014

[Замечание от 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!.

13 голосов
/ 13 октября 2010

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

  • Извлеките вашу WindowFocusListener часть в другой класс (также может быть внутренним или анонимным).Наилучшее решение - в этом случае у каждого класса есть определенное назначение.
  • Игнорировать предупреждение.

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

12 голосов
/ 13 октября 2010

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

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

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

4 голосов
/ 09 января 2013

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

addWindowFocusListener(Singleton.this);

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

2 голосов
/ 10 января 2012

Нет необходимости в отдельном классе слушателя.

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 голосов
/ 19 мая 2011

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

private Singleton() {
  ...
}

public static Singleton getInstance() {

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

  private class MyListener implements WindowFocusListener {
  ...
  }
}
1 голос
/ 08 февраля 2011

Аннотация @SuppressWarnings ("LeakingThisInConstructor"), применимая только к классу a, а не к самому конструктору.

Решение Я бы предложил: создать приватный метод init () {/ * используйте это здесь * /}и вызвать его из конструктора.NetBeans не предупредит вас.

0 голосов
/ 04 мая 2015

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

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

https://stackoverflow.com/a/8357990

0 голосов
/ 23 сентября 2013

Скажем, у вас изначально был такой класс, который использовал себя как 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 и избавляетесь от нее.

...