web-dev-qa-db-fra.com

Pourquoi cette instruction ne renvoie-t-elle pas une erreur StackOverflowError?

Je viens de voir ce bizarre morceau de code dans une autre question. Je pensais que cela entraînerait un StackOverflowError jeté, mais il ne le fait pas ...

public class Node {
    private Object one;
    private Object two;
    public static Node NIL = new Node(Node.NIL, Node.NIL);

    public Node(Object one, Object two) {
        this.one = one;
        this.two = two;
    }
}

Je pensais que ça allait exploser, à cause du Node.NIL se référençant pour construire.

Je ne peux pas comprendre pourquoi cela ne fonctionne pas.

73
Anthony Raymond

NIL est une variable statique. Il est initialisé une fois, lorsque la classe est initialisée. Lorsqu'il est initialisé, une seule instance Node est créée. La création de cette Node ne déclenche pas la création d'autres instances de Node, il n'y a donc pas de chaîne d'appels infinie. Passer Node.NIL À l'appel du constructeur a le même effet que passer null, car Node.NIL N'est pas encore initialisé lorsque le constructeur est appelé. Par conséquent, public static Node NIL = new Node(Node.NIL, Node.NIL); est identique à public static Node NIL = new Node(null, null);.

Si, d'autre part, NIL était une variable d'instance (et n'était pas passée en argument au constructeur Node, car le compilateur vous aurait empêché de la passer au constructeur dans dans ce cas), il serait initialisé à chaque création d'une instance de Node, ce qui créerait une nouvelle instance de Node, dont la création initialiserait une autre variable d'instance NIL, conduisant à une chaîne infinie d'appels de constructeur qui se termineraient par StackOverflowError.

100
Eran

La variable NIL reçoit d'abord la valeur null puis est initialisée de haut en bas. Ce n'est pas une fonction et n'est pas définie récursivement. Tout champ statique que vous utilisez avant son initialisation a la valeur par défaut et votre code est le même que

public static Node {
    public static Node NIL;

    static {
        NIL = new Node(null /*Node.NIL*/, null /*Node.NIL*/);
    }

    public Node(Object one, Object two) {
        // Assign values to fields
    }
}

Ce n'est pas différent de l'écriture

NIL = null; // set implicitly
NIL = new Node(NIL, NIL);

Si vous définissez un fonction ou méthode comme ceci, vous obtiendrez une StackoverflowException

Node NIL(Node a, Node b) {
    return NIL(NIL(a, b), NIL(a, b));
}
27
Peter Lawrey

La clé pour comprendre pourquoi elle ne provoque pas d'initialisation infinie est que lorsque la classe Node est en cours d'initialisation, la JVM en garde la trace et évite réinitialisation lors d'une récursivité référence à la classe dans son initialisation d'origine. Ceci est détaillé dans cette section de la spécification de langue :

Étant donné que le langage de programmation Java est multithread, l'initialisation d'une classe ou d'une interface nécessite une synchronisation minutieuse, car un autre thread peut essayer d'initialiser la même classe ou interface en même temps. Il est également possible que l'initialisation d'une classe ou d'une interface soit demandée récursivement dans le cadre de l'initialisation de cette classe ou interface ; par exemple, un initialiseur de variable dans la classe A peut invoquer une méthode d'une classe non liée B, qui peut à son tour invoquer une méthode de classe A. L'implémentation de la machine virtuelle Java est responsable de la synchronisation et de l'initialisation récursive en utilisant le suivant la procédure.

Ainsi, tandis que l'initialiseur statique crée l'instance statique NIL, la référence à Node.NIL dans le cadre de l'appel du constructeur ne réexécute pas l'initialiseur statique. Au lieu de cela, il fait simplement référence à la valeur que la référence NIL a à ce moment, qui est null dans ce cas.

20
manouti