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