Introducción

El objetivo principal de los programas informáticos es el procesamiento de datos. Para procesar datos es necesario almacenarlos de alguna manera. Propongo comprender cómo se almacenan los datos.
Asignación e Inicialización en Java - 1

variables

Las variables son contenedores que almacenan cualquier dato. Veamos el tutorial oficial de Oracle: Declaración de variables miembro . Según este Tutorial, existen varios tipos de variables:
  • Campos : variables declaradas en la clase;
  • Variables locales : variables en un método o bloque de código;
  • Parámetros : variables en la declaración del método (en la firma).
Todas las variables deben tener un tipo de variable y un nombre de variable.
  • El tipo de variable indica qué datos representa la variable (es decir, qué datos puede almacenar). Como sabemos, el tipo de una variable puede ser primitiva (primitives ) u objeto , no primitiva (Non-primitive). En el caso de las variables de objeto, su tipo se describe mediante una clase específica.
  • El nombre de la variable debe estar en minúsculas, en formato camel. Puede leer más sobre nombres en " Variables:Nombres ".
Además, si una variable de nivel de clase, es decir es un campo de clase, se puede especificar un modificador de acceso para él. Consulte Controlar el acceso a los miembros de una clase para obtener más detalles .

Declaración de variables

Entonces, recordamos qué es una variable. Para comenzar a trabajar con una variable, es necesario declararla. Primero, veamos una variable local. En lugar de un IDE, por conveniencia, usaremos la solución en línea de tutorialspoint: IDE en línea . Ejecutemos este sencillo programa en su IDE en línea:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
Entonces, como puede ver, hemos declarado una variable local con nombre numbery tipo int. Pulsamos el botón “Ejecutar” y obtenemos el error:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
¿Qué pasó? Declaramos una variable, pero no inicializamos su valor. Vale la pena señalar que este error no ocurrió en el momento de la ejecución (es decir, no en tiempo de ejecución), sino en el momento de la compilación. El compilador inteligente comprobó si la variable local se inicializaría antes de acceder a ella o no. Por lo tanto, de esto se derivan las siguientes afirmaciones:
  • Sólo se debe acceder a las variables locales después de que se hayan inicializado;
  • Las variables locales no tienen valores predeterminados;
  • Los valores de las variables locales se verifican en tiempo de compilación.
Entonces, se nos dice que la variable debe inicializarse. Inicializar una variable es asignar un valor a una variable. Entonces averigüemos qué es y por qué.

Inicializando una variable local

Inicializar variables es uno de los temas más complicados en Java, porque... está muy relacionado con el trabajo con la memoria, la implementación de JVM, la especificación de JVM y otras cosas igualmente aterradoras y complicadas. Pero puedes intentar resolverlo al menos hasta cierto punto. Vayamos de lo simple a lo complejo. Para inicializar la variable, usaremos el operador de asignación y cambiaremos la línea en nuestro código anterior:
int number = 2;
En esta opción no habrá errores y el valor se mostrará en pantalla. ¿Qué pasa en este caso? Intentemos razonar. Si queremos asignar un valor a una variable, entonces queremos que esa variable almacene un valor. Resulta que el valor debe almacenarse en algún lugar, pero ¿dónde? ¿En disco? Pero esto es muy lento y puede imponernos restricciones. Resulta que el único lugar donde podemos almacenar datos de manera rápida y eficiente “aquí y ahora” es la memoria. Esto significa que necesitamos asignar algo de espacio en la memoria. Esto es cierto. Cuando se inicializa una variable, se le asignará espacio en la memoria asignada al proceso Java dentro del cual se ejecutará nuestro programa. La memoria asignada a un proceso java se divide en varias áreas o zonas. Cuál de ellos asignará espacio depende del tipo de variable declarada. La memoria se divide en las siguientes secciones: montón, pila y no montón . Comencemos con la memoria de pila. Pila se traduce como pila (por ejemplo, una pila de libros). Es una estructura de datos LIFO (Último en entrar, primero en salir). Es decir, como una pila de libros. Cuando le agregamos libros, los ponemos encima y cuando los quitamos, tomamos el de arriba (es decir, el que se agregó más recientemente). Entonces, lanzamos nuestro programa. Como sabemos, un programa Java es ejecutado por una JVM, es decir, una máquina virtual Java. La JVM debe saber dónde debe comenzar la ejecución del programa. Para hacer esto, declaramos un método principal, que se llama "punto de entrada". Para la ejecución en la JVM, se crea un hilo principal (Thread). Cuando se crea un hilo, se le asigna su propia pila en la memoria. Esta pila consta de marcos. Cuando cada nuevo método se ejecuta en un hilo, se le asignará un nuevo marco y se agregará a la parte superior de la pila (como un libro nuevo en una pila de libros). Este marco contendrá referencias a objetos y tipos primitivos. Sí, sí, nuestro int se almacenará en la pila, porque... int es un tipo primitivo. Antes de asignar una trama, la JVM debe comprender qué guardar allí. Es por esta razón que recibiremos el error “es posible que la variable no se haya inicializado”, porque si no se inicializa, entonces la JVM no podrá preparar la pila por nosotros. Por tanto, a la hora de compilar un programa, un compilador inteligente nos ayudará a evitar cometer errores y estropearlo todo. (!) Para mayor claridad, recomiendo un artículo fantástico : " Java Stack and Heap: Tutorial de asignación de memoria de Java ". Enlace a un vídeo igualmente interesante:
Una vez completada la ejecución de un método, los fotogramas asignados para estos métodos se eliminarán de la pila del subproceso y, junto con ellos, se borrará la memoria asignada para este fotograma con todos los datos.

Inicialización de variables de objetos locales

Cambiemos nuestro código nuevamente a un poco más complicado:
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
¿Qué pasará aquí? Hablemos de ello otra vez. La JVM sabe desde dónde debe ejecutar el programa, es decir ella ve el método principal. Crea un hilo y le asigna memoria (después de todo, un hilo necesita almacenar los datos necesarios para la ejecución en algún lugar). En este hilo, se asigna un marco para el método principal. A continuación creamos un objeto HelloWorld. Este objeto ya no se crea en la pila, sino en el montón. Porque el objeto no es un tipo primitivo, sino un tipo de objeto. Y la pila solo almacenará una referencia al objeto en el montón (de alguna manera debemos acceder a este objeto). A continuación, en la pila del método principal, se asignarán marcos para ejecutar el método println. Después de ejecutar el método principal, todos los fotogramas serán destruidos. Si se destruye el marco, se destruirán todos los datos. El objeto objeto no será destruido inmediatamente. En primer lugar, la referencia al mismo se destruirá y, por lo tanto, nadie volverá a hacer referencia al objeto y ya no será posible acceder a este objeto en la memoria. Una JVM inteligente tiene su propio mecanismo para esto: un recolector de basura (recolector de basura o GC para abreviar). Luego elimina de la memoria objetos a los que nadie más hace referencia. Este proceso se describió nuevamente en el enlace que figura arriba. Incluso hay un vídeo con una explicación.

Inicializando campos

La inicialización de los campos especificados en una clase se produce de forma especial dependiendo de si el campo es estático o no. Si un campo tiene la palabra clave static, entonces este campo se refiere a la clase misma, y ​​si no se especifica la palabra static, entonces este campo se refiere a una instancia de la clase. Veamos esto con un ejemplo:
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
En este ejemplo, los campos se inicializan en diferentes momentos. El campo numérico se inicializará después de que se cree el objeto de clase HelloWorld. Pero el campo de recuento se inicializará cuando la máquina virtual Java cargue la clase. La carga de clases es un tema aparte, por lo que no lo mezclaremos aquí. Vale la pena saber que las variables estáticas se inicializan cuando la clase se conoce en tiempo de ejecución. Hay algo más importante aquí y ya lo habrás notado. No especificamos el valor en ninguna parte, pero funciona. Y de hecho. Variables que son campos, si no tienen un valor especificado se inicializan con un valor predeterminado. Para valores numéricos, esto es 0 o 0,0 para números de coma flotante. Para booleanos esto es falso. Y para todas las variables de tipo de objeto el valor será nulo (hablaremos de esto más adelante). Al parecer, ¿por qué es así? Sino porque los objetos se crean en Heap (en el montón). El trabajo con esta área se realiza en Runtime. Y podemos inicializar estas variables en tiempo de ejecución, a diferencia de la pila, cuya memoria debe prepararse antes de la ejecución. Así funciona la memoria en Java. Pero aquí hay una característica más. Este pequeño fragmento toca diferentes rincones de la memoria. Como recordamos, se asigna una trama en la memoria de pila para el método principal. Este marco almacena una referencia a un objeto en la memoria del montón. Pero, ¿dónde se almacena el recuento entonces? Como recordamos, esta variable se inicializa inmediatamente, antes de que se cree el objeto en el montón. Esta es una pregunta realmente complicada. Antes de Java 8, existía un área de memoria llamada PERMGEN. A partir de Java 8, esta área ha cambiado y se llama METASPACE. Esencialmente, las variables estáticas son parte de la definición de clase, es decir sus metadatos. Por tanto, es lógico que se almacene en el repositorio de metadatos, METASPACE. MetaSpace pertenece a la misma área de memoria No Heap y es parte de ella. También es importante tener en cuenta que se tiene en cuenta el orden en el que se declaran las variables. Por ejemplo, hay un error en este código:
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

que es nulo

Como se indicó anteriormente, las variables de tipos de objetos, si son campos de una clase, se inicializan con valores predeterminados y ese valor predeterminado es nulo. Pero ¿qué es nulo en Java? Lo primero que hay que recordar es que los tipos primitivos no pueden ser nulos. Y todo porque nulo es una referencia especial que no se refiere a ningún lugar, a ningún objeto. Por tanto, sólo una variable de objeto puede ser nula. Lo segundo que es importante entender es que null es una referencia. Hago referencia también tienen su peso. Sobre este tema, puede leer la pregunta en stackoverflow: " ¿La variable nula requiere espacio en la memoria ?".

Bloques de inicialización

Al considerar la inicialización de variables, sería un pecado no considerar los bloques de inicialización. Se parece a esto:
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

}
El orden de salida será: bloque estático, bloque, Constructor. Como podemos ver, los bloques de inicialización se ejecutan antes que el constructor. Y, a veces, este puede ser un medio conveniente de inicialización.

Conclusión

Espero que esta breve descripción haya podido proporcionar una idea de cómo funciona y por qué. #viacheslav