Question

Je voudrais éviter (la plupart des) mises en garde de Netbeans 6.9.1, et j'ai un problème avec l'avertissement 'Leaking this in constructor'.

Je comprends le problème, appeler une méthode dans le constructeur et le passage « this » est dangereux, car « this » peut ne pas avoir été complètement initialisé.

Il était facile de fixer l'avertissement dans mes classes singleton, parce que le constructeur est privé et seulement appelé de la même classe.

Ancien code (simplifié):

private Singleton() {
  ...
  addWindowFocusListener(this);
}

public static Singleton getInstance() {

  ...
  instance = new Singleton();
  ...
}

Nouveau code (simplifié):

private Singleton() {
  ...
}

public static Singleton getInstance() {

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

Ce correctif ne fonctionne pas si le constructeur est public et peut être appelé à partir d'autres classes. Comment est-il possible de fixer le code suivant:

public class MyClass {

  ...
  List<MyClass> instances = new ArrayList<MyClass>();
  ...

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

}

Bien sûr, je veux une solution qui ne nécessite pas de modifier tous mes codes en utilisant cette classe (en appelant une méthode d'initialisation par exemple).

Était-ce utile?

La solution

Puisque vous assurez-vous de mettre votre instances.add(this) à la fin du constructeur vous devrait à mon humble avis être sûr de dire au compilateur de supprimer simplement l'avertissement (*) . Un avertissement, par sa nature, ne signifie pas nécessairement qu'il ya quelque chose de mal, il faut juste votre attention.

Si vous savez ce que vous faites, vous pouvez utiliser une annotation @SuppressWarnings. Comme Terrel mentionné dans ses commentaires, l'annotation suivante, il fait que de NetBeans 6.9.1:

@SuppressWarnings("LeakingThisInConstructor")

(*) Mise à jour: Comme Isthar et Sergey ont fait remarquer il y a des cas où le code du constructeur « fuite » peut regarder parfaitement sûr (comme dans votre question) et pourtant il est pas. Y at-il plus de lecteurs qui peuvent approuver cela? J'envisage de supprimer cette réponse pour les raisons mentionnées.

Autres conseils

[Remarque par chiccodoro: explication Un pourquoi / quand une fuite this peut causer des problèmes, même si la déclaration des fuites est placée en dernière position dans le constructeur:]

sémantique champ final est différent de la sémantique sur le terrain « normal ». Par exemple,

Nous jouons un jeu en réseau. Permet de faire un objet de jeu récupérer des données à partir du réseau et un objet joueur qui écoute les événements du jeu d'agir en conséquence. L'objet du jeu se cache tous les détails du réseau, le joueur est seulement intéressé par les événements:

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.

Semble tout bon: l'accès à la liste est correctement synchronisé. Le défaut est que cet exemple fuit les Player.this au jeu, qui est en cours d'exécution d'un fil.

final est tout à fait effrayant :

  

... compilateurs ont une grande liberté de mouvement lit des champs finaux à travers les barrières de synchronisation ...

défaites à peu près tout synchronisation correcte. Mais heureusement,

  

Un fil qui ne peut voir une référence à un objet après cet objet a été complètement initialisé est garanti pour voir les valeurs initialisés correctement pour les champs final de cet objet.

Dans l'exemple, le constructeur écrit la référence des objets à la liste. (Et n'a donc pas été complètement initialisé encore, étant donné que le constructeur n'a pas terminé.) Après l'écriture, le constructeur est toujours pas fait. Il a juste de revenir du constructeur, mais supposons qu'il n'a pas encore. Maintenant, l'exécuteur testamentaire pourrait faire son travail et les événements diffusés à tous les auditeurs, y compris l'objet joueur pas encore initialisé! Le dernier champ du joueur (nom) ne peut pas être écrit et entraînera l'impression null sees event!.

Les meilleures options que vous avez:

  • Extrait de votre part WindowFocusListener dans une autre classe (pourrait aussi être intérieure ou anonyme). La meilleure solution, cette façon, chaque classe a un but spécifique.
  • Ignorer le message d'avertissement.

L'utilisation d'un singleton comme solution de contournement pour un constructeur qui fuit est pas vraiment efficace.

Ceci est un bon cas où une usine que les instances créées de votre classe serait utile. Si une usine a été responsable de la création d'instances de votre classe, alors vous avez un emplacement centralisé où le constructeur est appelé, et il serait trivial d'ajouter une méthode de init() nécessaire à votre code.

En ce qui concerne votre solution immédiate, je vous suggère de passer des appels this fuite à la dernière ligne de votre constructeur, puis les supprimer avec une annotation une fois que vous avez « prouvé » qu'il est sûr de le faire.

Dans IntelliJ IDEA, vous pouvez supprimer cet avertissement avec le commentaire suivant juste au-dessus de la ligne:
//noinspection ThisEscapedInObjectConstruction

On peut écrire:

addWindowFocusListener(Singleton.this);

Cela permettra d'éviter NB de montrer l'avertissement.

En utilisant une classe imbriquée (comme suggéré par Colin) est probablement votre meilleure option. Voici le pseudocode:

private Singleton() {
  ...
}

public static Singleton getInstance() {

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

  private class MyListener implements WindowFocusListener {
  ...
  }
}

Il n'y a pas besoin de classe d'écouteur.

public class Singleton implements WindowFocusListener {

    private Singleton() {
      ...
    }    

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

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

Les @SuppressWarnings d'annotation ( "LeakingThisInConstructor") applicable uniquement à la classe un pas au constructeur lui-même.

solusion Je suggère: créer init méthode privée () {/ * utiliser ici * /} et l'appeler du constructeur. Les NetBeans ne vous avertit pas.

Dites que vous avez eu à l'origine une classe comme celle qui se sert d'ActionListener et donc vous finissez par appeler addActionListener (ce) qui génère l'avertissement.

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

Comme @Colin Hébert mentionné, vous pouvez séparer le ActionListener dehors dans sa propre classe. Bien sûr, cela serait alors besoin d'une référence à la JFrame que vous voulez appeler .Dispose () sur. Si vous préférez ne pas remplir votre espace de nom variable, et que vous voulez être en mesure d'utiliser le ActionListener pour plusieurs JFrames, vous pouvez le faire avec pour récupérer le bouton getSource () suivi d'une chaîne d'appels getParent () à récupérer la classe qui extends JFrame puis appelez getSuperclass pour vous assurer qu'il est un 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();
        }
    }
}

Le code ci-dessus fonctionne pour tout bouton qui est un enfant à tout niveau d'une classe qui extends JFrame. Il est évident que si votre objet est tout simplement un JFrame il est juste une question de vérifier cette classe directement plutôt que de vérifier la super classe.

En fin de compte en utilisant cette méthode, vous obtenez une référence à quelque chose comme ceci:. MainClass $ CloseWindow qui a la classe super JFrame et alors vous coulée que la référence à JFrame et en disposer

Enveloppez votre this à deux crochets. Netbeans ne tient pas compte des erreurs par défaut si elles sont dans les sous-déclarations.

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

https://stackoverflow.com/a/8357990

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top