Pregunta ¿Por qué esta declaración no arroja un StackOverflowError?


Acabo de ver esto extraño pieza de código en otra pregunta. Pensé que resultaría en un StackOverflowError siendo arrojado, pero no ...

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

Pensé que iba a explotar, debido a la Node.NIL haciendo referencia a sí mismo para construir.

No puedo entender por qué no lo hace.


74
2018-01-30 09:37


origen


Respuestas:


NIL es una variable estática Se inicializa una vez, cuando la clase se inicializa. Cuando se inicializa, un solo Node instancia es creada. La creación de eso Node no desencadena la creación de ningún otro Node instancias, entonces no hay una cadena infinita de llamadas. Paso Node.NIL para la llamada del constructor tiene el mismo efecto que pasar null, ya que Node.NIL aún no se inicializó cuando se llama al constructor. Por lo tanto public static Node NIL = new Node(Node.NIL, Node.NIL); es lo mismo que public static Node NIL = new Node(null, null);.

Si, por otro lado, NIL era una variable de instancia (y no se pasó como un argumento al Node constructor, ya que el compilador le habría impedido pasarlo al constructor en ese caso), se inicializaría cada vez que una instancia de Node fue creado, lo que crearía un nuevo Node instancia, cuya creación inicializaría otra NIL variable de instancia, lo que lleva a una cadena infinita de llamadas al constructor que terminarían en StackOverflowError.


100
2018-01-30 09:39



los variable NIL recibe primero el valor null y luego se inicializa una vez de arriba a abajo. No es un función y no está definido recursivamente. Cualquier campo estático que utilice antes de inicializarse tiene el valor predeterminado y su código es el mismo 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
    }
}

Esto no es diferente a escribir

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

Si definiste un función o método así, obtendrías una StackoverflowException

Node NIL(Node a, Node b) {
    return NIL(NIL(a, b), NIL(a, b));
}

27
2018-01-30 09:44



La clave para entender por qué no causa la inicialización infinita es que cuando la clase Node se está inicializando, la JVM realiza un seguimiento de ello y evita reinicialización durante una referencia recursiva a la clase dentro de su inicialización original. Esto se detalla en esta sección de la especificación de idioma:

Debido a que el lenguaje de programación Java tiene múltiples subprocesos, la inicialización de una clase o interfaz requiere una sincronización cuidadosa, ya que algunos otros subprocesos pueden estar intentando inicializar la misma clase o interfaz al mismo tiempo. También existe la posibilidad de que la inicialización de una clase o interfaz se pueda solicitar recursivamente como parte de la inicialización de esa clase o interfaz; por ejemplo, un inicializador variable en la clase A podría invocar un método de una clase B no relacionada, que a su vez podría invocar un método de la clase A. La implementación de la Máquina virtual Java es responsable de cuidar la sincronización y la inicialización recursiva mediante el uso de siguiendo el procedimiento.

Entonces, mientras el inicializador estático está creando la instancia estática NIL, la referencia a Node.NIL como parte de la llamada de constructor no vuelve a ejecutar el inicializador estático de nuevo. En cambio, simplemente hace referencia a cualquier valor de referencia NILtiene en ese momento, que es null en este caso.


20
2018-01-30 09:49