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


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

10 ответа:

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

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

@SuppressWarnings("LeakingThisInConstructor")

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

[замечание chiccodoro: объяснение почему/когда подтекает this может вызвать проблемы, даже если оператор утечки помещается последним в конструктор:]

конечная семантика поля отличается от "нормальной" семантики поля. Например,

мы играем в сетевую игру. Позволяет сделать игровой объект, извлекающий данные из сети, и объект игрока, который слушает события из игры, чтобы действовать соответственно. Игровой объект скрывает все детали сети, игрока интересуют только события:

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.

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

финал вполне страшно:

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

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

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

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

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

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

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

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

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

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

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

addWindowFocusListener(Singleton.this);

Это позволит предотвратить НБ показывать предупреждение.

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

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 () {/*используйте это здесь*/} и вызовите его из конструктора. Среда не предупреждают.

скажем, у вас изначально был такой класс, который использовал себя как 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 () on. Если вы предпочитаете не заполнять пространство имен переменных, и вы хотите иметь возможность использовать 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, это просто вопрос проверки этого класса напрямую, а не проверки супер класс.

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

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

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

https://stackoverflow.com/a/8357990