79 votes

Java - Fuite dans le constructeur

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

Je comprends le problème, l'appel d'une méthode dans le constructeur et en passant "this" est dangereux, car "this" peut-être pas été entièrement initialisé.

Il était facile de fixer l'avertissement dans mon singleton classes, parce que le constructeur est privé et seulement appelé à partir 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é à d'autres classes. Comment est-il possible de corriger le code suivant:

public class MyClass {

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

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

}

Bien sûr, je veux un correctif qui ne nécessite pas de modifier tous mes codes à l'aide de cette classe ( par l'appel d'une méthode d'initialisation, par exemple).

44voto

chiccodoro Points 5881

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

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

@SuppressWarnings("LeakingThisInConstructor")

(*) Mise à jour: Comme Isthar l'a souligné dans son commentaire de ce genre de situation n'est pas nécessairement sans danger. Il "n'est" sûr " dans un thread unique paramètre. De JLS 7 17.5: "Un objet est considéré comme complètement initialisé lors de son constructeur se termine. Un thread qui ne peut voir une référence à un objet après que l'objet a été complètement initialisé, il est garanti à voir le initialisé correctement les valeurs pour l'objet final de champs". Pas de cette garantie si vous cette fuite vers un autre thread dans le constructeur! Aussi 'à la fin du constructeur" est peut-être réorganisées par la machine virtuelle."

Voir aussi Isthar de répondre ci-dessous pour plus de détails.

36voto

Ishtar Points 5751

[Remarque de chiccodoro: Une explication de pourquoi/quand les fuites this peut provoquer des problèmes, même si la fuite de la déclaration est placé en dernier dans le constructeur:]

Dernier champ sémantique est différent de "normal" champ sémantique. Un exemple,

Nous jouons un jeu en réseau. Permet de rendre un objet de Jeu de la récupération de données à partir du réseau et un Joueur de l'objet qui est à l'Écoute des événements à partir du jeu à agir en conséquence. Le jeu objet 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 problème, c'est que dans cet exemple les fuites le Joueur.ce Jeu, qui est en cours d'exécution d'un thread.

La finale est assez effrayant:

...les compilateurs ont une grande liberté de déplacer le lit de finale champs à travers la synchronisation des obstacles...

Ce joli beaucoup de défaites toutes bonne synchronisation. Mais heureusement

Un thread qui ne peut voir une référence à un objet après que l'objet a été complètement initialisé, il est garanti à voir le initialisé correctement les valeurs pour l'objet en final champs.

Dans l'exemple, le constructeur écrit les objets de référence à la liste. (Et n'a donc pas été complètement initialisé encore, puisque le constructeur n'a pas fini.) Après l'écriture, le constructeur n'est pas encore fait. Il suffit de le renvoyer au constructeur, mais imaginons qu'il n'a pas encore. Maintenant, l'exécuteur testamentaire pourrait faire son travail et de diffuser des événements pour tous les auditeurs, y compris les pas encore initialisé l'objet de joueur! Le dernier champ du joueur (nom) ne peut pas être écrit, ce qui donnera l'impression null sees event!.

13voto

Colin Hebert Points 40084

Les meilleures options que vous avez:

  • Extrayez votre WindowFocusListener dans une autre classe (peut également être interne ou anonyme). La meilleure solution, de cette façon, chaque classe a un objectif spécifique.
  • Ignorer le message d'avertissement.

Utiliser un singleton comme solution de contournement pour un constructeur qui fuit n'est pas vraiment efficace.

12voto

Nate W. Points 5211

C'est un bon cas d'une Usine qui a créé des instances de la classe serait utile. Si une Usine est responsable de la création des instances de votre classe, vous avez un emplacement centralisé où le constructeur est appelé, et il serait facile d'ajouter un requis init() méthode de votre code.

Quant à votre solution immédiate, je vous conseille de vous déplacer tous les appels de fuites this à la dernière ligne de votre constructeur, et ensuite les supprimer avec une annotation une fois que vous avez "prouver" qu'il est sécuritaire de le faire.

Dans IntelliJ IDEA, vous pouvez supprimer ce message d'avertissement avec le commentaire suivant à droite au-dessus de la ligne:
//noinspection ThisEscapedInObjectConstruction

4voto

Andrew Points 41

On peut écrire:

 addWindowFocusListener(Singleton.this);
 

Cela empêchera NB d'afficher l'avertissement.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X